@canonmsg/codex-plugin 0.22.1 → 0.22.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +4 -0
- package/dist/app-server-adapter.d.ts +20 -0
- package/dist/app-server-adapter.js +126 -5
- package/dist/host.d.ts +6 -21
- package/dist/host.js +139 -52
- package/dist/model-catalog.d.ts +19 -0
- package/dist/model-catalog.js +137 -0
- package/package.json +2 -2
package/dist/adapter.d.ts
CHANGED
|
@@ -8,6 +8,19 @@ export interface CodexSkillMetadata {
|
|
|
8
8
|
shortDescription?: string;
|
|
9
9
|
displayName?: string;
|
|
10
10
|
}
|
|
11
|
+
export interface CodexReasoningEffortMetadata {
|
|
12
|
+
value: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface CodexModelMetadata {
|
|
16
|
+
id: string;
|
|
17
|
+
model: string;
|
|
18
|
+
displayName: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
supportedReasoningEfforts: CodexReasoningEffortMetadata[];
|
|
21
|
+
defaultReasoningEffort?: string;
|
|
22
|
+
isDefault: boolean;
|
|
23
|
+
}
|
|
11
24
|
export declare class CodexAppServerAdapter {
|
|
12
25
|
private readonly cwd;
|
|
13
26
|
private readonly codexBin;
|
|
@@ -24,6 +37,7 @@ export declare class CodexAppServerAdapter {
|
|
|
24
37
|
private threadId;
|
|
25
38
|
private loadedThreadId;
|
|
26
39
|
private resolvedModel;
|
|
40
|
+
private resolvedReasoningEffort;
|
|
27
41
|
private currentTurnId;
|
|
28
42
|
private requestSeq;
|
|
29
43
|
private pending;
|
|
@@ -54,6 +68,8 @@ export declare class CodexAppServerAdapter {
|
|
|
54
68
|
dynamicTools?: readonly JsonRecord[];
|
|
55
69
|
});
|
|
56
70
|
getThreadId(): string | null;
|
|
71
|
+
getResolvedModel(): string | null;
|
|
72
|
+
getResolvedReasoningEffort(): string | null;
|
|
57
73
|
clearThreadId(): void;
|
|
58
74
|
setModel(model: string | null): void;
|
|
59
75
|
/**
|
|
@@ -78,7 +94,11 @@ export declare class CodexAppServerAdapter {
|
|
|
78
94
|
private buildWritableRoots;
|
|
79
95
|
listSkills(options?: {
|
|
80
96
|
forceReload?: boolean;
|
|
97
|
+
requestTimeoutMs?: number;
|
|
81
98
|
}): Promise<CodexSkillMetadata[]>;
|
|
99
|
+
listModels(options?: {
|
|
100
|
+
requestTimeoutMs?: number;
|
|
101
|
+
}): Promise<CodexModelMetadata[]>;
|
|
82
102
|
private buildTurnInput;
|
|
83
103
|
private ensureStarted;
|
|
84
104
|
private handleLine;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createInterface } from 'node:readline';
|
|
3
|
+
const DEFAULT_DISCOVERY_REQUEST_TIMEOUT_MS = 5_000;
|
|
3
4
|
export class CodexAppServerAdapter {
|
|
4
5
|
cwd;
|
|
5
6
|
codexBin;
|
|
@@ -16,6 +17,7 @@ export class CodexAppServerAdapter {
|
|
|
16
17
|
threadId;
|
|
17
18
|
loadedThreadId = null;
|
|
18
19
|
resolvedModel = null;
|
|
20
|
+
resolvedReasoningEffort;
|
|
19
21
|
currentTurnId = null;
|
|
20
22
|
requestSeq = 1;
|
|
21
23
|
pending = new Map();
|
|
@@ -37,6 +39,7 @@ export class CodexAppServerAdapter {
|
|
|
37
39
|
this.codexBin = opts.codexBin ?? 'codex';
|
|
38
40
|
this.model = opts.model ?? null;
|
|
39
41
|
this.reasoningEffort = opts.reasoningEffort ?? null;
|
|
42
|
+
this.resolvedReasoningEffort = this.reasoningEffort;
|
|
40
43
|
this.sandbox = opts.sandbox ?? null;
|
|
41
44
|
this.legacyApprovalPolicy = opts.approvalPolicy ?? null;
|
|
42
45
|
this.addDirs = opts.addDirs ?? [];
|
|
@@ -48,13 +51,21 @@ export class CodexAppServerAdapter {
|
|
|
48
51
|
getThreadId() {
|
|
49
52
|
return this.threadId;
|
|
50
53
|
}
|
|
54
|
+
getResolvedModel() {
|
|
55
|
+
return this.resolvedModel ?? this.model;
|
|
56
|
+
}
|
|
57
|
+
getResolvedReasoningEffort() {
|
|
58
|
+
return this.resolvedReasoningEffort ?? this.reasoningEffort;
|
|
59
|
+
}
|
|
51
60
|
clearThreadId() {
|
|
52
61
|
this.threadId = null;
|
|
53
62
|
this.loadedThreadId = null;
|
|
54
63
|
this.resolvedModel = null;
|
|
64
|
+
this.resolvedReasoningEffort = this.reasoningEffort;
|
|
55
65
|
}
|
|
56
66
|
setModel(model) {
|
|
57
67
|
this.model = model;
|
|
68
|
+
this.resolvedModel = model;
|
|
58
69
|
}
|
|
59
70
|
/**
|
|
60
71
|
* Sets GPT reasoning effort, applied via model_reasoning_effort. The config
|
|
@@ -66,9 +77,10 @@ export class CodexAppServerAdapter {
|
|
|
66
77
|
*/
|
|
67
78
|
setReasoningEffort(effort) {
|
|
68
79
|
const next = effort && effort.trim() ? effort.trim() : null;
|
|
69
|
-
if (next === this.reasoningEffort)
|
|
80
|
+
if (next === this.reasoningEffort && next === this.resolvedReasoningEffort)
|
|
70
81
|
return;
|
|
71
82
|
this.reasoningEffort = next;
|
|
83
|
+
this.resolvedReasoningEffort = next;
|
|
72
84
|
if (this.threadId && this.loadedThreadId === this.threadId) {
|
|
73
85
|
this.loadedThreadId = null;
|
|
74
86
|
}
|
|
@@ -200,7 +212,7 @@ export class CodexAppServerAdapter {
|
|
|
200
212
|
continue;
|
|
201
213
|
config[key] = parseConfigValue(raw.slice(separator + 1));
|
|
202
214
|
}
|
|
203
|
-
if (this.reasoningEffort
|
|
215
|
+
if (this.reasoningEffort) {
|
|
204
216
|
config.model_reasoning_effort = this.reasoningEffort;
|
|
205
217
|
}
|
|
206
218
|
return Object.keys(config).length > 0 ? { config } : {};
|
|
@@ -249,11 +261,38 @@ export class CodexAppServerAdapter {
|
|
|
249
261
|
const result = await this.sendRequest('skills/list', {
|
|
250
262
|
cwds: [this.cwd],
|
|
251
263
|
...(options.forceReload ? { forceReload: true } : {}),
|
|
252
|
-
});
|
|
264
|
+
}, options.requestTimeoutMs ?? DEFAULT_DISCOVERY_REQUEST_TIMEOUT_MS);
|
|
253
265
|
const parsed = parseSkillsListResponse(result);
|
|
254
266
|
this.skillsCache = parsed;
|
|
255
267
|
return parsed;
|
|
256
268
|
}
|
|
269
|
+
async listModels(options = {}) {
|
|
270
|
+
await this.ensureStarted();
|
|
271
|
+
const models = [];
|
|
272
|
+
const seen = new Set();
|
|
273
|
+
const seenCursors = new Set();
|
|
274
|
+
let cursor = null;
|
|
275
|
+
do {
|
|
276
|
+
const result = await this.sendRequest('model/list', {
|
|
277
|
+
limit: 100,
|
|
278
|
+
includeHidden: false,
|
|
279
|
+
...(cursor ? { cursor } : {}),
|
|
280
|
+
}, options.requestTimeoutMs ?? DEFAULT_DISCOVERY_REQUEST_TIMEOUT_MS);
|
|
281
|
+
for (const model of parseModelListResponse(result)) {
|
|
282
|
+
if (seen.has(model.model))
|
|
283
|
+
continue;
|
|
284
|
+
seen.add(model.model);
|
|
285
|
+
models.push(model);
|
|
286
|
+
}
|
|
287
|
+
const nextCursor = readString(result, 'nextCursor') ?? null;
|
|
288
|
+
if (nextCursor && seenCursors.has(nextCursor))
|
|
289
|
+
break;
|
|
290
|
+
if (nextCursor)
|
|
291
|
+
seenCursors.add(nextCursor);
|
|
292
|
+
cursor = nextCursor;
|
|
293
|
+
} while (cursor);
|
|
294
|
+
return models;
|
|
295
|
+
}
|
|
257
296
|
async buildTurnInput(prompt, imagePaths) {
|
|
258
297
|
const skillPrompt = parseSkillSlashPrompt(prompt);
|
|
259
298
|
const input = [];
|
|
@@ -373,6 +412,25 @@ export class CodexAppServerAdapter {
|
|
|
373
412
|
}
|
|
374
413
|
if (!this.isCurrentThreadNotification(params))
|
|
375
414
|
return;
|
|
415
|
+
if (method === 'thread/settings/updated') {
|
|
416
|
+
const settings = isRecord(params.threadSettings) ? params.threadSettings : {};
|
|
417
|
+
const model = readString(settings, 'model') ?? null;
|
|
418
|
+
const effort = readString(settings, 'effort') ?? null;
|
|
419
|
+
this.resolvedModel = model ?? this.resolvedModel;
|
|
420
|
+
this.resolvedReasoningEffort = effort;
|
|
421
|
+
this.currentOnEvent?.({ type: 'settings.updated', model, effort });
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (method === 'model/rerouted') {
|
|
425
|
+
const model = readString(params, 'toModel') ?? null;
|
|
426
|
+
this.resolvedModel = model ?? this.resolvedModel;
|
|
427
|
+
this.currentOnEvent?.({
|
|
428
|
+
type: 'settings.updated',
|
|
429
|
+
model,
|
|
430
|
+
effort: this.getResolvedReasoningEffort(),
|
|
431
|
+
});
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
376
434
|
if (method === 'turn/started') {
|
|
377
435
|
this.currentTurnId = readString(params.turn, 'id') ?? this.currentTurnId;
|
|
378
436
|
this.currentOnEvent?.({ type: 'turn.started' });
|
|
@@ -518,15 +576,39 @@ export class CodexAppServerAdapter {
|
|
|
518
576
|
this.messageTextByItem.clear();
|
|
519
577
|
this.planText = '';
|
|
520
578
|
}
|
|
521
|
-
sendRequest(method, params) {
|
|
579
|
+
sendRequest(method, params, timeoutMs) {
|
|
522
580
|
const id = this.requestSeq++;
|
|
523
581
|
return new Promise((resolve, reject) => {
|
|
524
|
-
|
|
582
|
+
let timer = null;
|
|
583
|
+
const clearTimer = () => {
|
|
584
|
+
if (!timer)
|
|
585
|
+
return;
|
|
586
|
+
clearTimeout(timer);
|
|
587
|
+
timer = null;
|
|
588
|
+
};
|
|
589
|
+
this.pending.set(id, {
|
|
590
|
+
resolve: (value) => {
|
|
591
|
+
clearTimer();
|
|
592
|
+
resolve(value);
|
|
593
|
+
},
|
|
594
|
+
reject: (error) => {
|
|
595
|
+
clearTimer();
|
|
596
|
+
reject(error);
|
|
597
|
+
},
|
|
598
|
+
});
|
|
599
|
+
if (timeoutMs !== undefined && timeoutMs > 0) {
|
|
600
|
+
timer = setTimeout(() => {
|
|
601
|
+
if (!this.pending.delete(id))
|
|
602
|
+
return;
|
|
603
|
+
reject(new Error(`Codex app-server ${method} timed out after ${timeoutMs}ms`));
|
|
604
|
+
}, timeoutMs);
|
|
605
|
+
}
|
|
525
606
|
try {
|
|
526
607
|
this.write({ id, method, params });
|
|
527
608
|
}
|
|
528
609
|
catch (error) {
|
|
529
610
|
this.pending.delete(id);
|
|
611
|
+
clearTimer();
|
|
530
612
|
reject(error);
|
|
531
613
|
}
|
|
532
614
|
});
|
|
@@ -619,6 +701,45 @@ function parseSkillsListResponse(result) {
|
|
|
619
701
|
}
|
|
620
702
|
return skills;
|
|
621
703
|
}
|
|
704
|
+
function parseModelListResponse(result) {
|
|
705
|
+
const entries = Array.isArray(result.data) ? result.data : [];
|
|
706
|
+
const models = [];
|
|
707
|
+
for (const entry of entries) {
|
|
708
|
+
if (!isRecord(entry) || entry.hidden === true)
|
|
709
|
+
continue;
|
|
710
|
+
const id = readString(entry, 'id');
|
|
711
|
+
const model = readString(entry, 'model') ?? id;
|
|
712
|
+
if (!id || !model)
|
|
713
|
+
continue;
|
|
714
|
+
const displayName = readString(entry, 'displayName') ?? model;
|
|
715
|
+
const description = readString(entry, 'description');
|
|
716
|
+
const efforts = Array.isArray(entry.supportedReasoningEfforts)
|
|
717
|
+
? entry.supportedReasoningEfforts.flatMap((rawEffort) => {
|
|
718
|
+
if (!isRecord(rawEffort))
|
|
719
|
+
return [];
|
|
720
|
+
const value = readString(rawEffort, 'reasoningEffort');
|
|
721
|
+
if (!value)
|
|
722
|
+
return [];
|
|
723
|
+
const effortDescription = readString(rawEffort, 'description');
|
|
724
|
+
return [{
|
|
725
|
+
value,
|
|
726
|
+
...(effortDescription ? { description: effortDescription } : {}),
|
|
727
|
+
}];
|
|
728
|
+
})
|
|
729
|
+
: [];
|
|
730
|
+
const defaultReasoningEffort = readString(entry, 'defaultReasoningEffort');
|
|
731
|
+
models.push({
|
|
732
|
+
id,
|
|
733
|
+
model,
|
|
734
|
+
displayName,
|
|
735
|
+
...(description ? { description } : {}),
|
|
736
|
+
supportedReasoningEfforts: efforts,
|
|
737
|
+
...(defaultReasoningEffort ? { defaultReasoningEffort } : {}),
|
|
738
|
+
isDefault: entry.isDefault === true,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
return models;
|
|
742
|
+
}
|
|
622
743
|
function stringifyPreview(value) {
|
|
623
744
|
if (typeof value === 'string')
|
|
624
745
|
return value;
|
package/dist/host.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type CanonRuntimeCommandDescriptor, type CanonRuntimeDescriptor, type CanonRuntimePresentationPolicy, type ExecutionEnvironmentMode, type WorkspaceOption, type CanonWorkspaceRootMetadata } from '@canonmsg/core';
|
|
3
3
|
import { type CodexSkillMetadata } from './app-server-adapter.js';
|
|
4
|
+
import { type CodexControlOption } from './model-catalog.js';
|
|
4
5
|
interface HostSessionState {
|
|
5
6
|
lastError?: string;
|
|
6
7
|
model?: string;
|
|
@@ -32,30 +33,14 @@ export declare function buildCodexLiveSessionConfig(input: {
|
|
|
32
33
|
permissionMode?: string | undefined;
|
|
33
34
|
model?: string | undefined;
|
|
34
35
|
};
|
|
35
|
-
/**
|
|
36
|
-
export declare const CODEX_EFFORT_OPTIONS: readonly [
|
|
37
|
-
readonly value: "minimal";
|
|
38
|
-
readonly label: "Minimal";
|
|
39
|
-
}, {
|
|
40
|
-
readonly value: "low";
|
|
41
|
-
readonly label: "Low";
|
|
42
|
-
}, {
|
|
43
|
-
readonly value: "medium";
|
|
44
|
-
readonly label: "Medium";
|
|
45
|
-
}, {
|
|
46
|
-
readonly value: "high";
|
|
47
|
-
readonly label: "High";
|
|
48
|
-
}, {
|
|
49
|
-
readonly value: "xhigh";
|
|
50
|
-
readonly label: "Extra high";
|
|
51
|
-
}];
|
|
36
|
+
/** Conservative fallback used only when native app-server discovery is unavailable. */
|
|
37
|
+
export declare const CODEX_EFFORT_OPTIONS: readonly CodexControlOption[];
|
|
52
38
|
export declare const CODEX_SESSION_CONFIG_FIELDS: readonly ["permissionMode", "effort"];
|
|
53
39
|
export declare function buildCodexSkillCommands(skills: ReadonlyArray<CodexSkillMetadata>): CanonRuntimeCommandDescriptor[];
|
|
54
40
|
export declare function buildCodexRuntimeDescriptor(input: {
|
|
55
|
-
models:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}>;
|
|
41
|
+
models: CodexControlOption[];
|
|
42
|
+
effortOptions?: CodexControlOption[];
|
|
43
|
+
defaultEffort?: string | null;
|
|
59
44
|
workspaces: WorkspaceOption[];
|
|
60
45
|
workspaceRoots?: CanonWorkspaceRootMetadata[];
|
|
61
46
|
executionModes: ExecutionEnvironmentMode[];
|
package/dist/host.js
CHANGED
|
@@ -9,7 +9,7 @@ import { captureTurnArtifactSnapshot, collectTurnArtifacts, } from '@canonmsg/co
|
|
|
9
9
|
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, resolveQuestionAllowOther, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, buildCanonInboundFrameV1, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createRuntimeStatePublisher, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfileLock, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, markLocalRuntimeStopped, normalizeTurnMetadata, parseRuntimeCardV1, RuntimeRequestManager, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, CanonApiError, loadRuntimeSessionState, sendMessageWithRetry, sendMessageWithRetryChunked, saveRuntimeSessionState, buildBoundedTurnTrail, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveHostWorkspaceCwd, shouldTriggerAgentTurn, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
10
10
|
import { decideAutoReply, } from './inbound-policy.js';
|
|
11
11
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
12
|
-
import { CodexAppServerAdapter } from './app-server-adapter.js';
|
|
12
|
+
import { CodexAppServerAdapter, } from './app-server-adapter.js';
|
|
13
13
|
import { CODEX_APP_DYNAMIC_TOOLS, deniedCodexAppToolResult, handleCodexAppToolCall, isCodexAppToolCall, } from './codex-app-tools.js';
|
|
14
14
|
import { mapCanonApprovalResultToCodexDecision, mapCodexAppServerApprovalRequest, } from './app-server-approval.js';
|
|
15
15
|
import { clearStoredThreadId, buildCodexThreadPolicyFingerprint, loadStoredThreadId, saveStoredThreadId, } from './session-store.js';
|
|
@@ -21,6 +21,7 @@ import { createCodexControlPoller } from './control-channel.js';
|
|
|
21
21
|
import { runCli } from './cli-entry.js';
|
|
22
22
|
import { collectMissedInboundMessages, STARTUP_RECOVERY_MAX_MESSAGES, STARTUP_RECOVERY_PAGE_SIZE, } from './startup-recovery.js';
|
|
23
23
|
import { applyTextSegmentBlock, beginCommandBlock, claimCommandBlock, createCommandBlockTracker, } from './turn-activity.js';
|
|
24
|
+
import { FALLBACK_CODEX_EFFORT_OPTIONS, buildCodexEffortOptions, buildCodexModelOptions, readCodexConfiguredEffort, resolveCodexDefaultModel, resolveCodexEffortForModel, } from './model-catalog.js';
|
|
24
25
|
const HELP = `canon-codex — run a local Codex agent host for Canon
|
|
25
26
|
|
|
26
27
|
USAGE
|
|
@@ -76,6 +77,7 @@ const MAX_SESSIONS = 12;
|
|
|
76
77
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
77
78
|
const HEARTBEAT_MS = 30_000;
|
|
78
79
|
const IDLE_CHECK_MS = 60_000;
|
|
80
|
+
const PLAN_REVIEW_TIMEOUT_MS = 10 * 60_000;
|
|
79
81
|
const CODEX_RUNTIME_CAPABILITIES = {
|
|
80
82
|
...DEFAULT_RUNTIME_CAPABILITIES,
|
|
81
83
|
supportsInterrupt: true,
|
|
@@ -87,15 +89,8 @@ let workingDir = process.cwd();
|
|
|
87
89
|
let workspaceOptions = [];
|
|
88
90
|
let workspaceRoots = [];
|
|
89
91
|
let workspaceRootMetadata = [];
|
|
90
|
-
/**
|
|
91
|
-
export const CODEX_EFFORT_OPTIONS =
|
|
92
|
-
{ value: 'minimal', label: 'Minimal' },
|
|
93
|
-
{ value: 'low', label: 'Low' },
|
|
94
|
-
{ value: 'medium', label: 'Medium' },
|
|
95
|
-
{ value: 'high', label: 'High' },
|
|
96
|
-
{ value: 'xhigh', label: 'Extra high' },
|
|
97
|
-
];
|
|
98
|
-
const CODEX_EFFORT_VALUES = new Set(CODEX_EFFORT_OPTIONS.map((option) => option.value));
|
|
92
|
+
/** Conservative fallback used only when native app-server discovery is unavailable. */
|
|
93
|
+
export const CODEX_EFFORT_OPTIONS = FALLBACK_CODEX_EFFORT_OPTIONS;
|
|
99
94
|
export const CODEX_SESSION_CONFIG_FIELDS = ['permissionMode', 'effort'];
|
|
100
95
|
const MAX_CODEX_SKILL_COMMAND_CHOICES = 50;
|
|
101
96
|
export function buildCodexSkillCommands(skills) {
|
|
@@ -175,8 +170,8 @@ export function buildCodexRuntimeDescriptor(input) {
|
|
|
175
170
|
defaultPermissionMode: input.defaultPermissionMode,
|
|
176
171
|
permissionModeLabel: 'Execution policy',
|
|
177
172
|
modelLiveBehavior: 'next_turn',
|
|
178
|
-
effortOptions: [...CODEX_EFFORT_OPTIONS],
|
|
179
|
-
defaultEffort: 'medium',
|
|
173
|
+
effortOptions: input.effortOptions ?? [...CODEX_EFFORT_OPTIONS],
|
|
174
|
+
defaultEffort: input.defaultEffort ?? 'medium',
|
|
180
175
|
effortLiveBehavior: 'next_turn',
|
|
181
176
|
presentation: input.presentation,
|
|
182
177
|
streamingTextMode: 'snapshot',
|
|
@@ -241,20 +236,6 @@ export function buildCodexTurnResponseRouting(input) {
|
|
|
241
236
|
export function getCodexRequestingUserId(message) {
|
|
242
237
|
return message.senderType === 'human' ? message.senderId : null;
|
|
243
238
|
}
|
|
244
|
-
function modelOptionLabel(model) {
|
|
245
|
-
if (/^gpt-5\.5(?:$|[-_:])/i.test(model))
|
|
246
|
-
return 'GPT-5.5';
|
|
247
|
-
if (/^gpt-5\.4-mini(?:$|[-_:])/i.test(model))
|
|
248
|
-
return 'GPT-5.4 Mini';
|
|
249
|
-
if (/^gpt-5\.4(?:$|[-_:])/i.test(model))
|
|
250
|
-
return 'GPT-5.4';
|
|
251
|
-
return model;
|
|
252
|
-
}
|
|
253
|
-
function buildCodexModelOptions(model) {
|
|
254
|
-
return typeof model === 'string' && model.trim()
|
|
255
|
-
? [{ value: model.trim(), label: modelOptionLabel(model.trim()) }]
|
|
256
|
-
: [];
|
|
257
|
-
}
|
|
258
239
|
async function publishAgentRuntime(agentId, runtime) {
|
|
259
240
|
await publishHostAgentRuntime(agentId, 'codex', runtime);
|
|
260
241
|
}
|
|
@@ -1018,9 +999,13 @@ export async function main() {
|
|
|
1018
999
|
throw new ExecutionEnvironmentError(modelGuard, modelGuard);
|
|
1019
1000
|
}
|
|
1020
1001
|
const storedThreadId = loadStoredThreadId(runtimeId, agentId, conversationId, environment.baseCwd, environment.mode, policy.fingerprint);
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
:
|
|
1002
|
+
const effectiveModel = policy.model ?? codexDefaultModel;
|
|
1003
|
+
const initialEffortResolution = resolveCodexEffortForModel({
|
|
1004
|
+
models: codexModels,
|
|
1005
|
+
model: effectiveModel,
|
|
1006
|
+
requestedEffort: config?.effort ?? configuredCodexEffort,
|
|
1007
|
+
});
|
|
1008
|
+
const initialEffort = initialEffortResolution.value;
|
|
1024
1009
|
const adapter = useAppServer
|
|
1025
1010
|
? new CodexAppServerAdapter({
|
|
1026
1011
|
cwd: sessionCwd,
|
|
@@ -1058,7 +1043,7 @@ export async function main() {
|
|
|
1058
1043
|
queue: [],
|
|
1059
1044
|
running: false,
|
|
1060
1045
|
state: buildCodexInitialSessionState({
|
|
1061
|
-
model:
|
|
1046
|
+
model: effectiveModel ?? undefined,
|
|
1062
1047
|
permissionMode: policy.permissionMode,
|
|
1063
1048
|
effort: initialEffort,
|
|
1064
1049
|
}),
|
|
@@ -1123,6 +1108,19 @@ export async function main() {
|
|
|
1123
1108
|
writeTurn(session);
|
|
1124
1109
|
void runNextTurn(session);
|
|
1125
1110
|
}
|
|
1111
|
+
function enqueueCodexPlanReviewResult(session, result, responseUserId) {
|
|
1112
|
+
if (result.status === 'cancelled' || result.status === 'timeout') {
|
|
1113
|
+
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Plan review ${result.status}`);
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
const feedback = result.feedback?.trim();
|
|
1117
|
+
const prompt = result.status === 'approve'
|
|
1118
|
+
? 'The plan was approved. Implement the approved plan now.'
|
|
1119
|
+
: result.status === 'reject'
|
|
1120
|
+
? `The plan was declined — keep planning and wait for guidance before implementing.${feedback ? `\n\nNotes:\n${feedback}` : ''}`
|
|
1121
|
+
: `Please revise the plan.${feedback ? `\n\nRevision feedback:\n${feedback}` : ''}`;
|
|
1122
|
+
enqueuePrompt(session, prompt, 'queue', false, result.receiptId ?? null, false, [], [], result.status !== 'approve', 'disabled', false, responseUserId);
|
|
1123
|
+
}
|
|
1126
1124
|
function resolveArtifactRoutingMode(participantContext) {
|
|
1127
1125
|
return participantContext.conversationType === 'direct' && participantContext.isOwner
|
|
1128
1126
|
? 'workspace-generated'
|
|
@@ -1331,6 +1329,14 @@ export async function main() {
|
|
|
1331
1329
|
if (isRecord(input.message.metadata)
|
|
1332
1330
|
&& input.message.metadata.type === 'plan_approval_reply'
|
|
1333
1331
|
&& typeof input.message.metadata.decision === 'string') {
|
|
1332
|
+
const planId = readString(input.message.metadata, 'planId');
|
|
1333
|
+
if (planId && runtimeRequests.handleMessage(input.conversationId, {
|
|
1334
|
+
senderId: input.message.senderId,
|
|
1335
|
+
metadata: input.message.metadata,
|
|
1336
|
+
})) {
|
|
1337
|
+
persistInboundRecoveryCursorWhenIdle(input.conversationId, input.message.id);
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1334
1340
|
const session = await getOrCreateSession(input.conversationId);
|
|
1335
1341
|
const feedback = readString(input.message.metadata, 'feedback');
|
|
1336
1342
|
const decision = input.message.metadata.decision;
|
|
@@ -1555,6 +1561,13 @@ export async function main() {
|
|
|
1555
1561
|
void refreshCodexSkillInventory(true).then(() => publishRuntimeHeartbeat());
|
|
1556
1562
|
return;
|
|
1557
1563
|
}
|
|
1564
|
+
if (event.type === 'settings.updated') {
|
|
1565
|
+
if (event.model)
|
|
1566
|
+
session.state.model = event.model;
|
|
1567
|
+
session.state.effort = event.effort ?? undefined;
|
|
1568
|
+
writeState(session);
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1558
1571
|
if (event.type === 'message') {
|
|
1559
1572
|
session.turnState = 'streaming';
|
|
1560
1573
|
markTurnProgress(session);
|
|
@@ -1657,6 +1670,17 @@ export async function main() {
|
|
|
1657
1670
|
clearStoredThread();
|
|
1658
1671
|
result = await runTurnOnce();
|
|
1659
1672
|
}
|
|
1673
|
+
if (session.adapter instanceof CodexAppServerAdapter) {
|
|
1674
|
+
const resolvedModel = session.adapter.getResolvedModel();
|
|
1675
|
+
const resolvedEffort = session.adapter.getResolvedReasoningEffort();
|
|
1676
|
+
if ((resolvedModel && resolvedModel !== session.state.model)
|
|
1677
|
+
|| resolvedEffort !== (session.state.effort ?? null)) {
|
|
1678
|
+
if (resolvedModel)
|
|
1679
|
+
session.state.model = resolvedModel;
|
|
1680
|
+
session.state.effort = resolvedEffort ?? undefined;
|
|
1681
|
+
writeState(session);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1660
1684
|
if (result.threadId && !session.resetRequested) {
|
|
1661
1685
|
saveStoredThreadId(runtimeId, agentId, session.conversationId, session.environment.baseCwd, result.threadId, session.environment.mode, session.policyFingerprint);
|
|
1662
1686
|
}
|
|
@@ -1666,20 +1690,43 @@ export async function main() {
|
|
|
1666
1690
|
requestingUserId: nextTurn.requestingUserId,
|
|
1667
1691
|
ownerId,
|
|
1668
1692
|
});
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1693
|
+
const planId = session.currentTurnId ?? randomUUID();
|
|
1694
|
+
let planCreated = false;
|
|
1695
|
+
let resolvePlanCreated;
|
|
1696
|
+
let rejectPlanCreated;
|
|
1697
|
+
const planCreatedPromise = new Promise((resolve, reject) => {
|
|
1698
|
+
resolvePlanCreated = resolve;
|
|
1699
|
+
rejectPlanCreated = reject;
|
|
1700
|
+
});
|
|
1701
|
+
const planReview = runtimeRequests.request('plan', session.conversationId, {
|
|
1676
1702
|
title: 'Codex Plan',
|
|
1677
1703
|
body: result.finalMessage,
|
|
1678
1704
|
...(responseRouting.responseUserId
|
|
1679
1705
|
? { responseUserId: responseRouting.responseUserId }
|
|
1680
1706
|
: {}),
|
|
1681
1707
|
...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
|
|
1708
|
+
}, {
|
|
1709
|
+
requestId: planId,
|
|
1710
|
+
expiresAt: Date.now() + PLAN_REVIEW_TIMEOUT_MS,
|
|
1711
|
+
responderPolicy: 'infer',
|
|
1712
|
+
onCreated: () => {
|
|
1713
|
+
planCreated = true;
|
|
1714
|
+
session.turnState = 'waiting_input';
|
|
1715
|
+
writeTurn(session);
|
|
1716
|
+
resolvePlanCreated();
|
|
1717
|
+
},
|
|
1718
|
+
});
|
|
1719
|
+
void planReview.then((planResult) => {
|
|
1720
|
+
resolvePlanCreated();
|
|
1721
|
+
enqueueCodexPlanReviewResult(session, planResult, responseRouting.responseUserId ?? null);
|
|
1722
|
+
}).catch((error) => {
|
|
1723
|
+
if (!planCreated) {
|
|
1724
|
+
rejectPlanCreated(error);
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Plan review failed:`, error instanceof Error ? error.message : error);
|
|
1682
1728
|
});
|
|
1729
|
+
await planCreatedPromise;
|
|
1683
1730
|
await handoffFinalMessage(session.conversationId);
|
|
1684
1731
|
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Sent plan approval card`);
|
|
1685
1732
|
}
|
|
@@ -1822,7 +1869,16 @@ export async function main() {
|
|
|
1822
1869
|
...EXECUTION_ENVIRONMENT_MODES,
|
|
1823
1870
|
];
|
|
1824
1871
|
const codexPermissionEnvelope = deriveCodexPermissionEnvelope(args);
|
|
1825
|
-
const
|
|
1872
|
+
const configuredCodexEffort = readCodexConfiguredEffort(args.config ?? []);
|
|
1873
|
+
let codexModels = [];
|
|
1874
|
+
let codexModelOptions = buildCodexModelOptions(codexModels, args.model);
|
|
1875
|
+
let codexEffortOptions = buildCodexEffortOptions(codexModels);
|
|
1876
|
+
let codexDefaultModel = resolveCodexDefaultModel(codexModels, args.model);
|
|
1877
|
+
let codexDefaultEffort = resolveCodexEffortForModel({
|
|
1878
|
+
models: codexModels,
|
|
1879
|
+
model: codexDefaultModel,
|
|
1880
|
+
requestedEffort: configuredCodexEffort,
|
|
1881
|
+
}).value;
|
|
1826
1882
|
const runtimePresentation = buildRuntimePresentationPolicy({
|
|
1827
1883
|
base: DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION,
|
|
1828
1884
|
preset: parseRuntimeVisibilityPreset(args['runtime-visibility']),
|
|
@@ -1832,7 +1888,7 @@ export async function main() {
|
|
|
1832
1888
|
let codexSkills = [];
|
|
1833
1889
|
const buildCurrentRuntimeDescriptor = () => ({
|
|
1834
1890
|
defaultWorkspaceId: workspaceOptions[0]?.id,
|
|
1835
|
-
...(
|
|
1891
|
+
...(codexDefaultModel ? { defaultModel: codexDefaultModel } : {}),
|
|
1836
1892
|
availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1837
1893
|
availableExecutionModes: hostAvailableExecutionModes,
|
|
1838
1894
|
availablePermissionModes: [...codexPermissionEnvelope.availablePermissionModes],
|
|
@@ -1841,6 +1897,8 @@ export async function main() {
|
|
|
1841
1897
|
: {}),
|
|
1842
1898
|
runtimeDescriptor: buildCodexRuntimeDescriptor({
|
|
1843
1899
|
models: codexModelOptions,
|
|
1900
|
+
effortOptions: codexEffortOptions,
|
|
1901
|
+
defaultEffort: codexDefaultEffort,
|
|
1844
1902
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1845
1903
|
workspaceRoots: workspaceRootMetadata,
|
|
1846
1904
|
executionModes: hostAvailableExecutionModes,
|
|
@@ -1863,16 +1921,32 @@ export async function main() {
|
|
|
1863
1921
|
model: typeof args.model === 'string' ? args.model : null,
|
|
1864
1922
|
configOverrides: args.config ?? [],
|
|
1865
1923
|
});
|
|
1924
|
+
try {
|
|
1925
|
+
const discoveredModels = await probe.listModels();
|
|
1926
|
+
if (discoveredModels.length > 0) {
|
|
1927
|
+
codexModels = discoveredModels;
|
|
1928
|
+
codexModelOptions = buildCodexModelOptions(codexModels, args.model);
|
|
1929
|
+
codexEffortOptions = buildCodexEffortOptions(codexModels);
|
|
1930
|
+
codexDefaultModel = resolveCodexDefaultModel(codexModels, args.model);
|
|
1931
|
+
codexDefaultEffort = resolveCodexEffortForModel({
|
|
1932
|
+
models: codexModels,
|
|
1933
|
+
model: codexDefaultModel,
|
|
1934
|
+
requestedEffort: configuredCodexEffort,
|
|
1935
|
+
}).value;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
catch (error) {
|
|
1939
|
+
console.error('[canon-codex] Failed to load Codex models:', error instanceof Error ? error.message : error);
|
|
1940
|
+
}
|
|
1866
1941
|
try {
|
|
1867
1942
|
codexSkills = await probe.listSkills({ forceReload });
|
|
1868
|
-
runtimeDescriptor = buildCurrentRuntimeDescriptor();
|
|
1869
1943
|
}
|
|
1870
1944
|
catch (error) {
|
|
1871
1945
|
codexSkills = [];
|
|
1872
|
-
runtimeDescriptor = buildCurrentRuntimeDescriptor();
|
|
1873
1946
|
console.error('[canon-codex] Failed to load Codex skills:', error instanceof Error ? error.message : error);
|
|
1874
1947
|
}
|
|
1875
1948
|
finally {
|
|
1949
|
+
runtimeDescriptor = buildCurrentRuntimeDescriptor();
|
|
1876
1950
|
probe.close();
|
|
1877
1951
|
}
|
|
1878
1952
|
}
|
|
@@ -1880,7 +1954,13 @@ export async function main() {
|
|
|
1880
1954
|
const session = sessions.get(conversationId);
|
|
1881
1955
|
if (!session || session.closed)
|
|
1882
1956
|
return;
|
|
1957
|
+
let modelChanged = false;
|
|
1883
1958
|
if (control.model && control.model !== session.state.model) {
|
|
1959
|
+
if (codexModelOptions.length > 0 && !codexModelOptions.some((option) => option.value === control.model)) {
|
|
1960
|
+
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Ignoring model outside the discovered Codex catalog (${control.model})`);
|
|
1961
|
+
writeState(session);
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1884
1964
|
const modelGuard = buildCodexModelGuardMessage(control.model, codexCliStatus);
|
|
1885
1965
|
if (modelGuard) {
|
|
1886
1966
|
session.state.lastError = modelGuard;
|
|
@@ -1892,29 +1972,36 @@ export async function main() {
|
|
|
1892
1972
|
}
|
|
1893
1973
|
session.adapter.setModel(control.model);
|
|
1894
1974
|
session.state.model = control.model;
|
|
1975
|
+
modelChanged = true;
|
|
1895
1976
|
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Model set for next turn -> ${control.model}`);
|
|
1896
|
-
writeState(session);
|
|
1897
1977
|
}
|
|
1898
1978
|
if (control.permissionMode) {
|
|
1899
1979
|
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] approval mode is session-creation-only; ignoring mid-session change request (${control.permissionMode})`);
|
|
1900
1980
|
// Convergence contract: a consumed session control must always be
|
|
1901
1981
|
// answered. Re-publish the currently applied state so clients settle on
|
|
1902
1982
|
// the authoritative value instead of holding the composer until timeout.
|
|
1903
|
-
writeState(session);
|
|
1904
1983
|
}
|
|
1905
|
-
if (control.effort) {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
session.state.
|
|
1984
|
+
if (control.effort || modelChanged) {
|
|
1985
|
+
const effortResolution = resolveCodexEffortForModel({
|
|
1986
|
+
models: codexModels,
|
|
1987
|
+
model: session.state.model,
|
|
1988
|
+
requestedEffort: control.effort ?? session.state.effort,
|
|
1989
|
+
});
|
|
1990
|
+
if (effortResolution.value !== session.state.effort) {
|
|
1991
|
+
session.adapter.setReasoningEffort(effortResolution.value);
|
|
1992
|
+
session.state.effort = effortResolution.value ?? undefined;
|
|
1993
|
+
}
|
|
1994
|
+
if (control.effort && !effortResolution.accepted) {
|
|
1995
|
+
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] ${control.effort} is unsupported by ${session.state.model ?? 'the active model'}; reset to ${effortResolution.value ?? 'the model default'}`);
|
|
1996
|
+
}
|
|
1997
|
+
else if (control.effort) {
|
|
1909
1998
|
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Reasoning effort set for next turn -> ${control.effort}`);
|
|
1910
|
-
writeState(session);
|
|
1911
1999
|
}
|
|
1912
|
-
else {
|
|
1913
|
-
console.error(`[canon-codex] [${conversationId.slice(0, 8)}]
|
|
1914
|
-
// Same contract: ignored values still get an authoritative re-publish.
|
|
1915
|
-
writeState(session);
|
|
2000
|
+
else if (modelChanged && effortResolution.value) {
|
|
2001
|
+
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] Reasoning effort for ${session.state.model} -> ${effortResolution.value}`);
|
|
1916
2002
|
}
|
|
1917
2003
|
}
|
|
2004
|
+
writeState(session);
|
|
1918
2005
|
}
|
|
1919
2006
|
async function handleControlSignal(event) {
|
|
1920
2007
|
const { conversationId, type } = event;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CodexModelMetadata } from './app-server-adapter.js';
|
|
2
|
+
export interface CodexControlOption {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const FALLBACK_CODEX_EFFORT_OPTIONS: ReadonlyArray<CodexControlOption>;
|
|
8
|
+
export declare function buildCodexModelOptions(models: ReadonlyArray<CodexModelMetadata>, configuredModel?: unknown): CodexControlOption[];
|
|
9
|
+
export declare function buildCodexEffortOptions(models: ReadonlyArray<CodexModelMetadata>): CodexControlOption[];
|
|
10
|
+
export declare function resolveCodexDefaultModel(models: ReadonlyArray<CodexModelMetadata>, configuredModel?: unknown): string | null;
|
|
11
|
+
export declare function resolveCodexEffortForModel(input: {
|
|
12
|
+
models: ReadonlyArray<CodexModelMetadata>;
|
|
13
|
+
model: string | null | undefined;
|
|
14
|
+
requestedEffort: string | null | undefined;
|
|
15
|
+
}): {
|
|
16
|
+
value: string | null;
|
|
17
|
+
accepted: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function readCodexConfiguredEffort(configOverrides: ReadonlyArray<string>): string | null;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const EFFORT_LABELS = {
|
|
2
|
+
minimal: 'Minimal',
|
|
3
|
+
low: 'Low',
|
|
4
|
+
medium: 'Medium',
|
|
5
|
+
high: 'High',
|
|
6
|
+
xhigh: 'Extra high',
|
|
7
|
+
max: 'Max',
|
|
8
|
+
ultra: 'Ultra',
|
|
9
|
+
};
|
|
10
|
+
const EFFORT_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
|
|
11
|
+
export const FALLBACK_CODEX_EFFORT_OPTIONS = [
|
|
12
|
+
{ value: 'minimal', label: 'Minimal' },
|
|
13
|
+
{ value: 'low', label: 'Low' },
|
|
14
|
+
{ value: 'medium', label: 'Medium' },
|
|
15
|
+
{ value: 'high', label: 'High' },
|
|
16
|
+
{ value: 'xhigh', label: 'Extra high' },
|
|
17
|
+
];
|
|
18
|
+
export function buildCodexModelOptions(models, configuredModel) {
|
|
19
|
+
const configured = typeof configuredModel === 'string' ? configuredModel.trim() : '';
|
|
20
|
+
const ordered = [...models].sort((left, right) => {
|
|
21
|
+
if (configured) {
|
|
22
|
+
if (left.model === configured)
|
|
23
|
+
return -1;
|
|
24
|
+
if (right.model === configured)
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
return Number(right.isDefault) - Number(left.isDefault);
|
|
28
|
+
});
|
|
29
|
+
const options = ordered.map((model) => ({
|
|
30
|
+
value: model.model,
|
|
31
|
+
label: model.displayName,
|
|
32
|
+
...(model.description ? { description: model.description } : {}),
|
|
33
|
+
}));
|
|
34
|
+
if (configured && !options.some((option) => option.value === configured)) {
|
|
35
|
+
options.push({ value: configured, label: configured });
|
|
36
|
+
}
|
|
37
|
+
return options;
|
|
38
|
+
}
|
|
39
|
+
export function buildCodexEffortOptions(models) {
|
|
40
|
+
if (models.length === 0)
|
|
41
|
+
return [...FALLBACK_CODEX_EFFORT_OPTIONS];
|
|
42
|
+
const values = new Set();
|
|
43
|
+
const descriptions = new Map();
|
|
44
|
+
for (const model of models) {
|
|
45
|
+
for (const effort of model.supportedReasoningEfforts) {
|
|
46
|
+
values.add(effort.value);
|
|
47
|
+
if (effort.description && !descriptions.has(effort.value)) {
|
|
48
|
+
descriptions.set(effort.value, effort.description);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return [...values]
|
|
53
|
+
.sort((left, right) => {
|
|
54
|
+
const leftIndex = EFFORT_ORDER.indexOf(left);
|
|
55
|
+
const rightIndex = EFFORT_ORDER.indexOf(right);
|
|
56
|
+
if (leftIndex === -1 && rightIndex === -1)
|
|
57
|
+
return left.localeCompare(right);
|
|
58
|
+
if (leftIndex === -1)
|
|
59
|
+
return 1;
|
|
60
|
+
if (rightIndex === -1)
|
|
61
|
+
return -1;
|
|
62
|
+
return leftIndex - rightIndex;
|
|
63
|
+
})
|
|
64
|
+
.map((value) => {
|
|
65
|
+
const description = buildEffortDescription(value, descriptions.get(value), models);
|
|
66
|
+
return {
|
|
67
|
+
value,
|
|
68
|
+
label: EFFORT_LABELS[value] ?? humanizeEffort(value),
|
|
69
|
+
...(description ? { description } : {}),
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function resolveCodexDefaultModel(models, configuredModel) {
|
|
74
|
+
const configured = typeof configuredModel === 'string' ? configuredModel.trim() : '';
|
|
75
|
+
if (configured)
|
|
76
|
+
return configured;
|
|
77
|
+
return models.find((model) => model.isDefault)?.model ?? models[0]?.model ?? null;
|
|
78
|
+
}
|
|
79
|
+
export function resolveCodexEffortForModel(input) {
|
|
80
|
+
const requested = input.requestedEffort?.trim() || null;
|
|
81
|
+
const model = input.models.find((candidate) => candidate.model === input.model);
|
|
82
|
+
if (!model) {
|
|
83
|
+
const fallbackValues = new Set([
|
|
84
|
+
...FALLBACK_CODEX_EFFORT_OPTIONS.map((option) => option.value),
|
|
85
|
+
...input.models.flatMap((candidate) => (candidate.supportedReasoningEfforts.map((effort) => effort.value))),
|
|
86
|
+
]);
|
|
87
|
+
if (!requested)
|
|
88
|
+
return { value: 'medium', accepted: true };
|
|
89
|
+
return fallbackValues.has(requested)
|
|
90
|
+
? { value: requested, accepted: true }
|
|
91
|
+
: { value: 'medium', accepted: false };
|
|
92
|
+
}
|
|
93
|
+
const supported = new Set(model.supportedReasoningEfforts.map((effort) => effort.value));
|
|
94
|
+
const fallback = supported.has(model.defaultReasoningEffort ?? '')
|
|
95
|
+
? model.defaultReasoningEffort ?? null
|
|
96
|
+
: model.supportedReasoningEfforts[0]?.value ?? null;
|
|
97
|
+
if (!requested)
|
|
98
|
+
return { value: fallback, accepted: true };
|
|
99
|
+
return supported.has(requested)
|
|
100
|
+
? { value: requested, accepted: true }
|
|
101
|
+
: { value: fallback, accepted: false };
|
|
102
|
+
}
|
|
103
|
+
export function readCodexConfiguredEffort(configOverrides) {
|
|
104
|
+
let configured = null;
|
|
105
|
+
for (const raw of configOverrides) {
|
|
106
|
+
const separator = raw.indexOf('=');
|
|
107
|
+
if (separator <= 0 || raw.slice(0, separator).trim() !== 'model_reasoning_effort')
|
|
108
|
+
continue;
|
|
109
|
+
const value = raw.slice(separator + 1).trim();
|
|
110
|
+
if (!value)
|
|
111
|
+
continue;
|
|
112
|
+
configured = unquote(value);
|
|
113
|
+
}
|
|
114
|
+
return configured;
|
|
115
|
+
}
|
|
116
|
+
function humanizeEffort(value) {
|
|
117
|
+
return value
|
|
118
|
+
.replace(/[_-]+/g, ' ')
|
|
119
|
+
.replace(/\b\w/g, (character) => character.toUpperCase());
|
|
120
|
+
}
|
|
121
|
+
function buildEffortDescription(effort, nativeDescription, models) {
|
|
122
|
+
const supportedBy = models.filter((model) => (model.supportedReasoningEfforts.some((candidate) => candidate.value === effort)));
|
|
123
|
+
const compatibility = supportedBy.length > 0 && supportedBy.length < models.length
|
|
124
|
+
? `Supported by ${supportedBy.map((model) => model.displayName).join(', ')}.`
|
|
125
|
+
: '';
|
|
126
|
+
return [nativeDescription, compatibility].filter(Boolean).join(' ') || undefined;
|
|
127
|
+
}
|
|
128
|
+
function unquote(value) {
|
|
129
|
+
if (value.length >= 2) {
|
|
130
|
+
const first = value[0];
|
|
131
|
+
const last = value[value.length - 1];
|
|
132
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
133
|
+
return value.slice(1, -1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.3",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@canonmsg/agent-sdk": "^5.1.1",
|
|
33
33
|
"@canonmsg/coding-agent-host": "^0.2.2",
|
|
34
|
-
"@canonmsg/core": "^4.2.
|
|
34
|
+
"@canonmsg/core": "^4.2.3"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=18.0.0"
|