@@ -51,6 +51,26 @@ func buildGenerateArgs(model, settingsPath string) []string {
5151 return args
5252}
5353
54+ // buildStreamingGenerateArgs is buildGenerateArgs for the stream-json path,
55+ // with the same isolation and auth-injection contract (see buildGenerateArgs).
56+ // --include-partial-messages enables the per-token stream_event envelopes
57+ // that PhaseFirstToken and PhaseGenerating are dispatched from, and
58+ // --verbose is required by the claude CLI for stream-json output.
59+ func buildStreamingGenerateArgs (model , settingsPath string ) []string {
60+ args := []string {
61+ "--print" ,
62+ "--output-format" , "stream-json" ,
63+ "--include-partial-messages" ,
64+ "--verbose" ,
65+ "--model" , model ,
66+ "--setting-sources" , "" ,
67+ }
68+ if settingsPath != "" {
69+ args = append (args , "--settings" , settingsPath )
70+ }
71+ return args
72+ }
73+
5474// writeAuthSettingsFile writes a minimal claude settings file containing only
5575// the given apiKeyHelper and returns its path plus a cleanup func. The file is
5676// created 0600 so the (possibly key-bearing) helper is no more exposed than the
@@ -183,12 +203,21 @@ func (c *ClaudeCodeAgent) GenerateText(ctx context.Context, prompt string, model
183203 return "" , classifyEnvelopeError (result , env .APIErrorStatus , exitCode )
184204 }
185205 // No structured signal on stdout — ctx cancellation is next most
186- // informative, since the rest is a guess.
187- if errors .Is (ctx .Err (), context .DeadlineExceeded ) {
188- return "" , context .DeadlineExceeded
189- }
190- if errors .Is (ctx .Err (), context .Canceled ) {
191- return "" , context .Canceled
206+ // informative, since the rest is a guess. Wrap the sentinel in a
207+ // *agent.TextGenerationError (like the streaming path and every other
208+ // provider) so the explain timeout diagnostic still gets captured
209+ // stderr and the stdout byte count when this path is reached via the
210+ // old-CLI streaming fallback.
211+ if ctxErr := ctx .Err (); ctxErr != nil && (errors .Is (ctxErr , context .DeadlineExceeded ) || errors .Is (ctxErr , context .Canceled )) {
212+ sentinel := context .Canceled
213+ if errors .Is (ctxErr , context .DeadlineExceeded ) {
214+ sentinel = context .DeadlineExceeded
215+ }
216+ return "" , & agent.TextGenerationError {
217+ Err : sentinel ,
218+ Stderr : strings .TrimSpace (stderr .String ()),
219+ StdoutBytes : stdout .Len (),
220+ }
192221 }
193222 if isExecNotFound (err ) {
194223 return "" , & ClaudeError {Kind : ClaudeErrorCLIMissing , Cause : err }
0 commit comments