@@ -18,6 +18,7 @@ const providers = require('./providers/index');
1818const catalog = require ( './providers/catalog' ) ;
1919const { resolveGateway } = require ( './providers/gateway' ) ;
2020const session = require ( './providers/session' ) ;
21+ const { createAuthRetry } = require ( './providers/authRetry' ) ;
2122const { registerAiEdit } = require ( './aiEdit' ) ;
2223const { registerLmProvider } = require ( './lmProvider' ) ;
2324const { registerInlineComplete } = require ( './inlineComplete' ) ;
@@ -653,6 +654,28 @@ async function refreshGatewayToken() {
653654 return refreshCloudToken ( ) ;
654655}
655656
657+ /**
658+ * A lapsed access token, for everything that sends a provider request and is neither the chat nor
659+ * the agent: inline edit, Agent Sketch and inline completion (each is handed this in its `deps`),
660+ * and Compact and the session-memory summary further down. They sent a request once and reported
661+ * the gateway's 401 — for a token the chat would have renewed and carried on with, and one nothing
662+ * renews ahead of time in a window that simply stays focused (checkCloudSession runs on the
663+ * webview's `ready` and on regaining focus). Through this, the token is renewed and the request sent
664+ * once more.
665+ *
666+ * The rules are in providers/authRetry.js. The two that matter here: it never ends a session — the
667+ * refresh endpoint answering 401, above, is still the only thing that does — and a BYOK request is
668+ * never refreshed or retried. The chat and the agent keep their own retry (handleSend; the agent's
669+ * refreshAuth hook): they have a transcript and a sign-in card to keep in step with it.
670+ *
671+ * ONE instance for the window, so that requests which fail together wait on a single renewal. And it
672+ * is told which session this window is on (sessionGeneration), for the reason the refresh keeps that
673+ * count: a request belongs to the session it was sent in. A token stored by a LATER sign-in is not a
674+ * renewal of the old one, and is never put on a request the earlier session sent. (A sign-in made in
675+ * ANOTHER window this count cannot see; for that, authRetry goes by who the tokens say they are for.)
676+ */
677+ const authRetry = createAuthRetry ( { prepProviderRequest, refreshGatewayToken, isAuthError, sessionGeneration : ( ) => sessionGeneration , dbg } ) ;
678+
656679/**
657680 * Resolve everything needed to call the active provider: id, key, model, baseURL, maxTokens.
658681 * Returns { ok:false, reason } when a required key/baseURL is missing (after optional prompting) or
@@ -1146,11 +1169,12 @@ async function summarizeSessionOutcome(messages, existingFacts) {
11461169 if ( ef . length ) { instr += '\n\nExisting project facts (reference by NUMBER under SUPERSEDES only; do NOT repeat them as FACTS):\n' + ef . map ( ( f , i ) => ( i + 1 ) + '. ' + f . text ) . join ( '\n' ) ; }
11471170 instr += '\n\nTranscript:\n\n' + flat ;
11481171 try {
1149- const out = await providers . complete ( {
1150- providerId : req . providerId , apiKey : req . apiKey , baseURL : req . baseURL , label : req . label ,
1172+ // Through authRetry: a session sealed after the cloud token lapsed still gets its summary.
1173+ const out = await authRetry ( req , ( r ) => providers . complete ( {
1174+ providerId : r . providerId , apiKey : r . apiKey , baseURL : r . baseURL , label : r . label ,
11511175 model, maxTokens : 200 , system : OUTCOME_SYSTEM ,
11521176 messages : [ { role : 'user' , content : instr } ]
1153- } ) ;
1177+ } ) ) ;
11541178 return parseOutcome ( out , ef ) ;
11551179 } catch ( e ) { dbg ( 'sessions.memory.summarize.error' , { msg : String ( ( e && e . message ) || e ) } ) ; return { summary : '' , facts : [ ] , supersedes : [ ] } ; }
11561180}
@@ -1877,12 +1901,14 @@ async function compactAgentMemory() {
18771901
18781902 let summary ;
18791903 try {
1880- summary = await providers . complete ( {
1881- providerId : req . providerId , apiKey : req . apiKey , baseURL : req . baseURL , label : req . label ,
1882- model : req . model , maxTokens : 1500 ,
1904+ // Through authRetry: a lapsed cloud token is renewed and the summary asked for once more, rather
1905+ // than Compact failing on a 401 that the next chat message would have recovered from.
1906+ summary = await authRetry ( req , ( r ) => providers . complete ( {
1907+ providerId : r . providerId , apiKey : r . apiKey , baseURL : r . baseURL , label : r . label ,
1908+ model : r . model , maxTokens : 1500 ,
18831909 system : COMPACT_SYSTEM ,
18841910 messages : [ { role : 'user' , content : COMPACT_INSTRUCTIONS + flat } ]
1885- } ) ;
1911+ } ) ) ;
18861912 } catch ( e ) { dbg ( 'compact.error' , { msg : String ( ( e && e . message ) || e ) } ) ; return { ok : false , reason : 'failed' } ; }
18871913 if ( ! summary || ! summary . trim ( ) ) { return { ok : false , reason : 'empty' } ; }
18881914
@@ -3393,7 +3419,7 @@ function activate(context) {
33933419 // Agent Sketch: the visual multi-agent flow canvas. Lazy require — only loads when opened.
33943420 vscode . commands . registerCommand ( 'levelcode.ai.sketch' , ( ) => {
33953421 try {
3396- require ( './sketch' ) . openSketch ( context , { prepProviderRequest, aiConfig, currentProviderId } ) ;
3422+ require ( './sketch' ) . openSketch ( context , { prepProviderRequest, aiConfig, currentProviderId, authRetry } ) ;
33973423 } catch ( e ) {
33983424 vscode . window . showErrorMessage ( 'Agent Sketch failed to load: ' + ( ( e && e . message ) || e ) ) ;
33993425 }
@@ -3432,6 +3458,7 @@ function activate(context) {
34323458 // AI edit-with-diff (select code → instruct → review diff → apply) — provider-agnostic.
34333459 registerAiEdit ( context , {
34343460 aiConfig,
3461+ authRetry, // a lapsed cloud token is renewed and the edit sent once more
34353462 prepProviderRequest,
34363463 streamChat : providers . streamChat ,
34373464 accountSignIn // the "Sign in" on an edit refused because the cloud session ended
@@ -3451,6 +3478,7 @@ function activate(context) {
34513478 // it must never pop a key dialog mid-typing.
34523479 registerInlineComplete ( context , {
34533480 aiConfig,
3481+ authRetry, // …and renewed as silently: a failed renewal is one missing suggestion, never a toast
34543482 prepProviderRequest,
34553483 complete : providers . complete ,
34563484 fastCompletionModel : catalog . fastCompletionModel
0 commit comments