Skip to content

Commit 9467938

Browse files
feat: move model related data to meta
1 parent 86d819f commit 9467938

19 files changed

Lines changed: 307 additions & 128 deletions

src/CodexAcpClient.ts

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import type {Disposable} from "vscode-jsonrpc";
1313
import type {
1414
ClientInfo,
1515
ReasoningEffort,
16-
ServerNotification
16+
ServerNotification,
17+
ServiceTier,
1718
} from "./app-server";
1819
import type {JsonValue} from "./app-server/serde_json/JsonValue";
19-
import {ModelId} from "./ModelId";
2020
import {AgentMode} from "./AgentMode";
2121
import path from "node:path";
2222
import {logger} from "./Logger";
@@ -215,10 +215,10 @@ export class CodexAcpClient {
215215
threadId: request.sessionId,
216216
});
217217
const codexModels = await this.fetchAvailableModels();
218-
const currentModelId = this.createModelId(codexModels, response.model, response.reasoningEffort).toString();
218+
const modelSelection = this.createModelSelection(codexModels, response.model, response.reasoningEffort, response.serviceTier);
219219
return {
220220
sessionId: request.sessionId,
221-
currentModelId: currentModelId,
221+
...modelSelection,
222222
models: codexModels,
223223
}
224224
}
@@ -237,10 +237,10 @@ export class CodexAcpClient {
237237
threadId: request.sessionId,
238238
});
239239
const codexModels = await this.fetchAvailableModels();
240-
const currentModelId = this.createModelId(codexModels, response.model, response.reasoningEffort).toString();
240+
const modelSelection = this.createModelSelection(codexModels, response.model, response.reasoningEffort, response.serviceTier);
241241
return {
242242
sessionId: request.sessionId,
243-
currentModelId: currentModelId,
243+
...modelSelection,
244244
models: codexModels,
245245
thread: response.thread,
246246
};
@@ -266,10 +266,10 @@ export class CodexAcpClient {
266266
if (codexModels.length === 0) {
267267
throw new Error("Codex did not return any models");
268268
}
269-
const currentModelId = this.createModelId(codexModels, response.model, response.reasoningEffort).toString();
269+
const modelSelection = this.createModelSelection(codexModels, response.model, response.reasoningEffort, response.serviceTier);
270270
return {
271271
sessionId: response.thread.id,
272-
currentModelId: currentModelId,
272+
...modelSelection,
273273
models: codexModels,
274274
};
275275
}
@@ -346,10 +346,15 @@ export class CodexAcpClient {
346346
}
347347

