Intent AI client: stream the upstream call, send adaptive thinking, raise the max-tokens default - #6959
Merged
Merged
Conversation
…6955) Three limits compounded in the one shared AI client, and they tightened exactly as the authored application grew: 1. The call was not streamed - a single blocking request whose 120-second window had to contain the entire generated document. 2. No `thinking` parameter was sent. On the configured default model (`claude-opus-4-8`) omitting it means running with NO thinking at all (adaptive must be sent explicitly on the 4.7/4.8 family; the old `budget_tokens` form is rejected with a 400), so a few hundred lines of structured YAML were emitted cold. 3. `DIRIGIBLE_INTENT_AI_MAX_TOKENS` defaulted to 8192, against a tool contract that re-emits the COMPLETE `app.intent` every turn AND every repair round. Fixed in that order, deliberately: without streaming, a larger budget or a longer reasoning pass turns a truncated answer into a timed-out one. - `"stream": true`, consumed as server-sent events. `assembleReply` concatenates the `text_delta`s as the answer and re-joins the matching `tool_use` block's `input_json_delta` fragments into the proposal - matched by the block's `index`, never "the last tool_use seen", so a fragment of another block can never corrupt it. Thinking deltas, `ping` and unknown event types are skipped: the API's versioning policy says new ones may appear. The per-request timeout becomes an outer bound (10 min) rather than a window the answer must fit into. - `thinking: {"type": "adaptive"}`, with the display default left alone - the reasoning is wanted for the answer's sake, not for anyone to read. - The `_MAX_TOKENS` default moves to 32768. The environment variable is unchanged; only the fallback moved. The default model is left alone: with thinking sent explicitly, reasoning no longer depends on it. The 412/502 error contract is unchanged, including a mid-stream `error` event (an overload the API reports inside a 200 response) mapping to the same `AssistantUpstreamException` a non-2xx status does. A proposal cut off mid-JSON now says so and names the ceiling, and a `stop_reason: "max_tokens"` is logged - neither cause is visible from either symptom otherwise. Both assistant surfaces (the Intent Editor agent and the Workbench Java assistant) go through this one client, so all three changes reach both. `ModelClientStreamTest` scripts a local SSE upstream: the assembly, the request body, a chunked answer flushed event by event, the interleaved-block case, a mid-stream error, truncation and a non-2xx status. The Builder shell's `IntentBuilderShellIT` stub now streams its proposal in narrow fragments for the same reason, and its client-side agent timeout - which was 7 minutes because it multiplied the old 120-second wall by three calls - is raised accordingly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6955.
Three compounding limits in the one shared AI client (
components/engine/engine-intent/.../ai/ModelClient.java), all of which tightened exactly as the authored application grew:requestBody()sent nothinkingparameter, and on the configured default model (claude-opus-4-8) omitting it means running with no thinking at all (adaptive must be sent explicitly on the 4.7/4.8 family; the oldbudget_tokensform is rejected with a 400). A few hundred lines of structured YAML were emitted cold.DIRIGIBLE_INTENT_AI_MAX_TOKENSdefaulted to8192, against a tool contract that re-emits the COMPLETEapp.intentevery turn and every repair round.Fixed in that order, deliberately: without streaming, a larger budget or a longer reasoning pass converts a truncated answer into a timed-out one.
What changed
"stream": true, consumed as server-sent events.assembleReplyconcatenates thetext_deltas as the answer and re-joins the matchingtool_useblock'sinput_json_deltafragments into the proposal - matched by the block'sindex, never "the lasttool_useseen", so a fragment of another block can never corrupt it. Thinking deltas,pingand unknown event types are skipped (the API's versioning policy is explicit that new ones may appear). The per-request timeout becomes an outer bound (10 min) rather than a window the answer has to fit into.thinking: {"type": "adaptive"}, with the display default (omitted) left alone - the reasoning is wanted for the answer's sake, not for anyone to read._MAX_TOKENSdefault 8192 -> 32768. The environment variable is unchanged; only the fallback moved. The default model is deliberately left alone - with thinking sent explicitly, reasoning no longer depends on which model is configured.Both assistant surfaces (the Intent Editor agent and the Workbench Java assistant) go through this one client, so all three changes reach both.
Error contract
Unchanged (412
AssistantNotConfiguredException/ 502AssistantUpstreamException), including a mid-streamerrorevent - an overload the API reports inside a 200 response - mapping to the sameAssistantUpstreamExceptiona non-2xx status does. Two new diagnostics for causes that are otherwise invisible from the symptom: a proposal cut off mid-JSON says so and names the ceiling, and astop_reason: "max_tokens"is logged.Tests
ModelClientStreamTest(new, network-free) scripts a local SSE upstream:pingand an unknown event type interleaved;stream: true,thinking.type == adaptiveand the configured ceiling (plus the raised default itself);tool_useblock never landing in the proposal;errorevent, a proposal truncated mid-JSON, a non-2xx status, and a blank API key.IntentBuilderShellIT's stub now answers with the event stream, splitting the tool input across several fragments on purpose - a stub that sent it in one piece would pass even if the client only ever read the first one. Its client-side agent timeout, which was 7 minutes because it multiplied the old 120-second wall by three calls, is raised accordingly.Green locally:
commons-config(10) +engine-intent(934) unit tests,formatter:validate, javadoc under-P release, andtests-integrationscompiles.🤖 Generated with Claude Code