Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions src/vs/platform/agentHost/common/agentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1711,19 +1711,6 @@ export interface IAgent {
/** Available models from this provider. */
readonly models: IObservable<readonly IAgentModelInfo[]>;

/**
* Re-enumerate this provider's model list and publish the result to
* {@link models}. Called both on provider-owned triggers (authentication,
* transport changes) and periodically by the host's model-refresh
* scheduler, so implementations MUST coalesce concurrent calls into a
* single backend request and MUST NOT reject: a failed refresh is logged
* and leaves the last known-good list in place.
*
* Optional so providers without a dynamic model catalog (mocks, test
* agents) need not implement it.
*/
refreshModels?(): Promise<void>;

/** List persisted sessions from this provider. */
listSessions(): Promise<IAgentSessionMetadata[]>;

Expand Down
4 changes: 3 additions & 1 deletion src/vs/platform/agentHost/node/agentHostMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import * as os from 'os';
import * as inspector from 'inspector';
import { AgentHostByokModelsEnabledEnvVar, AgentHostClaudeAgentEnabledEnvVar, AgentHostCodexAgentEnabledEnvVar, AgentHostIpcChannels, IAgentHostInspectInfo, IAgentHostSocketInfo, IAgentService, IConnectionTrackerService, isAgentEnabled } from '../common/agentService.js';
import { AgentHostCodexEnabledConfigKey, platformRootSchema } from '../common/agentHostSchema.js';
import { AgentModelRefreshScheduler, MODEL_REFRESH_INTERVAL_MS } from './agentModelRefreshScheduler.js';
import { AgentService } from './agentService.js';
import { IAgentHostStateManager } from './agentHostStateManager.js';
import { IAgentConfigurationService } from './agentConfigurationService.js';
Expand Down Expand Up @@ -281,6 +280,7 @@ async function startAgentHost(): Promise<void> {
throw err;
}

<<<<<<< HEAD
// Keep every provider's model catalog fresh. Provider-owned refresh
// triggers (authentication, transport flips, client restarts) are all
// edge-based, so this periodic tick is the only thing that notices a model
Expand All @@ -290,6 +290,8 @@ async function startAgentHost(): Promise<void> {
// can ever drain.
disposables.add(instantiationService.createInstance(AgentModelRefreshScheduler, agentService.agents, agentService.onDidStartTurn, MODEL_REFRESH_INTERVAL_MS));

=======
>>>>>>> e5b4addd5c7 (Revert https://github.com/microsoft/vscode/pull/327408 (#328871))
// Surface agent-SDK download progress to clients as generic `progress`
// notifications. The downloader fires process-global frames keyed by package
// id; the agent service fans each out to the `createSession` progress tokens
Expand Down
4 changes: 3 additions & 1 deletion src/vs/platform/agentHost/node/agentHostServerMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ import { CodexProxyService, ICodexProxyService } from './codex/codexProxyService
import { AgentSdkDownloader, IAgentSdkDownloader, type IAgentSdkDownloadProgress } from './agentSdkDownloader.js';
import { IAgentHostOTelService } from '../common/otel/agentHostOTelService.js';
import { AgentHostOTelService } from './otel/agentHostOTelService.js';
import { AgentModelRefreshScheduler, MODEL_REFRESH_INTERVAL_MS } from './agentModelRefreshScheduler.js';
import { AgentService } from './agentService.js';
import { IAgentHostStateManager } from './agentHostStateManager.js';
import { AgentHostClaudeAgentEnabledEnvVar, AgentHostClaudeSdkRootEnvVar, AgentHostCodexAgentEnabledEnvVar, IAgentService, AgentHostCodexAgentSdkRootEnvVar, isAgentEnabled } from '../common/agentService.js';
Expand Down Expand Up @@ -369,6 +368,7 @@ async function main(): Promise<void> {
});
}

<<<<<<< HEAD
// Keep every provider's model catalog fresh. Provider-owned refresh
// triggers (authentication, transport flips, client restarts) are all
// edge-based, so this periodic tick is the only thing that notices a model
Expand All @@ -378,6 +378,8 @@ async function main(): Promise<void> {
// can ever drain.
disposables.add(instantiationService.createInstance(AgentModelRefreshScheduler, agentService.agents, agentService.onDidStartTurn, MODEL_REFRESH_INTERVAL_MS));

=======
>>>>>>> e5b4addd5c7 (Revert https://github.com/microsoft/vscode/pull/327408 (#328871))
// WebSocket server
const wsServer = disposables.add(await WebSocketProtocolServer.create({
port: options.port,
Expand Down
5 changes: 4 additions & 1 deletion src/vs/platform/agentHost/node/agentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Disposable, DisposableMap, DisposableResourceMap, DisposableStore, IDis
import { LRUCache, ResourceMap } from '../../../base/common/map.js';
import { getExtensionForMimeType, getMediaMime } from '../../../base/common/mime.js';
import { Schemas } from '../../../base/common/network.js';
import { IObservable, observableValue } from '../../../base/common/observable.js';
import { observableValue } from '../../../base/common/observable.js';
import { dirname as resourcesDirname, extname as resourcesExtname, extUriBiasedIgnorePathCase, isEqual, isEqualOrParent, joinPath } from '../../../base/common/resources.js';
import { URI } from '../../../base/common/uri.js';
import { generateUuid } from '../../../base/common/uuid.js';
Expand Down Expand Up @@ -548,6 +548,7 @@ export class AgentService extends Disposable implements IAgentService {
this._serverToolHost = new AgentServerToolHost(this._stateManager, buildServerToolGroups(this._createSessionServerToolAccessor()));
}

<<<<<<< HEAD
/**
* The registered providers. Exposed so process-lifetime background jobs
* (notably {@link AgentModelRefreshScheduler}) can observe registrations
Expand All @@ -566,6 +567,8 @@ export class AgentService extends Disposable implements IAgentService {
return this._sideEffects.onDidStartTurn;
}

=======
>>>>>>> e5b4addd5c7 (Revert https://github.com/microsoft/vscode/pull/327408 (#328871))
// ---- provider registration ----------------------------------------------

/**
Expand Down
58 changes: 6 additions & 52 deletions src/vs/platform/agentHost/node/claude/claudeAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,6 @@ export class ClaudeAgent extends Disposable implements IAgent {

private readonly _models = observableValue<readonly IAgentModelInfo[]>(this, []);
readonly models: IObservable<readonly IAgentModelInfo[]> = this._models;
/**
* In-flight {@link refreshModels} call, so overlapping triggers (an auth
* token change, a transport flip, or a periodic tick from the host's
* model-refresh scheduler) collapse into a single enumeration instead of
* racing each other's writes to {@link _models}.
*/
private _modelRefreshInFlight: Promise<void> | undefined;

private _githubToken: string | undefined;
private _proxyHandle: IClaudeProxyHandle | undefined;
Expand Down Expand Up @@ -484,11 +477,7 @@ export class ClaudeAgent extends Disposable implements IAgent {
const next = this._resolveTransportMode();
if (next !== this._transportMode) {
this._transportMode = next;
// Proxy and native enumerate different catalogs. Do not retain
// models from the previous transport if the replacement cannot
// enumerate its own list.
this._models.set([], undefined);
void this._startModelRefresh();
void this._refreshModels();
// Flipping into proxy makes GitHub Copilot auth newly required.
// If no proxy handle was ever established, proactively ask the
// client to authenticate rather than waiting for the next command
Expand All @@ -512,7 +501,7 @@ export class ClaudeAgent extends Disposable implements IAgent {
// kick off the initial enumeration ourselves. (Transport *flips*
// after construction are covered by the `onDidRootConfigChange`
// subscription above.) `queueMicrotask` runs it off the ctor stack.
queueMicrotask(() => { void this._startModelRefresh(); });
queueMicrotask(() => { void this._refreshModels(); });
}
}

Expand Down Expand Up @@ -610,13 +599,7 @@ export class ClaudeAgent extends Disposable implements IAgent {
this._githubToken = token;
this._logService.info('[Claude] Auth token updated');
oldHandle?.dispose();
if (tokenChanged) {
// A different account can have different model entitlements. Do
// not retain the previous token's catalog if enumeration for the
// replacement token fails.
this._models.set([], undefined);
}
void this._startModelRefresh();
void this._refreshModels();
return true;
}

Expand All @@ -629,34 +612,6 @@ export class ClaudeAgent extends Disposable implements IAgent {
return this._transportMode === 'proxy';
}

/**
* {@link IAgent.refreshModels}. Coalesces onto an in-flight refresh and
* never rejects — {@link _refreshModels} already logs and handles failure.
*
* Only safe for callers with no new input to apply (the host's periodic
* scheduler). Triggers that invalidate the in-flight request — a rotated
* token, a transport flip — must call {@link _startModelRefresh} so they
* are not answered by a refresh bound to the superseded input.
*/
refreshModels(): Promise<void> {
return this._modelRefreshInFlight ?? this._startModelRefresh();
}

/**
* Unconditionally begins a refresh, superseding any in-flight one as the
* coalescing target. The superseded request stays harmless: its own
* stale-write guard drops the result if the token or transport moved on.
*/
private _startModelRefresh(): Promise<void> {
const refresh = this._refreshModels().finally(() => {
if (this._modelRefreshInFlight === refresh) {
this._modelRefreshInFlight = undefined;
}
});
this._modelRefreshInFlight = refresh;
return refresh;
}

private async _refreshModels(): Promise<void> {
const proxyAtStart = this._isProxyEnabled();
const tokenAtStart = this._githubToken;
Expand All @@ -678,10 +633,9 @@ export class ClaudeAgent extends Disposable implements IAgent {
this._models.set(filtered, undefined);
} catch (err) {
this._logService.error(err, '[Claude] Failed to refresh models');
// Keep the last known-good catalog. A periodic refresh is advisory;
// a transient service failure must not make every model disappear.
// Input changes that invalidate the catalog clear it at the point
// where that input changes.
if (this._isProxyEnabled() === proxyAtStart && (!proxyAtStart || this._githubToken === tokenAtStart)) {
this._models.set([], undefined);
}
}
}

Expand Down
26 changes: 8 additions & 18 deletions src/vs/platform/agentHost/node/codex/codexAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1080,24 +1080,14 @@ export class CodexAgent extends Disposable implements IAgent {
}
}

/**
* {@link IAgent.refreshModels}. Coalesces onto an in-flight refresh — from
* an account/usage-source change or an earlier tick — rather than issuing a
* second enumeration, and never rejects: {@link _refreshModels} logs and
* applies its own stale-write guards on failure.
*/
refreshModels(): Promise<void> {
return this._modelsRefreshPromise ?? this._queueModelRefresh();
}

private _queueModelRefresh(): Promise<void> {
private _queueModelRefresh(): void {
const refreshPromise = this._refreshModels().finally(() => {
if (this._modelsRefreshPromise === refreshPromise) {
this._modelsRefreshPromise = undefined;
}
});
this._modelsRefreshPromise = refreshPromise;
return refreshPromise;
void this._modelsRefreshPromise;
}

private _ensureAuthenticated(): string | undefined {
Expand Down Expand Up @@ -1338,9 +1328,9 @@ export class CodexAgent extends Disposable implements IAgent {
this._models.set(models, undefined);
} catch (err) {
this._logService.warn(`[Codex] Failed to refresh models: ${err instanceof Error ? err.message : String(err)}`);
// Keep the last known-good catalog. Usage-source changes clear the
// list in `_applyUsageSourceChange`; a transient periodic failure
// must not make every model disappear.
if (this._usageSource === usageSource && this._githubToken === token) {
this._models.set([], undefined);
}
}
}

Expand Down Expand Up @@ -1372,9 +1362,9 @@ export class CodexAgent extends Disposable implements IAgent {
}
} catch (err) {
this._logService.warn(`[Codex] Failed to refresh OpenAI models: ${err instanceof Error ? err.message : String(err)}`);
// Keep the last known-good catalog. Usage-source changes clear the
// list in `_applyUsageSourceChange`; a transient periodic failure
// must not make every model disappear.
if (this._usageSource === 'openai') {
this._models.set([], undefined);
}
}
}

Expand Down
Loading
Loading