348348
/**
349-
* Resolves a ModelId using the provided ID and reasoning effort.
350-
* Falls back to model defaults if parameters are missing or unsupported.
349+
* Resolves a model selection using the provided ID and reasoning effort.
350+
* Falls back to model defaults if parameters are missing.
351351
*/
352-
createModelId(availableModels: Model[], modelId: string | null, reasoningEffort: ReasoningEffort | null): ModelId {
352+
createModelSelection(
353+
availableModels: Model[],
354+
modelId: string | null,
355+
reasoningEffort: ReasoningEffort | null,
356+
serviceTier: ServiceTier | null,
357+
): ModelSelection {
353358
const selectedModel =
354359
availableModels.find(m => m.id === modelId) ??
355360
availableModels.find(m => m.isDefault);
@@ -358,7 +363,11 @@ export class CodexAcpClient {
358363
throw new Error(`Model selection failed: No model found for ID "${modelId}" and no default model is defined.`);
359364
}
360365

361-
return ModelId.create(selectedModel.id, reasoningEffort ?? selectedModel.defaultReasoningEffort);
366+
return {
367+
currentModelId: selectedModel.id,
368+
currentReasoningEffort: reasoningEffort ?? selectedModel.defaultReasoningEffort,
369+
currentServiceTier: serviceTier ?? null,
370+
};
362371
}
363372

364373
async subscribeToSessionEvents(
@@ -375,12 +384,11 @@ export class CodexAcpClient {
375384
async sendPrompt(
376385
request: acp.PromptRequest,
377386
agentMode: AgentMode,
378-
modelId: ModelId,
387+
modelSelection: ModelSelection,
379388
disableSummary: boolean,
380389
cwd: string,
381390
): Promise<TurnCompletedNotification> {
382391
const input = buildPromptItems(request.prompt);
383-
const effort = modelId.effort as ReasoningEffort | null; //TODO remove unsafe conversion
384392

385393
await this.refreshSkills(cwd, request._meta);
386394
await this.codexClient.turnStart({
@@ -392,9 +400,9 @@ export class CodexAcpClient {
392400
summary: disableSummary ? "none" : null,
393401
personality: null,
394402
cwd: null,
395-
effort: effort,
396-
model: modelId.model,
397-
serviceTier: modelId.serviceTier,
403+
effort: modelSelection.currentReasoningEffort,
404+
model: modelSelection.currentModelId,
405+
serviceTier: modelSelection.currentServiceTier,
398406
});
399407

400408
// Wait for turn completion
@@ -591,13 +599,17 @@ export type JsonObject = { [key in string]?: JsonValue }
591599
export type SessionMetadata = {
592600
sessionId: string,
593601
currentModelId: string,
602+
currentReasoningEffort: ReasoningEffort,
603+
currentServiceTier: ServiceTier | null,
594604
models: Model[],
595605
}
596606

597607
export type SessionMetadataWithThread = SessionMetadata & {
598608
thread: Thread,
599609
}
600610

611+
export type ModelSelection = Pick<SessionMetadata, "currentModelId" | "currentReasoningEffort" | "currentServiceTier">;
612+
601613
function buildPromptItems(prompt: acp.ContentBlock[]): UserInput[] {
602614
return prompt.map((block): UserInput | null => {
603615
switch (block.type) {

src/CodexAcpServer.ts

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import {CodexEventHandler} from "./CodexEventHandler";
44
import {CodexApprovalHandler} from "./CodexApprovalHandler";
55
import {CodexElicitationHandler} from "./CodexElicitationHandler";
66
import {type CodexAuthRequest, getCodexAuthMethods} from "./CodexAuthMethod";
7-
import {CodexAcpClient, type SessionMetadata, type SessionMetadataWithThread} from "./CodexAcpClient";
7+
import {CodexAcpClient, type ModelSelection, type SessionMetadata, type SessionMetadataWithThread} from "./CodexAcpClient";
88
import type {McpStartupResult} from "./CodexAppServerClient";
99
import {ACPSessionConnection, type UpdateSessionEvent} from "./ACPSessionConnection";
10-
import type {InputModality, ReasoningEffort} from "./app-server";
10+
import type {InputModality, ReasoningEffort, ServiceTier} from "./app-server";
1111
import type {
1212
Account,
1313
CollabAgentToolCallStatus,
@@ -18,7 +18,6 @@ import type {
1818
UserInput
1919
} from "./app-server/v2";
2020
import type {RateLimitsMap} from "./RateLimitsMap";
21-
import {ModelId} from "./ModelId";
2221
import {AgentMode} from "./AgentMode";
2322
import type {TokenCount} from "./TokenCount";
2423
import {toPromptUsage} from "./TokenCount";
@@ -36,6 +35,8 @@ import {
3635
export interface SessionState {
3736
sessionId: string,
3837
currentModelId: string,
38+
currentReasoningEffort: ReasoningEffort,
39+
currentServiceTier: ServiceTier | null,
3940
supportedReasoningEfforts: Array<ReasoningEffortOption>,
4041
supportedInputModalities: Array<InputModality>,
4142
agentMode: AgentMode,
@@ -160,12 +161,14 @@ export class CodexAcpServer implements acp.Agent {
160161
}
161162

162163
const account = await this.getActiveAccount();
163-
const {sessionId, currentModelId, models} = sessionMetadata;
164+
const {sessionId, currentModelId, currentReasoningEffort, currentServiceTier, models} = sessionMetadata;
164165
const sessionMcpServers = this.resolveSessionMcpServers(requestedMcpServers, "sessionId" in request);
165166
const currentModel = this.findCurrentModel(models, currentModelId);
166167
const sessionState: SessionState = {
167168
sessionId: sessionId,
168169
currentModelId: currentModelId,
170+
currentReasoningEffort: currentReasoningEffort,
171+
currentServiceTier: currentServiceTier,
169172
supportedReasoningEfforts: currentModel?.supportedReasoningEfforts ?? [],
170173
supportedInputModalities: currentModel?.inputModalities ?? ["text", "image"],
171174
agentMode: AgentMode.getInitialAgentMode(),
@@ -189,7 +192,7 @@ export class CodexAcpServer implements acp.Agent {
189192
}
190193

191194
this.publishAvailableCommandsAsync(sessionId);
192-
const sessionModelState: SessionModelState = this.createModelState(models, currentModelId);
195+
const sessionModelState: SessionModelState = this.createModelState(models, sessionState);
193196
const sessionModeState: SessionModeState = sessionState.agentMode.toSessionModeState();
194197

195198
return [sessionId, sessionModelState, sessionModeState];
@@ -310,34 +313,32 @@ export class CodexAcpServer implements acp.Agent {
310313
const sessionState = this.sessions.get(params.sessionId);
311314
if (!sessionState) throw new Error(`Session ${params.sessionId} not found`);
312315

313-
const requestedModelId = ModelId.fromString(params.modelId);
314-
const requestedModelName = requestedModelId.model;
315-
const requestedEffort = requestedModelId.effort;
316-
317316
const models = await this.codexAcpClient.fetchAvailableModels();
318-
const model = models.find(m => m.id === requestedModelName);
317+
const model = models.find(m => m.id === params.modelId);
319318
if (!model) throw new Error(`Unknown model ${params.modelId}`);
320-
if (requestedModelId.serviceTier === "fast" && !model.additionalSpeedTiers.includes("fast")) {
321-
throw new Error(`Unsupported service tier fast for model ${requestedModelName}`);
322-
}
323319

324-
const requestedEffortValue = requestedEffort as ReasoningEffort | undefined;
325-
let reasoningEffort: ReasoningEffort;
326-
if (requestedEffortValue) {
320+
const requestedEffort = readStringMeta(params._meta, "reasoningEffort") as ReasoningEffort | null;
321+
let reasoningEffort = model.defaultReasoningEffort;
322+
if (requestedEffort !== null) {
327323
const matchedEffort = model.supportedReasoningEfforts.find(
328-
(option) => option.reasoningEffort === requestedEffortValue
324+
(option) => option.reasoningEffort === requestedEffort
329325
)?.reasoningEffort;
330326

331327
if (!matchedEffort) {
332-
throw new Error(`Unsupported reasoning effort ${requestedEffortValue} for model ${requestedModelName}`);
328+
throw new Error(`Unsupported reasoning effort ${requestedEffort} for model ${model.id}`);
333329
}
334330

335331
reasoningEffort = matchedEffort;
336-
} else {
337-
reasoningEffort = model.defaultReasoningEffort;
338332
}
339333

340-
sessionState.currentModelId = ModelId.fromComponents(model, reasoningEffort, requestedModelId.serviceTier).toString();
334+
const requestedServiceTier = readStringMeta(params._meta, "serviceTier") as ServiceTier | null;
335+
if (requestedServiceTier !== null && !model.additionalSpeedTiers.includes(requestedServiceTier)) {
336+
throw new Error(`Unsupported service tier ${requestedServiceTier} for model ${model.id}`);
337+
}
338+
339+
sessionState.currentModelId = model.id;
340+
sessionState.currentReasoningEffort = reasoningEffort;
341+
sessionState.currentServiceTier = requestedServiceTier;
341342
sessionState.supportedReasoningEfforts = model.supportedReasoningEfforts;
342343
sessionState.supportedInputModalities = model.inputModalities;
343344

@@ -349,35 +350,28 @@ export class CodexAcpServer implements acp.Agent {
349350
}
350351

351352
private findCurrentModel(models: Model[], currentModelId: string): Model | undefined {
352-
const modelId = ModelId.fromString(currentModelId);
353-
return models.find(m => m.id === modelId.model);
353+
return models.find(m => m.id === currentModelId);
354354
}
355355

356-
private createModelState(availableModels: Model[], selectedModelId: string): SessionModelState {
356+
private createModelState(availableModels: Model[], selection: ModelSelection): SessionModelState {
357357
const allowedModels = availableModels
358-
.flatMap((model) =>
359-
model.supportedReasoningEfforts.flatMap((effort) => {
360-
const standardModel = {
361-
modelId: ModelId.fromComponents(model, effort.reasoningEffort).toString(),
362-
name: `${model.displayName} (${effort.reasoningEffort})`,
363-
description: `${model.description} ${effort.description}`,
364-
};
365-
if (!model.additionalSpeedTiers.includes("fast")) {
366-
return [standardModel];
367-
}
368-
return [
369-
standardModel,
370-
{
371-
modelId: ModelId.fromComponents(model, effort.reasoningEffort, "fast").toString(),
372-
name: `${model.displayName} (${effort.reasoningEffort}, fast)`,
373-
description: `${model.description} ${effort.description} Fast service tier.`,
374-
},
375-
];
376-
})
377-
);
358+
.map((model) => ({
359+
modelId: model.id,
360+
name: model.displayName,
361+
description: model.description,
362+
_meta: {
363+
supportedReasoningEfforts: model.supportedReasoningEfforts,
364+
defaultReasoningEffort: model.defaultReasoningEffort,
365+
serviceTiers: model.additionalSpeedTiers,
366+
},
367+
}));
378368
return {
379369
availableModels: allowedModels,
380-
currentModelId: selectedModelId,
370+
currentModelId: selection.currentModelId,
371+
_meta: {
372+
currentReasoningEffort: selection.currentReasoningEffort,
373+
currentServiceTier: selection.currentServiceTier,
374+
},
381375
}
382376
}
383377

@@ -401,12 +395,14 @@ export class CodexAcpServer implements acp.Agent {
401395
);
402396

403397
const account = await this.getActiveAccount();
404-
const {sessionId, currentModelId, models, thread} = sessionMetadata;
398+
const {sessionId, currentModelId, currentReasoningEffort, currentServiceTier, models, thread} = sessionMetadata;
405399
const sessionMcpServers = this.resolveSessionMcpServers(requestedMcpServers, true);
406400
const currentModel = this.findCurrentModel(models, currentModelId);
407401
const sessionState: SessionState = {
408402
sessionId: sessionId,
409403
currentModelId: currentModelId,
404+
currentReasoningEffort: currentReasoningEffort,
405+
currentServiceTier: currentServiceTier,
410406
supportedReasoningEfforts: currentModel?.supportedReasoningEfforts ?? [],
411407
supportedInputModalities: currentModel?.inputModalities ?? ["text", "image"],
412408
agentMode: AgentMode.getInitialAgentMode(),
@@ -430,7 +426,7 @@ export class CodexAcpServer implements acp.Agent {
430426
}
431427

432428
await this.availableCommands.publish(sessionId);
433-
const sessionModelState: SessionModelState = this.createModelState(models, currentModelId);
429+
const sessionModelState: SessionModelState = this.createModelState(models, sessionState);
434430
const sessionModeState: SessionModeState = sessionState.agentMode.toSessionModeState();
435431

436432
return {
@@ -767,7 +763,6 @@ export class CodexAcpServer implements acp.Agent {
767763
};
768764
}
769765

770-
const modelId = ModelId.fromString(sessionState.currentModelId);
771766
const modelLacksReasoning = sessionState.supportedReasoningEfforts.length > 0
772767
&& sessionState.supportedReasoningEfforts.every(e => e.reasoningEffort === "none");
773768

@@ -784,7 +779,7 @@ export class CodexAcpServer implements acp.Agent {
784779
}
785780
const agentMode = sessionState.agentMode;
786781
const turnCompleted = await this.runWithProcessCheck(
787-
() => this.codexAcpClient.sendPrompt(params, agentMode, modelId, disableSummary, sessionState.cwd));
782+
() => this.codexAcpClient.sendPrompt(params, agentMode, sessionState, disableSummary, sessionState.cwd));
788783

789784
// Check if turn was interrupted (cancelled)
790785
if (turnCompleted.turn.status === "interrupted") {
@@ -828,7 +823,7 @@ export class CodexAcpServer implements acp.Agent {
828823
private buildQuotaMeta(sessionState: SessionState): { quota: QuotaMeta } {
829824
const lastTokenUsage = sessionState.lastTokenUsage;
830825

831-
const modelName = ModelId.fromString(sessionState.currentModelId).model;
826+
const modelName = sessionState.currentModelId;
832827

833828
// FIXME: currently all tokens are reported for the current model
834829
const modelUsage = (lastTokenUsage != null)
@@ -901,3 +896,14 @@ export class CodexAcpServer implements acp.Agent {
901896
function getRequestedMcpServerNames(mcpServers: Array<acp.McpServer>): Array<string> {
902897
return Array.from(new Set(mcpServers.map(server => server.name)));
903898
}
899+
900+
function readStringMeta(meta: { [key: string]: unknown } | null | undefined, key: string): string | null {
901+
const value = meta?.[key];
902+
if (value === undefined || value === null) {
903+
return null;
904+
}
905+
if (typeof value !== "string") {
906+
throw RequestError.invalidParams(`Expected _meta.${key} to be a string`);
907+
}
908+
return value;
909+
}

0 commit comments

Comments
 (0)