Skip to content
Draft
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
66 changes: 50 additions & 16 deletions packages/agent/src/adapters/claude/claude-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1240,20 +1240,46 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
? withTimeout(q.initializationResult(), SESSION_VALIDATION_TIMEOUT_MS)
: undefined;

const [modelOptions] = await Promise.all([
this.getModelConfigOptions(
settingsManager.getSettings().model || meta?.model || undefined,
),
...(meta?.taskRunId
? [
this.client.extNotification(POSTHOG_NOTIFICATIONS.SDK_SESSION, {
taskRunId: meta.taskRunId,
sessionId,
adapter: "claude",
}),
]
: []),
]);
// When the caller already pinned a model (cloud always does), the awaited
// gateway /v1/models call is only used to populate the UI's available-models
// dropdown — kicking it off in the background lets us return the session
// sooner. With no pinned model we still wait, since we need the gateway
// list to choose a default.
const knownModelId =
settingsManager.getSettings().model || meta?.model || undefined;

const sdkSessionNotification = meta?.taskRunId
? this.client.extNotification(POSTHOG_NOTIFICATIONS.SDK_SESSION, {
taskRunId: meta.taskRunId,
sessionId,
adapter: "claude",
})
: Promise.resolve();

let modelOptions: {
currentModelId: string;
options: SessionConfigSelectOption[];
};
if (knownModelId) {
// Synthesize a minimal options list; the real list arrives via
// deferBackgroundFetches below.
modelOptions = {
currentModelId: knownModelId,
options: [
{
value: knownModelId,
name: knownModelId,
description: "",
},
],
};
void sdkSessionNotification;
} else {
[modelOptions] = await Promise.all([
this.getModelConfigOptions(undefined),
sdkSessionNotification,
]);
}

if (initPromise) {
try {
Expand Down Expand Up @@ -1571,8 +1597,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
// ================================

/**
* Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
* Both populate caches used later — neither is needed to return configOptions.
* Fire-and-forget: fetch slash commands, MCP tool metadata, and prime the
* gateway models cache in parallel. None of these are needed to return
* configOptions — priming the gateway cache here keeps `getContextWindowForModel`
* accurate by the time the first prompt fires.
*/
private deferBackgroundFetches(q: Query): void {
Promise.all([
Expand All @@ -1585,6 +1613,12 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
this.options?.onMcpServersReady?.(serverNames);
}
}),
// Warm the gateway models cache so subsequent context-window lookups
// don't fall back to the 200k default. Result is stored on the
// agent instance via fetchGatewayModels' internal cache.
this.getModelConfigOptions(this.session?.modelId).catch(() => {
// Best-effort: failures here just leave the default in place.
}),
]).catch((err) =>
this.logger.error("Background fetch failed", { error: err }),
);
Expand Down
10 changes: 8 additions & 2 deletions packages/agent/src/server/agent-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import type {
GitCheckpointEvent,
HandoffLocalGitState,
LogLevel,
Task,
TaskRun,
TaskRunArtifact,
} from "../types";
Expand Down Expand Up @@ -1026,7 +1027,7 @@ export class AgentServer {
this.logger.debug("Failed to set task run to in_progress", err),
);

await this.sendInitialTaskMessage(payload, preTaskRun);
await this.sendInitialTaskMessage(payload, preTaskRun, preTask);
}

private extractErrorClassification(error: unknown): {
Expand Down Expand Up @@ -1067,6 +1068,7 @@ export class AgentServer {
private async sendInitialTaskMessage(
payload: JwtPayload,
prefetchedRun?: TaskRun | null,
prefetchedTask?: Task | null,
): Promise<void> {
if (!this.session) return;

Expand Down Expand Up @@ -1105,7 +1107,11 @@ export class AgentServer {
}

try {
const task = await this.posthogAPI.getTask(payload.task_id);
// Reuse the task fetched during session init when available; it was
// fetched milliseconds ago in the same boot path, so re-fetching it
// here is a redundant sandbox->PostHog round trip on the hot path.
const task =
prefetchedTask ?? (await this.posthogAPI.getTask(payload.task_id));

const initialPromptOverride = taskRun
? this.getInitialPromptOverride(taskRun)
Expand Down
Loading