@@ -16,6 +16,7 @@ import {
1616
1717export const DEFAULT_ADVISOR_PROVIDER = "openai" ;
1818export const DEFAULT_ADVISOR_MODEL = "openai/openai/gpt-5.5" ;
19+ export const ADVISOR_OPENAI_COMPATIBLE_BASE_URL = "https://inference-api.nvidia.com/v1" ;
1920export const READ_ONLY_TOOLS = [ "read" , "grep" , "find" , "ls" ] ;
2021
2122const ZERO_COST = { input : 0 , output : 0 , cacheRead : 0 , cacheWrite : 0 } ;
@@ -29,12 +30,14 @@ const ZERO_USAGE = {
2930} ;
3031
3132type AdvisorProviderConfig = Parameters < ModelRegistry [ "registerProvider" ] > [ 1 ] ;
33+ type AdvisorModelConfig = NonNullable < AdvisorProviderConfig [ "models" ] > [ number ] ;
3234
3335export type RunAdvisorResult = {
3436 /** Assistant text from the final turn. For single-turn callers, this is the full response. */
3537 text : string ;
3638 raw : string ;
3739 turnTexts : string [ ] ;
40+ turnErrors : string [ ] ;
3841} ;
3942
4043export type AdvisorSyntheticToolContentType = "diff" | "json" | "text" ;
@@ -84,9 +87,16 @@ export type RunReadOnlyAdvisorOptions = {
8487export function openAiAdvisorProviderConfig ( credentialEnv : string ) : AdvisorProviderConfig {
8588 return {
8689 api : "openai-completions" ,
87- baseUrl : "https://integrate.api.nvidia.com/v1" ,
90+ baseUrl : ADVISOR_OPENAI_COMPATIBLE_BASE_URL ,
8891 models : [
89- advisorModel ( DEFAULT_ADVISOR_MODEL , "GPT-5.5" , 256000 , 32768 , true , [ "text" , "image" ] ) ,
92+ advisorModel ( DEFAULT_ADVISOR_MODEL , "GPT-5.5" , 256000 , 32768 , false , [ "text" , "image" ] , {
93+ supportsDeveloperRole : false ,
94+ supportsReasoningEffort : false ,
95+ supportsStore : false ,
96+ supportsStrictMode : false ,
97+ supportsUsageInStreaming : false ,
98+ maxTokensField : "max_tokens" ,
99+ } ) ,
90100 ] ,
91101 [ "api" + "Key" ] : credentialEnv ,
92102 } as AdvisorProviderConfig ;
@@ -99,8 +109,9 @@ export function advisorModel(
99109 maxTokens : number ,
100110 reasoning : boolean ,
101111 input : ( "text" | "image" ) [ ] ,
102- ) : NonNullable < AdvisorProviderConfig [ "models" ] > [ number ] {
103- return { id, name, reasoning, input, cost : ZERO_COST , contextWindow, maxTokens } ;
112+ compat ?: AdvisorModelConfig [ "compat" ] ,
113+ ) : AdvisorModelConfig {
114+ return { id, name, reasoning, input, cost : ZERO_COST , contextWindow, maxTokens, compat } ;
104115}
105116
106117export async function runReadOnlyAdvisor (
@@ -112,7 +123,9 @@ export async function runReadOnlyAdvisor(
112123 const { authStorage, modelRegistry } = prepareAdvisorConfig ( provider , options . credentialEnv ) ;
113124 const model = modelRegistry . find ( provider , modelId ) ;
114125 if ( ! model || ! modelRegistry . hasConfiguredAuth ( model ) ) {
115- throw new Error ( `Could not configure advisor model ${ modelId } ` ) ;
126+ throw new Error (
127+ `Could not configure advisor model ${ provider } /${ modelId } ; set ${ options . credentialEnv } ` ,
128+ ) ;
116129 }
117130
118131 const settingsManager = SettingsManager . inMemory ( {
@@ -154,20 +167,44 @@ export async function runReadOnlyAdvisor(
154167 const rawHeader = [
155168 modelFallbackMessage ? `[${ options . logPrefix } ] ${ modelFallbackMessage } ` : undefined ,
156169 `[${ options . logPrefix } ] model=${ model . provider } /${ model . id } ` ,
170+ `[${ options . logPrefix } ] base_url=${ model . baseUrl } ` ,
157171 `[${ options . logPrefix } ] tools=${ READ_ONLY_TOOLS . join ( "," ) } ` ,
158172 `[${ options . logPrefix } ] prompt_turns=${ promptTurns . length } ` ,
159173 "--- ASSISTANT TEXT ---" ,
160174 ] . filter ( ( line ) : line is string => Boolean ( line ) ) ;
161175
162176 const raw = new CappedBuffer ( options . maxCaptureBytes , `${ rawHeader . join ( "\n" ) } \n` ) ;
163177 const turnTextBuffers : CappedBuffer [ ] = [ ] ;
178+ const turnErrors : string [ ] = [ ] ;
164179 let currentTurnText : CappedBuffer | undefined ;
165180 let currentTurnName = "" ;
181+ let currentTurnError : string | undefined ;
182+
183+ const captureTurnError = ( source : string , message : string | undefined ) : void => {
184+ const normalized = normalizeProviderError ( message ) ;
185+ if ( ! normalized ) return ;
186+ currentTurnError ||= normalized ;
187+ raw . append ( `\n[${ options . logPrefix } ] ${ source } : ${ normalized } \n` ) ;
188+ } ;
166189
167190 const unsubscribe = session . subscribe ( ( event : AgentSessionEvent ) => {
168- if ( event . type === "message_update" && event . assistantMessageEvent . type === "text_delta" ) {
169- currentTurnText ?. append ( event . assistantMessageEvent . delta ) ;
170- raw . append ( event . assistantMessageEvent . delta ) ;
191+ if ( event . type === "message_update" ) {
192+ if ( event . assistantMessageEvent . type === "text_delta" ) {
193+ currentTurnText ?. append ( event . assistantMessageEvent . delta ) ;
194+ raw . append ( event . assistantMessageEvent . delta ) ;
195+ return ;
196+ }
197+ if ( event . assistantMessageEvent . type === "error" ) {
198+ captureTurnError (
199+ "assistant_stream_error" ,
200+ event . assistantMessageEvent . error . errorMessage || event . assistantMessageEvent . reason ,
201+ ) ;
202+ return ;
203+ }
204+ return ;
205+ }
206+ if ( event . type === "message_end" ) {
207+ captureTurnError ( "assistant_message_error" , assistantMessageError ( event . message ) ) ;
171208 return ;
172209 }
173210 if ( event . type === "tool_execution_start" ) {
@@ -214,6 +251,7 @@ export async function runReadOnlyAdvisor(
214251 for ( const [ index , turn ] of promptTurns . entries ( ) ) {
215252 currentTurnName = turn . name ;
216253 currentTurnText = new CappedBuffer ( options . maxCaptureBytes ) ;
254+ currentTurnError = undefined ;
217255 turnTextBuffers . push ( currentTurnText ) ;
218256 const turnIndex = `${ index + 1 } /${ promptTurns . length } ` ;
219257 injectSyntheticToolResults ( {
@@ -232,6 +270,9 @@ export async function runReadOnlyAdvisor(
232270 raw . append (
233271 `\n[${ options . logPrefix } ] user_turn_end ${ turnIndex } ${ turn . name } textBytes=${ turnTextBytes } \n` ,
234272 ) ;
273+ if ( currentTurnError ) {
274+ turnErrors . push ( `${ turn . name } : ${ currentTurnError } ` ) ;
275+ }
235276 currentTurnText = undefined ;
236277 currentTurnName = "" ;
237278 }
@@ -264,7 +305,28 @@ export async function runReadOnlyAdvisor(
264305 if ( truncationNotes . length > 0 ) raw . appendFooter ( `\n${ truncationNotes . join ( "\n" ) } \n` ) ;
265306
266307 const turnTexts = turnTextBuffers . map ( ( buffer ) => buffer . toString ( ) ) ;
267- return { text : turnTexts . at ( - 1 ) || "" , raw : raw . toStringWithTrailingNewline ( ) , turnTexts } ;
308+ return {
309+ text : turnTexts . at ( - 1 ) || "" ,
310+ raw : raw . toStringWithTrailingNewline ( ) ,
311+ turnTexts,
312+ turnErrors,
313+ } ;
314+ }
315+
316+ function assistantMessageError ( message : unknown ) : string | undefined {
317+ if ( ! message || typeof message !== "object" ) return undefined ;
318+ const record = message as { role ?: unknown ; stopReason ?: unknown ; errorMessage ?: unknown } ;
319+ if ( record . role !== "assistant" ) return undefined ;
320+ if ( record . stopReason !== "error" && record . stopReason !== "aborted" ) return undefined ;
321+ return typeof record . errorMessage === "string" && record . errorMessage . trim ( )
322+ ? record . errorMessage
323+ : String ( record . stopReason ) ;
324+ }
325+
326+ function normalizeProviderError ( message : string | undefined ) : string | undefined {
327+ if ( ! message ) return undefined ;
328+ const normalized = message . trim ( ) . replace ( / \s + / g, " " ) ;
329+ return normalized || undefined ;
268330}
269331
270332function normalizePromptTurns ( promptTurns : AdvisorPromptTurn [ ] ) : AdvisorPromptTurn [ ] {
@@ -447,7 +509,7 @@ function prepareAdvisorConfig(
447509) : { authStorage : AuthStorage ; modelRegistry : ModelRegistry } {
448510 const authStorage = AuthStorage . inMemory ( ) ;
449511 const modelRegistry = ModelRegistry . inMemory ( authStorage ) ;
450- const credential = process . env [ credentialEnv ] || process . env . OPENAI_API_KEY ;
512+ const credential = process . env [ credentialEnv ] ?. trim ( ) ;
451513 if ( credential ) {
452514 authStorage . setRuntimeApiKey ( provider , credential ) ;
453515 modelRegistry . registerProvider ( provider , openAiAdvisorProviderConfig ( credentialEnv ) ) ;
0 commit comments