@nyssance/codex-acp-v2 0.3.4 → 0.3.5
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/CHANGELOG.md +41 -0
- package/dist/index.js +824 -215
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -21721,7 +21721,7 @@ var package_default = {
|
|
|
21721
21721
|
publishConfig: {
|
|
21722
21722
|
access: "public"
|
|
21723
21723
|
},
|
|
21724
|
-
version: "0.3.
|
|
21724
|
+
version: "0.3.5",
|
|
21725
21725
|
description: "Native ACP v2 agent for the OpenAI Codex app-server",
|
|
21726
21726
|
packageManager: "bun@1.4.0",
|
|
21727
21727
|
engines: {
|
|
@@ -21733,6 +21733,7 @@ var package_default = {
|
|
|
21733
21733
|
files: [
|
|
21734
21734
|
"dist/index.js",
|
|
21735
21735
|
"README.md",
|
|
21736
|
+
"CHANGELOG.md",
|
|
21736
21737
|
"LICENSE",
|
|
21737
21738
|
"package.json"
|
|
21738
21739
|
],
|
|
@@ -21758,7 +21759,9 @@ var package_default = {
|
|
|
21758
21759
|
test: "vitest run --exclude src/__tests__/e2e.test.ts",
|
|
21759
21760
|
"test:watch": "vitest",
|
|
21760
21761
|
typecheck: "tsc --noEmit",
|
|
21761
|
-
"test:e2e": "RUN_E2E_TESTS=true vitest run src/__tests__/e2e.test.ts"
|
|
21762
|
+
"test:e2e": "RUN_E2E_TESTS=true vitest run src/__tests__/e2e.test.ts",
|
|
21763
|
+
"test:benchmark": "RUN_BENCHMARKS=true vitest run src/__tests__/benchmark.test.ts",
|
|
21764
|
+
"test:soak": "bun --expose-gc src/benchmarks/bridge-soak.ts"
|
|
21762
21765
|
},
|
|
21763
21766
|
homepage: "https://github.com/nyssance/codex-acp-v2#readme",
|
|
21764
21767
|
bugs: {
|
|
@@ -21794,6 +21797,46 @@ var package_default = {
|
|
|
21794
21797
|
}
|
|
21795
21798
|
};
|
|
21796
21799
|
|
|
21800
|
+
// src/agent/turnErrors.ts
|
|
21801
|
+
function classifyTurnError(info) {
|
|
21802
|
+
const failure = (category, retryable = false, stopReason = "_error") => ({ category, retryable, stopReason });
|
|
21803
|
+
if (info === null)
|
|
21804
|
+
return failure("unknown");
|
|
21805
|
+
if (typeof info === "object") {
|
|
21806
|
+
if ("activeTurnNotSteerable" in info)
|
|
21807
|
+
return failure("turn_not_steerable");
|
|
21808
|
+
const status = "httpConnectionFailed" in info ? info.httpConnectionFailed.httpStatusCode : ("responseStreamConnectionFailed" in info) ? info.responseStreamConnectionFailed.httpStatusCode : ("responseStreamDisconnected" in info) ? info.responseStreamDisconnected.httpStatusCode : info.responseTooManyFailedAttempts.httpStatusCode;
|
|
21809
|
+
return failure("connection", status === null || status === 408 || status === 429 || status >= 500);
|
|
21810
|
+
}
|
|
21811
|
+
switch (info) {
|
|
21812
|
+
case "contextWindowExceeded":
|
|
21813
|
+
return failure("context_window", false, "max_tokens");
|
|
21814
|
+
case "cyberPolicy":
|
|
21815
|
+
case "misalignmentPolicyViolation":
|
|
21816
|
+
return failure("policy", false, "refusal");
|
|
21817
|
+
case "sessionBudgetExceeded":
|
|
21818
|
+
return failure("budget");
|
|
21819
|
+
case "usageLimitExceeded":
|
|
21820
|
+
return failure("usage_limit");
|
|
21821
|
+
case "rateLimitExceeded":
|
|
21822
|
+
return failure("rate_limit", true);
|
|
21823
|
+
case "serverOverloaded":
|
|
21824
|
+
return failure("overloaded", true);
|
|
21825
|
+
case "internalServerError":
|
|
21826
|
+
return failure("internal", true);
|
|
21827
|
+
case "unauthorized":
|
|
21828
|
+
return failure("authentication");
|
|
21829
|
+
case "badRequest":
|
|
21830
|
+
return failure("invalid_request");
|
|
21831
|
+
case "threadRollbackFailed":
|
|
21832
|
+
return failure("rollback");
|
|
21833
|
+
case "sandboxError":
|
|
21834
|
+
return failure("sandbox");
|
|
21835
|
+
case "other":
|
|
21836
|
+
return failure("unknown");
|
|
21837
|
+
}
|
|
21838
|
+
}
|
|
21839
|
+
|
|
21797
21840
|
// src/agent/CodexAgent.ts
|
|
21798
21841
|
import path6 from "path";
|
|
21799
21842
|
|
|
@@ -22756,9 +22799,6 @@ function toAcpStatus(status) {
|
|
|
22756
22799
|
return "cancelled";
|
|
22757
22800
|
}
|
|
22758
22801
|
}
|
|
22759
|
-
function toolCallCancelled(toolCallId) {
|
|
22760
|
-
return frame({ toolCallId, status: "cancelled" });
|
|
22761
|
-
}
|
|
22762
22802
|
function frame(fields) {
|
|
22763
22803
|
return { sessionUpdate: "tool_call_update", ...fields };
|
|
22764
22804
|
}
|
|
@@ -22847,14 +22887,17 @@ function commandStarted(item) {
|
|
|
22847
22887
|
});
|
|
22848
22888
|
}
|
|
22849
22889
|
function commandCompleted(item) {
|
|
22890
|
+
const output = item.aggregatedOutput ?? "";
|
|
22891
|
+
const truncated = output.length > 16384;
|
|
22850
22892
|
return frame({
|
|
22851
22893
|
toolCallId: item.id,
|
|
22852
22894
|
status: item.status === "completed" ? "completed" : "failed",
|
|
22853
22895
|
rawOutput: {
|
|
22854
|
-
output:
|
|
22896
|
+
output: truncated ? output.slice(0, 16384).replace(/[\uD800-\uDBFF]$/, "") : output,
|
|
22855
22897
|
exitCode: item.exitCode,
|
|
22856
22898
|
durationMs: item.durationMs
|
|
22857
22899
|
},
|
|
22900
|
+
...truncated ? { _meta: { codex: { outputTruncated: true, outputCharacters: output.length, ...usesTerminal(item) ? { terminalId: item.id } : {} } } } : {},
|
|
22858
22901
|
...usesTerminal(item) || !item.aggregatedOutput ? {} : { content: [textContent(item.aggregatedOutput)] }
|
|
22859
22902
|
});
|
|
22860
22903
|
}
|
|
@@ -23071,8 +23114,8 @@ function fuzzySearchUpdated(event, create) {
|
|
|
23071
23114
|
rawInput: { query: event.query }
|
|
23072
23115
|
});
|
|
23073
23116
|
}
|
|
23074
|
-
function fuzzySearchCompleted(event) {
|
|
23075
|
-
return frame({ toolCallId: fuzzySearchToolCallId(event.sessionId), status: "completed" });
|
|
23117
|
+
function fuzzySearchCompleted(event, create = false) {
|
|
23118
|
+
return frame({ toolCallId: fuzzySearchToolCallId(event.sessionId), ...create ? { name: ToolName.FuzzySearch, title: "File search", kind: "search" } : {}, status: "completed" });
|
|
23076
23119
|
}
|
|
23077
23120
|
function guardianReviewToolCallId(reviewId) {
|
|
23078
23121
|
return `guardian-review:${reviewId}`;
|
|
@@ -23143,6 +23186,7 @@ function mcpStartupFailed(serverName, message) {
|
|
|
23143
23186
|
}
|
|
23144
23187
|
|
|
23145
23188
|
// src/codex/sessionConfig.ts
|
|
23189
|
+
import { pathToFileURL } from "url";
|
|
23146
23190
|
import path3 from "path";
|
|
23147
23191
|
var MCP_SERVER_NAME_WHITESPACE = /\p{White_Space}/gu;
|
|
23148
23192
|
function sanitizeMcpServerName(name) {
|
|
@@ -23263,7 +23307,7 @@ function fromUserInput(input) {
|
|
|
23263
23307
|
}
|
|
23264
23308
|
}
|
|
23265
23309
|
function toFileUri(value) {
|
|
23266
|
-
return value.startsWith("file://") ? value :
|
|
23310
|
+
return value.startsWith("file://") ? value : pathToFileURL(value).href;
|
|
23267
23311
|
}
|
|
23268
23312
|
function isSupportedImageUrl(uri) {
|
|
23269
23313
|
if (!uri)
|
|
@@ -23295,6 +23339,65 @@ function promptTitle(prompt) {
|
|
|
23295
23339
|
return null;
|
|
23296
23340
|
}
|
|
23297
23341
|
|
|
23342
|
+
// src/bridge/itemSnapshot.ts
|
|
23343
|
+
function itemSnapshot(item) {
|
|
23344
|
+
switch (item.type) {
|
|
23345
|
+
case "userMessage": {
|
|
23346
|
+
const content = item.content.flatMap(fromUserInput);
|
|
23347
|
+
return content.length > 0 ? [{ sessionUpdate: "user_message", messageId: item.id, content }] : [];
|
|
23348
|
+
}
|
|
23349
|
+
case "agentMessage":
|
|
23350
|
+
return item.text.length > 0 ? [{
|
|
23351
|
+
sessionUpdate: "agent_message",
|
|
23352
|
+
messageId: item.id,
|
|
23353
|
+
content: [{ type: "text", text: item.text }],
|
|
23354
|
+
...item.phase ? { _meta: { codex: { phase: item.phase } } } : {}
|
|
23355
|
+
}] : [];
|
|
23356
|
+
case "reasoning": {
|
|
23357
|
+
const parts = (item.summary.length > 0 ? item.summary : item.content).filter((part) => part.length > 0);
|
|
23358
|
+
return parts.length > 0 ? [{ sessionUpdate: "agent_thought", messageId: item.id, content: parts.map((text) => ({ type: "text", text })) }] : [];
|
|
23359
|
+
}
|
|
23360
|
+
case "plan":
|
|
23361
|
+
return item.text.length > 0 ? [{ sessionUpdate: "plan_update", plan: { type: "markdown", planId: item.id, content: item.text } }] : [];
|
|
23362
|
+
case "fileChange":
|
|
23363
|
+
return [fileChangeStarted(item)];
|
|
23364
|
+
case "commandExecution": {
|
|
23365
|
+
const updates = [commandStarted(item)];
|
|
23366
|
+
if (usesTerminal(item))
|
|
23367
|
+
updates.unshift(terminalSnapshot(item));
|
|
23368
|
+
if (item.status !== "inProgress")
|
|
23369
|
+
updates.push(commandCompleted(item));
|
|
23370
|
+
return updates;
|
|
23371
|
+
}
|
|
23372
|
+
case "mcpToolCall":
|
|
23373
|
+
return [mcpToolCallStarted(item)];
|
|
23374
|
+
case "dynamicToolCall":
|
|
23375
|
+
return [{ ...dynamicToolCallStarted(item), ...dynamicToolCallCompleted(item) }];
|
|
23376
|
+
case "webSearch":
|
|
23377
|
+
return [webSearchSnapshot(item)];
|
|
23378
|
+
case "imageView":
|
|
23379
|
+
return [imageViewed(item)];
|
|
23380
|
+
case "imageGeneration":
|
|
23381
|
+
return [imageGenerationSnapshot(item)];
|
|
23382
|
+
case "collabAgentToolCall":
|
|
23383
|
+
return [collabToolCall(item, true)];
|
|
23384
|
+
case "subAgentActivity":
|
|
23385
|
+
return [subAgentActivity(item, "completed", true)];
|
|
23386
|
+
case "contextCompaction":
|
|
23387
|
+
return [compactionUpdate(item.id, "completed")];
|
|
23388
|
+
case "enteredReviewMode":
|
|
23389
|
+
return [];
|
|
23390
|
+
case "exitedReviewMode": {
|
|
23391
|
+
const text = item.review.trim();
|
|
23392
|
+
return text.length > 0 ? [{ sessionUpdate: "agent_message", messageId: item.id, content: [{ type: "text", text }] }] : [];
|
|
23393
|
+
}
|
|
23394
|
+
case "hookPrompt":
|
|
23395
|
+
case "functionCallOutput":
|
|
23396
|
+
case "sleep":
|
|
23397
|
+
return [];
|
|
23398
|
+
}
|
|
23399
|
+
}
|
|
23400
|
+
|
|
23298
23401
|
// src/bridge/EventBridge.ts
|
|
23299
23402
|
var TURN_PLAN_ID = "codex-turn-plan";
|
|
23300
23403
|
var PLAN_STREAM_INTERVAL_MS = 150;
|
|
@@ -23307,6 +23410,7 @@ class EventBridge {
|
|
|
23307
23410
|
completedPlan = null;
|
|
23308
23411
|
noticeSequence = 0;
|
|
23309
23412
|
openToolCalls = new Set;
|
|
23413
|
+
announcedTools = new Set;
|
|
23310
23414
|
messagePhases = new Map;
|
|
23311
23415
|
reasoningWithDeltas = new Set;
|
|
23312
23416
|
terminalItems = new Set;
|
|
@@ -23327,6 +23431,14 @@ class EventBridge {
|
|
|
23327
23431
|
this.lastError = null;
|
|
23328
23432
|
this.completedPlan = null;
|
|
23329
23433
|
this.openToolCalls.clear();
|
|
23434
|
+
this.announcedTools.clear();
|
|
23435
|
+
this.messagePhases.clear();
|
|
23436
|
+
this.reasoningWithDeltas.clear();
|
|
23437
|
+
this.terminalItems.clear();
|
|
23438
|
+
this.imageGenerations.clear();
|
|
23439
|
+
this.subAgentActivities.clear();
|
|
23440
|
+
this.fuzzySearches.clear();
|
|
23441
|
+
this.guardianReviews.clear();
|
|
23330
23442
|
this.clearPlanState();
|
|
23331
23443
|
}
|
|
23332
23444
|
takeError() {
|
|
@@ -23339,24 +23451,47 @@ class EventBridge {
|
|
|
23339
23451
|
this.completedPlan = null;
|
|
23340
23452
|
return plan;
|
|
23341
23453
|
}
|
|
23454
|
+
async restore(items) {
|
|
23455
|
+
for (const item of items) {
|
|
23456
|
+
if (item.type === "commandExecution" && item.status === "inProgress" && usesTerminal(item))
|
|
23457
|
+
this.terminalItems.add(item.id);
|
|
23458
|
+
for (const update of itemSnapshot(item)) {
|
|
23459
|
+
this.trackToolCall(update);
|
|
23460
|
+
await this.client.update(update);
|
|
23461
|
+
}
|
|
23462
|
+
}
|
|
23463
|
+
}
|
|
23342
23464
|
async handle(notification) {
|
|
23343
|
-
|
|
23465
|
+
let updates = await this.translate(notification);
|
|
23466
|
+
if (notification.method === "item/completed") {
|
|
23467
|
+
const snapshots = itemSnapshot(notification.params.item);
|
|
23468
|
+
const first = snapshots.find((update) => update.sessionUpdate === "tool_call_update");
|
|
23469
|
+
if (first?.sessionUpdate === "tool_call_update" && !this.announcedTools.has(first.toolCallId)) {
|
|
23470
|
+
updates = snapshots;
|
|
23471
|
+
}
|
|
23472
|
+
}
|
|
23344
23473
|
for (const update of updates) {
|
|
23345
23474
|
this.trackToolCall(update);
|
|
23346
23475
|
await this.client.update(update);
|
|
23347
23476
|
}
|
|
23348
23477
|
}
|
|
23349
|
-
async
|
|
23478
|
+
async finishOpenToolCalls(status) {
|
|
23350
23479
|
const ids = [...this.openToolCalls];
|
|
23480
|
+
const terminals = [...this.terminalItems];
|
|
23351
23481
|
this.openToolCalls.clear();
|
|
23482
|
+
this.terminalItems.clear();
|
|
23483
|
+
for (const terminalId of terminals) {
|
|
23484
|
+
await this.client.update({ sessionUpdate: "terminal_update", terminalId, exitStatus: { exitCode: null, signal: null }, _meta: { codex: { reconciled: true, turnOutcome: status } } });
|
|
23485
|
+
}
|
|
23352
23486
|
for (const toolCallId of ids) {
|
|
23353
|
-
await this.client.update(
|
|
23487
|
+
await this.client.update({ sessionUpdate: "tool_call_update", toolCallId, status, _meta: { codex: { reconciled: true } } });
|
|
23354
23488
|
}
|
|
23355
23489
|
}
|
|
23356
23490
|
trackToolCall(update) {
|
|
23357
23491
|
if (update.sessionUpdate !== "tool_call_update")
|
|
23358
23492
|
return;
|
|
23359
23493
|
const { toolCallId, status } = update;
|
|
23494
|
+
this.announcedTools.add(toolCallId);
|
|
23360
23495
|
if (status === "pending" || status === "in_progress")
|
|
23361
23496
|
this.openToolCalls.add(toolCallId);
|
|
23362
23497
|
else if (status !== undefined && status !== null)
|
|
@@ -23366,7 +23501,7 @@ class EventBridge {
|
|
|
23366
23501
|
await this.flushPlans();
|
|
23367
23502
|
}
|
|
23368
23503
|
dispose() {
|
|
23369
|
-
this.
|
|
23504
|
+
this.beginTurn();
|
|
23370
23505
|
}
|
|
23371
23506
|
async translate(notification) {
|
|
23372
23507
|
switch (notification.method) {
|
|
@@ -23398,14 +23533,17 @@ ${stdin}
|
|
|
23398
23533
|
`)] : [];
|
|
23399
23534
|
}
|
|
23400
23535
|
case "item/fileChange/patchUpdated":
|
|
23401
|
-
return [fileChangePatched({
|
|
23536
|
+
return [(this.announcedTools.has(notification.params.itemId) ? fileChangePatched : fileChangeStarted)({
|
|
23402
23537
|
type: "fileChange",
|
|
23403
23538
|
id: notification.params.itemId,
|
|
23404
23539
|
changes: notification.params.changes,
|
|
23405
23540
|
status: "inProgress"
|
|
23406
23541
|
})];
|
|
23407
23542
|
case "item/mcpToolCall/progress":
|
|
23408
|
-
return [
|
|
23543
|
+
return [{
|
|
23544
|
+
...!this.announcedTools.has(notification.params.itemId) ? { name: ToolName.Mcp, title: "MCP tool", kind: "execute", status: "in_progress" } : {},
|
|
23545
|
+
...mcpToolCallProgress(notification.params.itemId, notification.params.message)
|
|
23546
|
+
}];
|
|
23409
23547
|
case "item/plan/delta":
|
|
23410
23548
|
this.planDelta(notification.params.itemId, notification.params.delta);
|
|
23411
23549
|
return [];
|
|
@@ -23427,9 +23565,11 @@ ${stdin}
|
|
|
23427
23565
|
this.fuzzySearches.add(id);
|
|
23428
23566
|
return [fuzzySearchUpdated(notification.params, create)];
|
|
23429
23567
|
}
|
|
23430
|
-
case "fuzzyFileSearch/sessionCompleted":
|
|
23568
|
+
case "fuzzyFileSearch/sessionCompleted": {
|
|
23569
|
+
const create = !this.fuzzySearches.has(notification.params.sessionId);
|
|
23431
23570
|
this.fuzzySearches.delete(notification.params.sessionId);
|
|
23432
|
-
return [fuzzySearchCompleted(notification.params)];
|
|
23571
|
+
return [fuzzySearchCompleted(notification.params, create)];
|
|
23572
|
+
}
|
|
23433
23573
|
case "thread/tokenUsage/updated": {
|
|
23434
23574
|
const usage = notification.params.tokenUsage;
|
|
23435
23575
|
this.session.lastUsage = toTokenCount(usage.last);
|
|
@@ -23538,7 +23678,7 @@ ${details}` : summary)];
|
|
|
23538
23678
|
case "commandExecution":
|
|
23539
23679
|
if (usesTerminal(item)) {
|
|
23540
23680
|
this.terminalItems.add(item.id);
|
|
23541
|
-
return [
|
|
23681
|
+
return [terminalStarted(item), commandStarted(item)];
|
|
23542
23682
|
}
|
|
23543
23683
|
return [commandStarted(item)];
|
|
23544
23684
|
case "mcpToolCall":
|
|
@@ -23577,7 +23717,7 @@ ${details}` : summary)];
|
|
|
23577
23717
|
switch (item.type) {
|
|
23578
23718
|
case "agentMessage":
|
|
23579
23719
|
this.messagePhases.delete(item.id);
|
|
23580
|
-
return
|
|
23720
|
+
return itemSnapshot(item);
|
|
23581
23721
|
case "reasoning": {
|
|
23582
23722
|
if (this.reasoningWithDeltas.delete(item.id))
|
|
23583
23723
|
return [];
|
|
@@ -25551,10 +25691,6 @@ async function logout(codex) {
|
|
|
25551
25691
|
await updated;
|
|
25552
25692
|
});
|
|
25553
25693
|
}
|
|
25554
|
-
async function authRequired(codex) {
|
|
25555
|
-
const account = await codex.accountRead({ refreshToken: false });
|
|
25556
|
-
return account.requiresOpenaiAuth && account.account === null;
|
|
25557
|
-
}
|
|
25558
25694
|
|
|
25559
25695
|
// src/agent/clientSession.ts
|
|
25560
25696
|
class ClientSession {
|
|
@@ -25565,6 +25701,11 @@ class ClientSession {
|
|
|
25565
25701
|
turnActive = false;
|
|
25566
25702
|
turnGeneration = 0;
|
|
25567
25703
|
disposed = false;
|
|
25704
|
+
output = new AbortController;
|
|
25705
|
+
terminalAttempted = false;
|
|
25706
|
+
get signal() {
|
|
25707
|
+
return this.output.signal;
|
|
25708
|
+
}
|
|
25568
25709
|
constructor(sessionId, link, capabilities) {
|
|
25569
25710
|
this.sessionId = sessionId;
|
|
25570
25711
|
this.link = link;
|
|
@@ -25573,38 +25714,46 @@ class ClientSession {
|
|
|
25573
25714
|
async update(update) {
|
|
25574
25715
|
if (this.disposed)
|
|
25575
25716
|
return;
|
|
25576
|
-
await this.link.notify(methods.client.session.update, { sessionId: this.sessionId, update });
|
|
25717
|
+
await abortable(this.link.notify(methods.client.session.update, { sessionId: this.sessionId, update }), this.output.signal);
|
|
25577
25718
|
}
|
|
25578
25719
|
async updateAll(updates) {
|
|
25579
25720
|
for (const update of updates)
|
|
25580
25721
|
await this.update(update);
|
|
25581
25722
|
}
|
|
25582
25723
|
async requestPermission(request, signal) {
|
|
25583
|
-
signal
|
|
25724
|
+
signal = signal ? AbortSignal.any([signal, this.output.signal]) : this.output.signal;
|
|
25725
|
+
signal.throwIfAborted();
|
|
25584
25726
|
return await this.waitingOnClient(() => {
|
|
25727
|
+
signal?.throwIfAborted();
|
|
25585
25728
|
const pending = this.link.request(methods.client.session.requestPermission, { sessionId: this.sessionId, ...request }, signal ? { cancellationSignal: signal } : undefined);
|
|
25586
25729
|
return signal ? abortable(pending, signal) : pending;
|
|
25587
|
-
});
|
|
25730
|
+
}, signal);
|
|
25588
25731
|
}
|
|
25589
25732
|
async createElicitation(request, signal) {
|
|
25590
|
-
signal
|
|
25733
|
+
signal = signal ? AbortSignal.any([signal, this.output.signal]) : this.output.signal;
|
|
25734
|
+
signal.throwIfAborted();
|
|
25591
25735
|
return await this.waitingOnClient(() => {
|
|
25736
|
+
signal?.throwIfAborted();
|
|
25592
25737
|
const pending = this.link.request(methods.client.elicitation.create, request, signal ? { cancellationSignal: signal } : undefined);
|
|
25593
25738
|
return signal ? abortable(pending, signal) : pending;
|
|
25594
|
-
});
|
|
25739
|
+
}, signal);
|
|
25595
25740
|
}
|
|
25596
25741
|
async completeElicitation(elicitationId) {
|
|
25597
25742
|
if (this.disposed)
|
|
25598
25743
|
return;
|
|
25599
|
-
await this.link.notify(methods.client.elicitation.complete, { elicitationId });
|
|
25744
|
+
await abortable(this.link.notify(methods.client.elicitation.complete, { elicitationId }), this.output.signal);
|
|
25600
25745
|
}
|
|
25601
25746
|
reportRunning() {
|
|
25747
|
+
this.terminalAttempted = false;
|
|
25602
25748
|
this.turnGeneration += 1;
|
|
25603
25749
|
this.turnActive = true;
|
|
25604
25750
|
this.waiting = 0;
|
|
25605
25751
|
this.state({ sessionUpdate: "state_update", state: "running" });
|
|
25606
25752
|
}
|
|
25607
25753
|
async reportIdle(stopReason, extra) {
|
|
25754
|
+
if (this.terminalAttempted)
|
|
25755
|
+
return;
|
|
25756
|
+
this.terminalAttempted = true;
|
|
25608
25757
|
this.turnActive = false;
|
|
25609
25758
|
this.waiting = 0;
|
|
25610
25759
|
await this.update({
|
|
@@ -25615,18 +25764,20 @@ class ClientSession {
|
|
|
25615
25764
|
...extra?._meta === undefined ? {} : { _meta: extra._meta }
|
|
25616
25765
|
});
|
|
25617
25766
|
}
|
|
25618
|
-
async waitingOnClient(operation) {
|
|
25767
|
+
async waitingOnClient(operation, signal) {
|
|
25619
25768
|
if (this.disposed)
|
|
25620
25769
|
throw new Error("Session is closed");
|
|
25621
25770
|
const generation = this.turnActive ? this.turnGeneration : null;
|
|
25622
|
-
if (generation !== null && this.waiting++ === 0) {
|
|
25623
|
-
await this.state({ sessionUpdate: "state_update", state: "requires_action" });
|
|
25624
|
-
}
|
|
25625
25771
|
try {
|
|
25772
|
+
if (generation !== null && this.waiting++ === 0) {
|
|
25773
|
+
const pending = this.state({ sessionUpdate: "state_update", state: "requires_action" });
|
|
25774
|
+
await (signal ? abortable(pending, signal) : pending);
|
|
25775
|
+
}
|
|
25626
25776
|
return await operation();
|
|
25627
25777
|
} finally {
|
|
25628
25778
|
if (this.turnActive && generation === this.turnGeneration && --this.waiting === 0) {
|
|
25629
|
-
|
|
25779
|
+
const pending = this.state({ sessionUpdate: "state_update", state: "running" });
|
|
25780
|
+
await (signal ? abortable(pending, signal).catch(() => {}) : pending);
|
|
25630
25781
|
}
|
|
25631
25782
|
}
|
|
25632
25783
|
}
|
|
@@ -25639,6 +25790,7 @@ class ClientSession {
|
|
|
25639
25790
|
}
|
|
25640
25791
|
dispose() {
|
|
25641
25792
|
this.disposed = true;
|
|
25793
|
+
this.output.abort();
|
|
25642
25794
|
this.turnActive = false;
|
|
25643
25795
|
this.waiting = 0;
|
|
25644
25796
|
}
|
|
@@ -25878,7 +26030,7 @@ function historyUpdates(turns) {
|
|
|
25878
26030
|
const updates = [];
|
|
25879
26031
|
for (const turn of turns) {
|
|
25880
26032
|
for (const item of turn.items) {
|
|
25881
|
-
updates.push(...
|
|
26033
|
+
updates.push(...itemSnapshot(item));
|
|
25882
26034
|
}
|
|
25883
26035
|
}
|
|
25884
26036
|
return updates;
|
|
@@ -25903,63 +26055,6 @@ function firstLine(text2) {
|
|
|
25903
26055
|
const line = text2.split(/\r?\n/).map((part) => part.trim()).find((part) => part.length > 0) ?? text2;
|
|
25904
26056
|
return line.length > 120 ? `${line.slice(0, 117)}...` : line;
|
|
25905
26057
|
}
|
|
25906
|
-
function itemHistory(item) {
|
|
25907
|
-
switch (item.type) {
|
|
25908
|
-
case "userMessage": {
|
|
25909
|
-
const content = item.content.flatMap(fromUserInput);
|
|
25910
|
-
return content.length > 0 ? [{ sessionUpdate: "user_message", messageId: item.id, content }] : [];
|
|
25911
|
-
}
|
|
25912
|
-
case "agentMessage":
|
|
25913
|
-
return item.text.length > 0 ? [{
|
|
25914
|
-
sessionUpdate: "agent_message",
|
|
25915
|
-
messageId: item.id,
|
|
25916
|
-
content: [{ type: "text", text: item.text }],
|
|
25917
|
-
...item.phase ? { _meta: { codex: { phase: item.phase } } } : {}
|
|
25918
|
-
}] : [];
|
|
25919
|
-
case "reasoning": {
|
|
25920
|
-
const parts = (item.summary.length > 0 ? item.summary : item.content).filter((part) => part.length > 0);
|
|
25921
|
-
return parts.length > 0 ? [{ sessionUpdate: "agent_thought", messageId: item.id, content: parts.map((text2) => ({ type: "text", text: text2 })) }] : [];
|
|
25922
|
-
}
|
|
25923
|
-
case "plan":
|
|
25924
|
-
return item.text.length > 0 ? [{ sessionUpdate: "plan_update", plan: { type: "markdown", planId: item.id, content: item.text } }] : [];
|
|
25925
|
-
case "fileChange":
|
|
25926
|
-
return [fileChangeStarted(item)];
|
|
25927
|
-
case "commandExecution": {
|
|
25928
|
-
const updates = [commandStarted(item)];
|
|
25929
|
-
if (usesTerminal(item))
|
|
25930
|
-
updates.push(terminalSnapshot(item));
|
|
25931
|
-
if (item.status !== "inProgress")
|
|
25932
|
-
updates.push(commandCompleted(item));
|
|
25933
|
-
return updates;
|
|
25934
|
-
}
|
|
25935
|
-
case "mcpToolCall":
|
|
25936
|
-
return [mcpToolCallStarted(item)];
|
|
25937
|
-
case "dynamicToolCall":
|
|
25938
|
-
return [{ ...dynamicToolCallStarted(item), ...dynamicToolCallCompleted(item) }];
|
|
25939
|
-
case "webSearch":
|
|
25940
|
-
return [webSearchSnapshot(item)];
|
|
25941
|
-
case "imageView":
|
|
25942
|
-
return [imageViewed(item)];
|
|
25943
|
-
case "imageGeneration":
|
|
25944
|
-
return [imageGenerationSnapshot(item)];
|
|
25945
|
-
case "collabAgentToolCall":
|
|
25946
|
-
return [collabToolCall(item, true)];
|
|
25947
|
-
case "subAgentActivity":
|
|
25948
|
-
return [subAgentActivity(item, "completed", true)];
|
|
25949
|
-
case "contextCompaction":
|
|
25950
|
-
return [compactionUpdate(item.id, "completed")];
|
|
25951
|
-
case "enteredReviewMode":
|
|
25952
|
-
return [];
|
|
25953
|
-
case "exitedReviewMode": {
|
|
25954
|
-
const text2 = item.review.trim();
|
|
25955
|
-
return text2.length > 0 ? [{ sessionUpdate: "agent_message", messageId: item.id, content: [{ type: "text", text: text2 }] }] : [];
|
|
25956
|
-
}
|
|
25957
|
-
case "hookPrompt":
|
|
25958
|
-
case "functionCallOutput":
|
|
25959
|
-
case "sleep":
|
|
25960
|
-
return [];
|
|
25961
|
-
}
|
|
25962
|
-
}
|
|
25963
26058
|
|
|
25964
26059
|
// src/agent/providers.ts
|
|
25965
26060
|
var OPENAI_PROVIDER_ID = "openai";
|
|
@@ -25974,6 +26069,11 @@ class ProviderRouting {
|
|
|
25974
26069
|
this.baseConfig = baseConfig;
|
|
25975
26070
|
this.configuredProvider = configuredProvider;
|
|
25976
26071
|
}
|
|
26072
|
+
copy() {
|
|
26073
|
+
const copy = new ProviderRouting(this.baseConfig, this.configuredProvider);
|
|
26074
|
+
copy.gateway = this.gateway;
|
|
26075
|
+
return copy;
|
|
26076
|
+
}
|
|
25977
26077
|
get active() {
|
|
25978
26078
|
return this.gateway;
|
|
25979
26079
|
}
|
|
@@ -26137,7 +26237,6 @@ function createActiveTurn(threadId) {
|
|
|
26137
26237
|
// src/agent/CodexAgent.ts
|
|
26138
26238
|
var CLOSE_TURN_GRACE_MS = 5000;
|
|
26139
26239
|
var HISTORY_PAGE_SIZE = 50;
|
|
26140
|
-
var ERROR_STOP_REASON = "_error";
|
|
26141
26240
|
var IMPLEMENT_PLAN_OPTION = "implement_plan";
|
|
26142
26241
|
var REVISE_PLAN_OPTION = "revise_plan";
|
|
26143
26242
|
|
|
@@ -26146,6 +26245,11 @@ class CodexAgent {
|
|
|
26146
26245
|
codex;
|
|
26147
26246
|
process;
|
|
26148
26247
|
providers;
|
|
26248
|
+
switching = false;
|
|
26249
|
+
admissions = 0;
|
|
26250
|
+
pendingUnsubscribes = new Map;
|
|
26251
|
+
sessionMutations = new Set;
|
|
26252
|
+
providerQueue = Promise.resolve();
|
|
26149
26253
|
info;
|
|
26150
26254
|
env;
|
|
26151
26255
|
closeGraceMs;
|
|
@@ -26153,7 +26257,15 @@ class CodexAgent {
|
|
|
26153
26257
|
capabilities = null;
|
|
26154
26258
|
codexInitialized = false;
|
|
26155
26259
|
initializing = null;
|
|
26260
|
+
terminalTurns = new WeakSet;
|
|
26261
|
+
completedTurns = new WeakMap;
|
|
26156
26262
|
skillRoots = [];
|
|
26263
|
+
skillsGeneration = 0;
|
|
26264
|
+
changingSkillRoots = false;
|
|
26265
|
+
publishedSkills = new WeakMap;
|
|
26266
|
+
skillSnapshots = new Map;
|
|
26267
|
+
skillsQueue = Promise.resolve();
|
|
26268
|
+
accountGeneration = 0;
|
|
26157
26269
|
constructor(link, options) {
|
|
26158
26270
|
this.link = link;
|
|
26159
26271
|
this.codex = options.codex;
|
|
@@ -26164,6 +26276,18 @@ class CodexAgent {
|
|
|
26164
26276
|
this.closeGraceMs = options.closeGraceMs ?? CLOSE_TURN_GRACE_MS;
|
|
26165
26277
|
this.process?.exited.then(() => this.handleCodexExit());
|
|
26166
26278
|
this.codex.connection.onClose(() => this.handleCodexExit());
|
|
26279
|
+
const stopObserving = this.codex.observeNotifications((notification) => {
|
|
26280
|
+
if (notification.method === "skills/changed") {
|
|
26281
|
+
this.skillsGeneration += 1;
|
|
26282
|
+
this.skillSnapshots.clear();
|
|
26283
|
+
if (!this.changingSkillRoots)
|
|
26284
|
+
for (const runtime of this.sessions.values())
|
|
26285
|
+
this.refreshAvailableCommands(runtime);
|
|
26286
|
+
}
|
|
26287
|
+
if (notification.method === "account/updated")
|
|
26288
|
+
this.refreshAccounts().catch((error40) => logger.error("refreshing account failed", error40));
|
|
26289
|
+
});
|
|
26290
|
+
this.codex.disconnectSignal.addEventListener("abort", stopObserving, { once: true });
|
|
26167
26291
|
}
|
|
26168
26292
|
async initialize(params) {
|
|
26169
26293
|
if (params.protocolVersion !== PROTOCOL_VERSION) {
|
|
@@ -26222,7 +26346,11 @@ class CodexAgent {
|
|
|
26222
26346
|
return {};
|
|
26223
26347
|
}
|
|
26224
26348
|
async refreshAccounts() {
|
|
26349
|
+
this.codex.invalidateModels();
|
|
26350
|
+
const generation = ++this.accountGeneration;
|
|
26225
26351
|
const account = (await this.codex.accountRead({ refreshToken: false })).account;
|
|
26352
|
+
if (generation !== this.accountGeneration)
|
|
26353
|
+
return;
|
|
26226
26354
|
for (const runtime of this.sessions.values())
|
|
26227
26355
|
runtime.session.account = account;
|
|
26228
26356
|
}
|
|
@@ -26232,16 +26360,14 @@ class CodexAgent {
|
|
|
26232
26360
|
}
|
|
26233
26361
|
async setProvider(params) {
|
|
26234
26362
|
this.requireInitialized("providers/set");
|
|
26235
|
-
this.
|
|
26236
|
-
this.providers.set(params);
|
|
26237
|
-
await this.rebindSessions();
|
|
26363
|
+
await this.changeProvider((candidate) => candidate.set(params));
|
|
26238
26364
|
return {};
|
|
26239
26365
|
}
|
|
26240
26366
|
async disableProvider(params) {
|
|
26241
26367
|
this.requireInitialized("providers/disable");
|
|
26242
|
-
|
|
26243
|
-
|
|
26244
|
-
await this.
|
|
26368
|
+
if (params.providerId !== OPENAI_PROVIDER_ID)
|
|
26369
|
+
return {};
|
|
26370
|
+
await this.changeProvider((candidate) => candidate.disable(params));
|
|
26245
26371
|
return {};
|
|
26246
26372
|
}
|
|
26247
26373
|
assertNoActiveTurns(method) {
|
|
@@ -26250,39 +26376,147 @@ class CodexAgent {
|
|
|
26250
26376
|
throw RequestError.invalidRequest({ sessions: busy }, `${method} cannot change routing while a turn is running; cancel it or wait for idle`);
|
|
26251
26377
|
}
|
|
26252
26378
|
}
|
|
26253
|
-
|
|
26254
|
-
|
|
26255
|
-
|
|
26256
|
-
|
|
26257
|
-
|
|
26258
|
-
|
|
26259
|
-
|
|
26260
|
-
|
|
26261
|
-
|
|
26262
|
-
|
|
26263
|
-
|
|
26264
|
-
|
|
26265
|
-
|
|
26266
|
-
|
|
26267
|
-
|
|
26268
|
-
|
|
26269
|
-
|
|
26379
|
+
assertRoutingAvailable() {
|
|
26380
|
+
if (this.switching)
|
|
26381
|
+
throw RequestError.invalidRequest(undefined, "Provider routing is changing; retry after providers/set or providers/disable finishes");
|
|
26382
|
+
}
|
|
26383
|
+
async admission(operation, sessionId) {
|
|
26384
|
+
this.assertRoutingAvailable();
|
|
26385
|
+
if (sessionId && this.sessionMutations.has(sessionId))
|
|
26386
|
+
throw RequestError.invalidRequest({ sessionId }, "Session lifecycle or configuration work is in progress; retry when it finishes");
|
|
26387
|
+
if (sessionId)
|
|
26388
|
+
this.sessionMutations.add(sessionId);
|
|
26389
|
+
this.admissions += 1;
|
|
26390
|
+
try {
|
|
26391
|
+
return await operation();
|
|
26392
|
+
} finally {
|
|
26393
|
+
this.admissions -= 1;
|
|
26394
|
+
if (sessionId)
|
|
26395
|
+
this.sessionMutations.delete(sessionId);
|
|
26396
|
+
}
|
|
26397
|
+
}
|
|
26398
|
+
async changeProvider(change) {
|
|
26399
|
+
const pending = this.providerQueue.then(async () => {
|
|
26400
|
+
this.assertNoActiveTurns("providers/set or providers/disable");
|
|
26401
|
+
if (this.admissions > 0)
|
|
26402
|
+
throw RequestError.invalidRequest(undefined, "Session configuration or lifecycle work is in progress; retry the provider change when it finishes");
|
|
26403
|
+
this.switching = true;
|
|
26404
|
+
const candidate = this.providers.copy();
|
|
26405
|
+
const attempted = [];
|
|
26406
|
+
const staged = new Map;
|
|
26407
|
+
try {
|
|
26408
|
+
change(candidate);
|
|
26409
|
+
const catalog = await this.withCodex(() => this.codex.allModels(true));
|
|
26410
|
+
const modelProvider = await this.resolveModelProvider(candidate);
|
|
26411
|
+
for (const runtime of this.sessions.values()) {
|
|
26412
|
+
try {
|
|
26413
|
+
await this.codex.threadTurnsList({ threadId: runtime.session.id, limit: 1, itemsView: "notLoaded" });
|
|
26414
|
+
} catch (error40) {
|
|
26415
|
+
throw RequestError.invalidRequest({ sessionId: runtime.session.id, details: errorMessage(error40) }, "Cannot reload this session's history for a provider change; close empty sessions and configure the provider before creating them, or retry after history is available");
|
|
26416
|
+
}
|
|
26417
|
+
}
|
|
26418
|
+
for (const runtime of this.sessions.values()) {
|
|
26419
|
+
const config2 = { ...runtime.config };
|
|
26420
|
+
const providers = candidate.threadConfig()["model_providers"];
|
|
26421
|
+
if (providers === undefined)
|
|
26422
|
+
delete config2["model_providers"];
|
|
26423
|
+
else
|
|
26424
|
+
config2["model_providers"] = providers;
|
|
26425
|
+
const nextCatalog = candidate.catalog(catalog);
|
|
26426
|
+
const requestedModel = candidate.active?.model ?? (findModel(nextCatalog, runtime.session.model.model) ? runtime.session.model.model : null);
|
|
26427
|
+
const selection = resolveModelSelection(nextCatalog, requestedModel, requestedModel === runtime.session.model.model ? runtime.session.model.effort : null);
|
|
26428
|
+
attempted.push(runtime);
|
|
26429
|
+
await this.codex.threadUnsubscribe({ threadId: runtime.session.id });
|
|
26430
|
+
const thread = await this.withCodex(() => this.codex.threadResume({
|
|
26431
|
+
threadId: runtime.session.id,
|
|
26432
|
+
cwd: runtime.session.cwd,
|
|
26433
|
+
config: config2,
|
|
26434
|
+
modelProvider,
|
|
26435
|
+
model: selection.model,
|
|
26436
|
+
excludeTurns: true
|
|
26437
|
+
}));
|
|
26438
|
+
staged.set(runtime, { config: config2, catalog: nextCatalog, model: resolveModelSelection(nextCatalog, candidate.active?.model ?? thread.model, thread.reasoningEffort) });
|
|
26439
|
+
}
|
|
26440
|
+
this.providers = candidate;
|
|
26441
|
+
for (const [runtime, next] of staged) {
|
|
26442
|
+
runtime.config = next.config;
|
|
26443
|
+
runtime.modelProvider = modelProvider;
|
|
26444
|
+
runtime.session.catalog = next.catalog;
|
|
26445
|
+
runtime.session.model = next.model;
|
|
26446
|
+
runtime.stale = false;
|
|
26447
|
+
runtime.client.update({ sessionUpdate: "config_option_update", configOptions: sessionConfigOptions(runtime.session), _meta: { codex: { routing: { stale: false } } } }).catch((error40) => logger.error("Provider config notification failed", error40));
|
|
26448
|
+
}
|
|
26449
|
+
} catch (error40) {
|
|
26450
|
+
if (attempted.length === 0 && error40 instanceof RequestError)
|
|
26451
|
+
throw error40;
|
|
26452
|
+
const staleSessions = [];
|
|
26453
|
+
for (const runtime of attempted) {
|
|
26454
|
+
try {
|
|
26455
|
+
await this.codex.threadUnsubscribe({ threadId: runtime.session.id });
|
|
26456
|
+
await this.codex.threadResume({
|
|
26457
|
+
threadId: runtime.session.id,
|
|
26458
|
+
cwd: runtime.session.cwd,
|
|
26459
|
+
config: runtime.config,
|
|
26460
|
+
modelProvider: runtime.modelProvider,
|
|
26461
|
+
model: runtime.session.model.model,
|
|
26462
|
+
excludeTurns: true
|
|
26463
|
+
});
|
|
26464
|
+
} catch (rollbackError) {
|
|
26465
|
+
runtime.stale = true;
|
|
26466
|
+
staleSessions.push(runtime.session.id);
|
|
26467
|
+
logger.error("Provider rollback failed", rollbackError, { sessionId: runtime.session.id });
|
|
26468
|
+
runtime.client.update({ sessionUpdate: "config_option_update", configOptions: sessionConfigOptions(runtime.session), _meta: { codex: { routing: { stale: true } } } }).catch(() => {});
|
|
26469
|
+
}
|
|
26470
|
+
}
|
|
26471
|
+
throw RequestError.internalError({ staleSessions, attemptedSessions: attempted.map((runtime) => runtime.session.id) }, `Provider change failed: ${errorMessage(error40)}${staleSessions.length ? "; resume affected sessions or retry the provider change before prompting" : "; previous routing restored"}`);
|
|
26472
|
+
} finally {
|
|
26473
|
+
this.switching = false;
|
|
26474
|
+
}
|
|
26475
|
+
});
|
|
26476
|
+
this.providerQueue = pending.catch(() => {});
|
|
26477
|
+
await pending;
|
|
26270
26478
|
}
|
|
26271
|
-
async newSession(params) {
|
|
26272
|
-
const runtime = await this.openSession({ kind: "new", request: params });
|
|
26479
|
+
async newSession(params, signal) {
|
|
26480
|
+
const runtime = await this.openSession({ kind: "new", request: params }, signal);
|
|
26273
26481
|
return { sessionId: runtime.session.id, configOptions: sessionConfigOptions(runtime.session) };
|
|
26274
26482
|
}
|
|
26275
|
-
async resumeSession(params) {
|
|
26276
|
-
const runtime = await this.openSession({ kind: "resume", request: params });
|
|
26483
|
+
async resumeSession(params, signal) {
|
|
26484
|
+
const runtime = await this.openSession({ kind: "resume", request: params }, signal);
|
|
26277
26485
|
return { configOptions: sessionConfigOptions(runtime.session) };
|
|
26278
26486
|
}
|
|
26279
|
-
async forkSession(params) {
|
|
26280
|
-
const runtime = await this.openSession({ kind: "fork", request: params });
|
|
26487
|
+
async forkSession(params, signal) {
|
|
26488
|
+
const runtime = await this.openSession({ kind: "fork", request: params }, signal);
|
|
26281
26489
|
return { sessionId: runtime.session.id, configOptions: sessionConfigOptions(runtime.session) };
|
|
26282
26490
|
}
|
|
26283
|
-
async openSession(open2) {
|
|
26491
|
+
async openSession(open2, signal) {
|
|
26492
|
+
if (signal?.aborted)
|
|
26493
|
+
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
26494
|
+
const pending = this.admission(async () => {
|
|
26495
|
+
const runtime = await this.openSessionInternal(open2, signal);
|
|
26496
|
+
if (signal?.aborted) {
|
|
26497
|
+
await this.closeRuntime(runtime.session.id);
|
|
26498
|
+
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
26499
|
+
}
|
|
26500
|
+
return runtime;
|
|
26501
|
+
}, open2.kind === "new" ? undefined : open2.request.sessionId);
|
|
26502
|
+
try {
|
|
26503
|
+
return signal ? await abortable(pending, signal) : await pending;
|
|
26504
|
+
} catch (error40) {
|
|
26505
|
+
if (signal?.aborted)
|
|
26506
|
+
throw RequestError.requestCancelled(undefined, "Session opening was cancelled; any thread returned later will be unsubscribed");
|
|
26507
|
+
throw error40;
|
|
26508
|
+
}
|
|
26509
|
+
}
|
|
26510
|
+
assertUnsubscribeSettled(sessionId) {
|
|
26511
|
+
if (this.pendingUnsubscribes.has(sessionId)) {
|
|
26512
|
+
throw RequestError.invalidRequest({ sessionId }, "A previous unsubscribe is still pending; retry after Codex responds or reconnect the agent");
|
|
26513
|
+
}
|
|
26514
|
+
}
|
|
26515
|
+
async openSessionInternal(open2, signal) {
|
|
26284
26516
|
const method = { new: "session/new", resume: "session/resume", fork: "session/fork" }[open2.kind];
|
|
26285
26517
|
const capabilities = this.requireInitialized(method);
|
|
26518
|
+
if (open2.kind !== "new")
|
|
26519
|
+
this.assertUnsubscribeSettled(open2.request.sessionId);
|
|
26286
26520
|
const { request } = open2;
|
|
26287
26521
|
if (typeof request.cwd !== "string" || !path6.isAbsolute(request.cwd)) {
|
|
26288
26522
|
throw RequestError.invalidParams({ cwd: request.cwd }, "cwd must be an absolute path");
|
|
@@ -26294,17 +26528,21 @@ class CodexAgent {
|
|
|
26294
26528
|
const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories);
|
|
26295
26529
|
if (open2.kind === "resume" && this.sessions.has(open2.request.sessionId)) {
|
|
26296
26530
|
await this.closeRuntime(open2.request.sessionId);
|
|
26531
|
+
this.assertUnsubscribeSettled(open2.request.sessionId);
|
|
26297
26532
|
}
|
|
26298
26533
|
const mcpServers = request.mcpServers ?? [];
|
|
26299
|
-
|
|
26534
|
+
let accountVersion = this.accountGeneration;
|
|
26535
|
+
const account = await this.withCodex(() => this.codex.accountRead({ refreshToken: false }));
|
|
26536
|
+
if (this.providers.active === null && account.requiresOpenaiAuth && account.account === null) {
|
|
26300
26537
|
throw RequestError.authRequired(undefined, "Log in to Codex first (auth/login)");
|
|
26301
26538
|
}
|
|
26302
|
-
await this.withCodex(() => this.refreshSkills(request.cwd, additionalDirectories));
|
|
26303
26539
|
const existingMcp = mcpServers.length > 0 ? await this.withCodex(() => this.configuredMcpServerNames(request.cwd)) : new Set;
|
|
26304
26540
|
const config2 = buildThreadConfig(this.providers.threadConfig(), request.cwd, additionalDirectories, mcpServers, existingMcp);
|
|
26305
26541
|
const mcpStartupGeneration = this.codex.mcpStartupGeneration;
|
|
26306
26542
|
const modelProvider = await this.withCodex(() => this.resolveModelProvider());
|
|
26307
|
-
const thread = await this.withCodex(async () => {
|
|
26543
|
+
const { thread, skills } = await this.withSkillsContext(request.cwd, additionalDirectories, async (skills2) => ({ skills: skills2, thread: await this.withCodex(async () => {
|
|
26544
|
+
if (signal?.aborted)
|
|
26545
|
+
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
26308
26546
|
switch (open2.kind) {
|
|
26309
26547
|
case "new":
|
|
26310
26548
|
return await this.codex.threadStart({ config: config2, cwd: request.cwd, modelProvider });
|
|
@@ -26313,13 +26551,32 @@ class CodexAgent {
|
|
|
26313
26551
|
case "fork":
|
|
26314
26552
|
return await this.codex.threadFork({ threadId: open2.request.sessionId, config: config2, cwd: request.cwd, modelProvider, excludeTurns: true });
|
|
26315
26553
|
}
|
|
26316
|
-
});
|
|
26554
|
+
}) }));
|
|
26317
26555
|
const sessionId = thread.thread.id;
|
|
26556
|
+
let removeCancellation = () => {};
|
|
26557
|
+
const openingCompletions = new Map;
|
|
26558
|
+
const openingStarts = new Set;
|
|
26559
|
+
const stopObservingOpen = this.codex.observeNotifications((notification) => {
|
|
26560
|
+
if (notification.method === "turn/completed" && notification.params.threadId === sessionId) {
|
|
26561
|
+
if (openingCompletions.size >= 32)
|
|
26562
|
+
openingCompletions.delete(openingCompletions.keys().next().value);
|
|
26563
|
+
openingCompletions.set(notification.params.turn.id, notification.params);
|
|
26564
|
+
}
|
|
26565
|
+
if (notification.method === "turn/started" && notification.params.threadId === sessionId && this.sessions.has(sessionId)) {
|
|
26566
|
+
if (openingStarts.size >= 32)
|
|
26567
|
+
openingStarts.delete(openingStarts.values().next().value);
|
|
26568
|
+
openingStarts.add(notification.params.turn.id);
|
|
26569
|
+
}
|
|
26570
|
+
});
|
|
26318
26571
|
try {
|
|
26319
|
-
|
|
26320
|
-
|
|
26321
|
-
|
|
26322
|
-
|
|
26572
|
+
if (signal?.aborted)
|
|
26573
|
+
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
26574
|
+
const codexCatalog = await this.withCodex(() => this.codex.allModels());
|
|
26575
|
+
let openedAccount = account.account;
|
|
26576
|
+
while (accountVersion !== this.accountGeneration) {
|
|
26577
|
+
accountVersion = this.accountGeneration;
|
|
26578
|
+
openedAccount = (await this.withCodex(() => this.codex.accountRead({ refreshToken: false }))).account;
|
|
26579
|
+
}
|
|
26323
26580
|
const gateway = this.providers.active;
|
|
26324
26581
|
const catalog = this.providers.catalog(codexCatalog);
|
|
26325
26582
|
const model = resolveModelSelection(catalog, gateway?.model ?? thread.model, thread.reasoningEffort);
|
|
@@ -26333,7 +26590,7 @@ class CodexAgent {
|
|
|
26333
26590
|
mode: initialAgentMode(this.env),
|
|
26334
26591
|
collaborationMode: DEFAULT_COLLABORATION_MODE,
|
|
26335
26592
|
fastMode: thread.serviceTier === FAST_SERVICE_TIER,
|
|
26336
|
-
account:
|
|
26593
|
+
account: openedAccount,
|
|
26337
26594
|
title: null,
|
|
26338
26595
|
titleIsExplicit: false,
|
|
26339
26596
|
activeTurn: null,
|
|
@@ -26341,7 +26598,16 @@ class CodexAgent {
|
|
|
26341
26598
|
contextWindow: null,
|
|
26342
26599
|
closed: false
|
|
26343
26600
|
};
|
|
26344
|
-
const runtime = this.installRuntime(session, capabilities);
|
|
26601
|
+
const runtime = this.installRuntime(session, capabilities, config2, thread.modelProvider);
|
|
26602
|
+
const cancelOpening = () => {
|
|
26603
|
+
runtime.session.closed = true;
|
|
26604
|
+
runtime.lifetime.abort();
|
|
26605
|
+
runtime.client.dispose();
|
|
26606
|
+
};
|
|
26607
|
+
signal?.addEventListener("abort", cancelOpening, { once: true });
|
|
26608
|
+
removeCancellation = () => signal?.removeEventListener("abort", cancelOpening);
|
|
26609
|
+
if (signal?.aborted)
|
|
26610
|
+
cancelOpening();
|
|
26345
26611
|
const replay = open2.kind === "fork" || open2.kind === "resume" && open2.request.replayFrom?.type === "start";
|
|
26346
26612
|
if (open2.kind !== "new") {
|
|
26347
26613
|
await this.replayHistory(runtime, replay, thread.thread);
|
|
@@ -26350,9 +26616,17 @@ class CodexAgent {
|
|
|
26350
26616
|
await this.withCodex(() => this.codex.threadInjectItems({ threadId: sessionId, items: seed.map(seedItem) }));
|
|
26351
26617
|
}
|
|
26352
26618
|
}
|
|
26619
|
+
if (thread.thread.status.type === "active" && runtime.session.activeTurn === null) {
|
|
26620
|
+
const latest = await this.withCodex(() => this.codex.threadTurnsList({ threadId: session.id, limit: 1, sortDirection: "desc", itemsView: "full" }));
|
|
26621
|
+
const active = latest.data.find((turn) => turn.status === "inProgress");
|
|
26622
|
+
if (active && !(openingStarts.has(active.id) && openingCompletions.has(active.id))) {
|
|
26623
|
+
const completed = openingCompletions.get(active.id);
|
|
26624
|
+
this.observeForeignTurn(runtime, active.id, completed ? Promise.resolve(completed) : undefined, active.items);
|
|
26625
|
+
}
|
|
26626
|
+
}
|
|
26353
26627
|
if (mcpServers.length > 0)
|
|
26354
26628
|
this.reportMcpStartup(runtime, mcpStartupGeneration);
|
|
26355
|
-
this.publishAvailableCommands(runtime);
|
|
26629
|
+
this.publishAvailableCommands(runtime, skills);
|
|
26356
26630
|
return runtime;
|
|
26357
26631
|
} catch (error40) {
|
|
26358
26632
|
const runtime = this.sessions.get(sessionId);
|
|
@@ -26364,23 +26638,26 @@ class CodexAgent {
|
|
|
26364
26638
|
}
|
|
26365
26639
|
this.sessions.delete(sessionId);
|
|
26366
26640
|
this.codex.detachThread(sessionId);
|
|
26367
|
-
await this.
|
|
26641
|
+
await within(this.unsubscribeThread(sessionId), this.closeGraceMs).catch(() => {});
|
|
26368
26642
|
throw error40;
|
|
26643
|
+
} finally {
|
|
26644
|
+
removeCancellation();
|
|
26645
|
+
stopObservingOpen();
|
|
26369
26646
|
}
|
|
26370
26647
|
}
|
|
26371
|
-
installRuntime(session, capabilities) {
|
|
26648
|
+
installRuntime(session, capabilities, config2, modelProvider) {
|
|
26372
26649
|
const client = new ClientSession(session.id, this.link, capabilities);
|
|
26373
26650
|
const bridge = new EventBridge(client, session);
|
|
26374
26651
|
const turnContext = new TurnContext(session.id);
|
|
26375
26652
|
const signal = () => {
|
|
26376
26653
|
const turn = session.activeTurn;
|
|
26377
|
-
return
|
|
26654
|
+
return AbortSignal.any([this.codex.disconnectSignal, ...turn ? [turn.abort.signal, turn.stop.signal] : []]);
|
|
26378
26655
|
};
|
|
26379
26656
|
const approval = new CodexApprovalHandler(client, turnContext, signal);
|
|
26380
26657
|
const elicitation = new CodexElicitationHandler(client, turnContext, signal);
|
|
26381
|
-
const runtime = { session, client, bridge, turnContext, elicitation, lifetime: new AbortController, queue: Promise.resolve() };
|
|
26658
|
+
const runtime = { config: config2, modelProvider, stale: false, session, client, bridge, turnContext, elicitation, lifetime: new AbortController, queue: Promise.resolve() };
|
|
26382
26659
|
const drained = (operation) => async (params) => {
|
|
26383
|
-
await this.drain(runtime);
|
|
26660
|
+
await abortable(this.drain(runtime), this.codex.disconnectSignal);
|
|
26384
26661
|
return await operation(params);
|
|
26385
26662
|
};
|
|
26386
26663
|
this.codex.attachThread(session.id, {
|
|
@@ -26399,7 +26676,23 @@ class CodexAgent {
|
|
|
26399
26676
|
return runtime;
|
|
26400
26677
|
}
|
|
26401
26678
|
enqueue(runtime, notification) {
|
|
26679
|
+
if (runtime.lifetime.signal.aborted)
|
|
26680
|
+
return;
|
|
26681
|
+
if (notification.method === "turn/started" && runtime.session.activeTurn === null) {
|
|
26682
|
+
this.observeForeignTurn(runtime, notification.params.turn.id);
|
|
26683
|
+
}
|
|
26684
|
+
const turn = runtime.session.activeTurn;
|
|
26685
|
+
if (notification.method.startsWith("item/") && "turnId" in notification.params && typeof notification.params.turnId === "string") {
|
|
26686
|
+
if (!turn || turn.turnId !== null && notification.params.turnId !== turn.turnId)
|
|
26687
|
+
return;
|
|
26688
|
+
}
|
|
26689
|
+
if (turn && notification.method === "turn/completed")
|
|
26690
|
+
this.completedTurns.set(turn, notification.params.turn.id);
|
|
26691
|
+
if (runtime.lifetime.signal.aborted)
|
|
26692
|
+
return;
|
|
26402
26693
|
const run = async () => {
|
|
26694
|
+
if (runtime.lifetime.signal.aborted)
|
|
26695
|
+
return;
|
|
26403
26696
|
try {
|
|
26404
26697
|
runtime.turnContext.observe(notification);
|
|
26405
26698
|
await runtime.elicitation.observe(notification);
|
|
@@ -26410,11 +26703,21 @@ class CodexAgent {
|
|
|
26410
26703
|
};
|
|
26411
26704
|
runtime.queue = runtime.queue.then(run, run);
|
|
26412
26705
|
}
|
|
26706
|
+
observeForeignTurn(runtime, turnId, completion, items = []) {
|
|
26707
|
+
if (runtime.session.activeTurn || runtime.session.closed)
|
|
26708
|
+
return;
|
|
26709
|
+
const turn = createActiveTurn(runtime.session.id);
|
|
26710
|
+
runtime.session.activeTurn = turn;
|
|
26711
|
+
this.turnStarted(runtime, turn, turnId, runtime.session.id);
|
|
26712
|
+
runtime.client.reportRunning();
|
|
26713
|
+
const completed = completion ?? this.codex.awaitTurnCompleted(runtime.session.id, turnId, turn.stop.signal);
|
|
26714
|
+
this.runPrompt(runtime, turn, { sessionId: runtime.session.id, prompt: [] }, completed, items);
|
|
26715
|
+
}
|
|
26413
26716
|
async drain(runtime) {
|
|
26414
26717
|
let current;
|
|
26415
26718
|
do {
|
|
26416
26719
|
current = runtime.queue;
|
|
26417
|
-
await current;
|
|
26720
|
+
await abortable(current, runtime.client.signal);
|
|
26418
26721
|
} while (runtime.queue !== current);
|
|
26419
26722
|
}
|
|
26420
26723
|
async replayHistory(runtime, replay, thread) {
|
|
@@ -26437,12 +26740,18 @@ class CodexAgent {
|
|
|
26437
26740
|
await publishTitle(firstPage.data);
|
|
26438
26741
|
return;
|
|
26439
26742
|
}
|
|
26743
|
+
const cursors = new Set;
|
|
26440
26744
|
let cursor = null;
|
|
26441
26745
|
do {
|
|
26442
26746
|
const page = await this.turnPage(session.id, cursor);
|
|
26443
26747
|
await publishTitle(page.data);
|
|
26444
26748
|
await client.updateAll(historyUpdates(page.data));
|
|
26445
26749
|
cursor = page.nextCursor;
|
|
26750
|
+
if (cursor !== null) {
|
|
26751
|
+
if (cursors.has(cursor))
|
|
26752
|
+
throw RequestError.internalError({ sessionId: session.id }, "Codex history pagination repeated a cursor; retry after restarting Codex");
|
|
26753
|
+
cursors.add(cursor);
|
|
26754
|
+
}
|
|
26446
26755
|
} while (cursor !== null && !session.closed);
|
|
26447
26756
|
await publishTitle([]);
|
|
26448
26757
|
}
|
|
@@ -26467,12 +26776,13 @@ class CodexAgent {
|
|
|
26467
26776
|
logger.error("MCP startup reporting failed", error40, { sessionId: runtime.session.id });
|
|
26468
26777
|
}
|
|
26469
26778
|
}
|
|
26470
|
-
async publishAvailableCommands(runtime) {
|
|
26779
|
+
async publishAvailableCommands(runtime, snapshot) {
|
|
26471
26780
|
try {
|
|
26472
|
-
const skills = await this.codex.skillsList({ cwds: [runtime.session.cwd, ...runtime.session.additionalDirectories] });
|
|
26781
|
+
const skills = snapshot ?? await this.codex.skillsList({ cwds: [runtime.session.cwd, ...runtime.session.additionalDirectories] });
|
|
26473
26782
|
if (runtime.session.closed)
|
|
26474
26783
|
return;
|
|
26475
26784
|
await runtime.client.update({ sessionUpdate: "available_commands_update", availableCommands: availableCommands(skills.data) });
|
|
26785
|
+
this.publishedSkills.set(runtime, skills);
|
|
26476
26786
|
} catch (error40) {
|
|
26477
26787
|
logger.error("publishing available commands failed", error40, { sessionId: runtime.session.id });
|
|
26478
26788
|
}
|
|
@@ -26499,58 +26809,92 @@ class CodexAgent {
|
|
|
26499
26809
|
};
|
|
26500
26810
|
}
|
|
26501
26811
|
async closeSession(params) {
|
|
26812
|
+
return await this.admission(() => this.closeSessionInternal(params), params.sessionId);
|
|
26813
|
+
}
|
|
26814
|
+
async closeSessionInternal(params) {
|
|
26502
26815
|
this.requireInitialized("session/close");
|
|
26503
|
-
await this.closeRuntime(params.sessionId);
|
|
26504
|
-
return {};
|
|
26816
|
+
return await this.closeRuntime(params.sessionId);
|
|
26505
26817
|
}
|
|
26506
26818
|
async deleteSession(params) {
|
|
26819
|
+
return await this.admission(() => this.deleteSessionInternal(params), params.sessionId);
|
|
26820
|
+
}
|
|
26821
|
+
async deleteSessionInternal(params) {
|
|
26507
26822
|
this.requireInitialized("session/delete");
|
|
26508
26823
|
await this.closeRuntime(params.sessionId);
|
|
26509
26824
|
await this.withCodex(() => this.codex.threadDelete({ threadId: params.sessionId }));
|
|
26510
26825
|
return {};
|
|
26511
26826
|
}
|
|
26512
26827
|
async archiveSession(params) {
|
|
26828
|
+
return await this.admission(() => this.archiveSessionInternal(params), params.sessionId);
|
|
26829
|
+
}
|
|
26830
|
+
async archiveSessionInternal(params) {
|
|
26513
26831
|
this.requireInitialized("_codex/session_archive");
|
|
26514
26832
|
await this.closeRuntime(params.sessionId);
|
|
26515
26833
|
await this.withCodex(() => this.codex.threadArchive({ threadId: params.sessionId }));
|
|
26516
26834
|
return {};
|
|
26517
26835
|
}
|
|
26518
26836
|
async unarchiveSession(params) {
|
|
26837
|
+
return await this.admission(() => this.unarchiveSessionInternal(params), params.sessionId);
|
|
26838
|
+
}
|
|
26839
|
+
async unarchiveSessionInternal(params) {
|
|
26519
26840
|
this.requireInitialized("_codex/session_unarchive");
|
|
26520
26841
|
await this.withCodex(() => this.codex.threadUnarchive({ threadId: params.sessionId }));
|
|
26521
26842
|
return {};
|
|
26522
26843
|
}
|
|
26523
26844
|
async closeRuntime(sessionId) {
|
|
26845
|
+
const deadline = performance.now() + this.closeGraceMs;
|
|
26524
26846
|
const runtime = this.sessions.get(sessionId);
|
|
26525
26847
|
if (!runtime)
|
|
26526
|
-
return;
|
|
26848
|
+
return {};
|
|
26527
26849
|
runtime.session.closed = true;
|
|
26528
26850
|
runtime.lifetime.abort();
|
|
26529
26851
|
const turn = runtime.session.activeTurn;
|
|
26530
26852
|
if (turn) {
|
|
26531
26853
|
this.interruptTurn(runtime, turn);
|
|
26532
|
-
|
|
26533
|
-
|
|
26534
|
-
|
|
26535
|
-
});
|
|
26536
|
-
try {
|
|
26537
|
-
if (await Promise.race([turn.finished.then(() => "finished"), timeout]) === "timeout") {
|
|
26538
|
-
turn.stop.abort();
|
|
26539
|
-
await turn.finished;
|
|
26540
|
-
}
|
|
26541
|
-
} finally {
|
|
26542
|
-
clearTimeout(timer);
|
|
26854
|
+
await within(turn.finished, Math.max(0, this.closeGraceMs / 2));
|
|
26855
|
+
if (runtime.session.activeTurn === turn) {
|
|
26856
|
+
await within(this.reportIdle(runtime, turn, "cancelled", { usage: usageOf(runtime.session) }), Math.max(0, (deadline - performance.now()) / 2)).catch(() => {});
|
|
26543
26857
|
}
|
|
26858
|
+
turn.stop.abort();
|
|
26544
26859
|
}
|
|
26545
|
-
this.sessions.
|
|
26860
|
+
if (this.sessions.get(sessionId) === runtime)
|
|
26861
|
+
this.sessions.delete(sessionId);
|
|
26546
26862
|
this.codex.detachThread(sessionId);
|
|
26547
|
-
runtime.bridge.dispose();
|
|
26548
26863
|
runtime.client.dispose();
|
|
26549
|
-
|
|
26864
|
+
runtime.bridge.dispose();
|
|
26865
|
+
if (turn) {
|
|
26866
|
+
turn.resolveStarted(null);
|
|
26867
|
+
runtime.session.activeTurn = null;
|
|
26868
|
+
turn.resolveFinished();
|
|
26869
|
+
}
|
|
26870
|
+
const unsubscribe = this.unsubscribeThread(sessionId);
|
|
26871
|
+
let remoteUnsubscribe = "confirmed";
|
|
26872
|
+
try {
|
|
26873
|
+
if (!await within(unsubscribe, Math.max(0, deadline - performance.now()))) {
|
|
26874
|
+
remoteUnsubscribe = "timed_out";
|
|
26875
|
+
}
|
|
26876
|
+
} catch (error40) {
|
|
26877
|
+
remoteUnsubscribe = "failed";
|
|
26550
26878
|
logger.error("thread/unsubscribe failed", error40, { sessionId });
|
|
26551
|
-
}
|
|
26879
|
+
}
|
|
26880
|
+
if (remoteUnsubscribe !== "confirmed")
|
|
26881
|
+
logger.log("session close detached locally", { sessionId, remoteUnsubscribe });
|
|
26882
|
+
return { _meta: { codex: { close: { attempted: true, localDetached: true, remoteUnsubscribe } } } };
|
|
26883
|
+
}
|
|
26884
|
+
unsubscribeThread(sessionId) {
|
|
26885
|
+
const unsubscribe = this.codex.threadUnsubscribe({ threadId: sessionId });
|
|
26886
|
+
this.pendingUnsubscribes.set(sessionId, unsubscribe);
|
|
26887
|
+
const release = () => {
|
|
26888
|
+
if (this.pendingUnsubscribes.get(sessionId) === unsubscribe)
|
|
26889
|
+
this.pendingUnsubscribes.delete(sessionId);
|
|
26890
|
+
};
|
|
26891
|
+
unsubscribe.then(release, release);
|
|
26892
|
+
return unsubscribe;
|
|
26552
26893
|
}
|
|
26553
26894
|
async setSessionConfigOption(params) {
|
|
26895
|
+
return await this.admission(() => this.setSessionConfigOptionInternal(params), params.sessionId);
|
|
26896
|
+
}
|
|
26897
|
+
async setSessionConfigOptionInternal(params) {
|
|
26554
26898
|
const runtime = this.runtime(params.sessionId, "session/set_config_option");
|
|
26555
26899
|
await this.withCodex(() => applyConfigOption(runtime.session, this.codex, params));
|
|
26556
26900
|
const configOptions = sessionConfigOptions(runtime.session);
|
|
@@ -26565,7 +26909,12 @@ class CodexAgent {
|
|
|
26565
26909
|
return runtime;
|
|
26566
26910
|
}
|
|
26567
26911
|
async prompt(params) {
|
|
26912
|
+
this.assertRoutingAvailable();
|
|
26913
|
+
if (this.sessionMutations.has(params.sessionId))
|
|
26914
|
+
throw RequestError.invalidRequest({ sessionId: params.sessionId }, "Session lifecycle or configuration work is in progress; retry when it finishes");
|
|
26568
26915
|
const runtime = this.runtime(params.sessionId, "session/prompt");
|
|
26916
|
+
if (runtime.stale)
|
|
26917
|
+
throw RequestError.invalidRequest({ sessionId: params.sessionId, _meta: { codex: { routing: { stale: true } } } }, "Session routing is stale; resume the session or successfully change providers before prompting");
|
|
26569
26918
|
const { session } = runtime;
|
|
26570
26919
|
if (!Array.isArray(params.prompt) || params.prompt.length === 0) {
|
|
26571
26920
|
throw RequestError.invalidParams(undefined, "prompt must contain at least one content block");
|
|
@@ -26592,23 +26941,28 @@ class CodexAgent {
|
|
|
26592
26941
|
await this.codex.turnSteer({ threadId: runtime.session.id, expectedTurnId: turnId, input: toUserInput(params.prompt) });
|
|
26593
26942
|
return { _meta: { codex: { steered: turnId } } };
|
|
26594
26943
|
} catch (error40) {
|
|
26944
|
+
if (this.completedTurns.get(turn) === turnId || runtime.session.activeTurn !== turn) {
|
|
26945
|
+
await turn.finished;
|
|
26946
|
+
return await this.prompt(params);
|
|
26947
|
+
}
|
|
26595
26948
|
throw RequestError.invalidRequest({ sessionId: params.sessionId, turnId }, `Could not steer the running turn: ${errorMessage(error40)}`);
|
|
26596
26949
|
}
|
|
26597
26950
|
}
|
|
26598
|
-
async runPrompt(runtime, turn, params) {
|
|
26951
|
+
async runPrompt(runtime, turn, params, observed, items = []) {
|
|
26599
26952
|
const { session, bridge } = runtime;
|
|
26600
26953
|
bridge.beginTurn();
|
|
26954
|
+
if (items.length)
|
|
26955
|
+
runtime.queue = runtime.queue.then(() => bridge.restore(items));
|
|
26601
26956
|
try {
|
|
26602
26957
|
const command = parseCommand(params.prompt);
|
|
26603
26958
|
const outcome = command ? resolveCommand(command, session) : { kind: "prompt" };
|
|
26604
|
-
let completed = await abortable(this.executePrompt(runtime, turn, command, outcome, params), turn.stop.signal);
|
|
26959
|
+
let completed = await abortable(observed ?? this.executePrompt(runtime, turn, command, outcome, params), turn.stop.signal);
|
|
26605
26960
|
await this.drain(runtime);
|
|
26606
26961
|
await bridge.flush();
|
|
26607
|
-
if (completed?.turn.status === "completed" && !turn.abort.signal.aborted) {
|
|
26962
|
+
if (completed?.turn.status === "completed" && !turn.abort.signal.aborted && !this.exitHandled) {
|
|
26608
26963
|
completed = await abortable(this.maybeImplementPlan(runtime, turn, completed), turn.stop.signal);
|
|
26609
26964
|
}
|
|
26610
26965
|
if (completed?.turn.status === "interrupted" || turn.abort.signal.aborted) {
|
|
26611
|
-
await bridge.cancelOpenToolCalls();
|
|
26612
26966
|
await this.reportIdle(runtime, turn, "cancelled", { usage: usageOf(session) });
|
|
26613
26967
|
return;
|
|
26614
26968
|
}
|
|
@@ -26624,8 +26978,13 @@ class CodexAgent {
|
|
|
26624
26978
|
await this.publishFallbackTitle(runtime, promptTitle(params.prompt));
|
|
26625
26979
|
await this.reportIdle(runtime, turn, "end_turn", { usage: usageOf(session) });
|
|
26626
26980
|
} catch (error40) {
|
|
26981
|
+
if (this.terminalTurns.has(turn)) {
|
|
26982
|
+
logger.error("Terminal update failed", error40, { sessionId: session.id });
|
|
26983
|
+
return;
|
|
26984
|
+
}
|
|
26985
|
+
if (turn.stop.signal.aborted && session.closed)
|
|
26986
|
+
return;
|
|
26627
26987
|
if (turn.abort.signal.aborted || session.closed) {
|
|
26628
|
-
await bridge.cancelOpenToolCalls().catch(() => {});
|
|
26629
26988
|
await this.reportIdle(runtime, turn, "cancelled", { usage: usageOf(session) }).catch(() => {});
|
|
26630
26989
|
return;
|
|
26631
26990
|
}
|
|
@@ -26671,23 +27030,42 @@ class CodexAgent {
|
|
|
26671
27030
|
const { session } = runtime;
|
|
26672
27031
|
const model = findModel(session.catalog, session.model.model);
|
|
26673
27032
|
const disableSummary = session.account?.type === "apiKey" || modelLacksReasoning(model);
|
|
26674
|
-
|
|
26675
|
-
|
|
26676
|
-
|
|
26677
|
-
|
|
26678
|
-
|
|
26679
|
-
|
|
26680
|
-
|
|
26681
|
-
|
|
26682
|
-
|
|
26683
|
-
|
|
26684
|
-
|
|
26685
|
-
|
|
26686
|
-
|
|
26687
|
-
|
|
26688
|
-
|
|
26689
|
-
|
|
26690
|
-
|
|
27033
|
+
let startSent = false;
|
|
27034
|
+
const beforeStart = new AbortController;
|
|
27035
|
+
const cancelBeforeStart = () => {
|
|
27036
|
+
if (!startSent)
|
|
27037
|
+
beforeStart.abort(turn.abort.signal.reason);
|
|
27038
|
+
};
|
|
27039
|
+
turn.abort.signal.addEventListener("abort", cancelBeforeStart, { once: true });
|
|
27040
|
+
if (turn.abort.signal.aborted)
|
|
27041
|
+
cancelBeforeStart();
|
|
27042
|
+
try {
|
|
27043
|
+
const result = await abortable(this.withSkillsContext(session.cwd, session.additionalDirectories, async (skills) => {
|
|
27044
|
+
if (this.publishedSkills.get(runtime) !== skills)
|
|
27045
|
+
this.publishAvailableCommands(runtime, skills);
|
|
27046
|
+
if (turn.abort.signal.aborted || turn.stop.signal.aborted)
|
|
27047
|
+
return { completed: Promise.resolve(interruptedTurn(session.id)) };
|
|
27048
|
+
startSent = true;
|
|
27049
|
+
const completed = this.withCodex(() => this.codex.runTurn({
|
|
27050
|
+
threadId: session.id,
|
|
27051
|
+
input: toUserInput(prompt),
|
|
27052
|
+
approvalPolicy: session.mode.approvalPolicy,
|
|
27053
|
+
approvalsReviewer: session.mode.approvalsReviewer,
|
|
27054
|
+
sandboxPolicy: withWritableRoots(session.mode.sandboxPolicy, session.additionalDirectories),
|
|
27055
|
+
model: session.model.model,
|
|
27056
|
+
effort: session.model.effort,
|
|
27057
|
+
summary: disableSummary ? "none" : "auto",
|
|
27058
|
+
serviceTier: session.fastMode ? FAST_SERVICE_TIER : null
|
|
27059
|
+
}, (turnId) => {
|
|
27060
|
+
this.turnStarted(runtime, turn, turnId, session.id);
|
|
27061
|
+
}, turn.stop.signal));
|
|
27062
|
+
await Promise.race([turn.started, completed]);
|
|
27063
|
+
return { completed };
|
|
27064
|
+
}), beforeStart.signal);
|
|
27065
|
+
return await result.completed;
|
|
27066
|
+
} finally {
|
|
27067
|
+
turn.abort.signal.removeEventListener("abort", cancelBeforeStart);
|
|
27068
|
+
}
|
|
26691
27069
|
}
|
|
26692
27070
|
async runCompaction(runtime, turn) {
|
|
26693
27071
|
const threadId = runtime.session.id;
|
|
@@ -26711,6 +27089,7 @@ class CodexAgent {
|
|
|
26711
27089
|
const plan = runtime.bridge.takeCompletedPlan();
|
|
26712
27090
|
if (!plan || runtime.session.collaborationMode !== PLAN_COLLABORATION_MODE)
|
|
26713
27091
|
return completed;
|
|
27092
|
+
this.completedTurns.delete(turn);
|
|
26714
27093
|
const signal = AbortSignal.any([turn.abort.signal, turn.stop.signal]);
|
|
26715
27094
|
const approved = await this.requestPlanApproval(runtime, plan, signal);
|
|
26716
27095
|
if (!approved || signal.aborted)
|
|
@@ -26766,11 +27145,16 @@ class CodexAgent {
|
|
|
26766
27145
|
}
|
|
26767
27146
|
}
|
|
26768
27147
|
async reportIdle(runtime, turn, reason, extra) {
|
|
27148
|
+
if (this.terminalTurns.has(turn))
|
|
27149
|
+
return;
|
|
27150
|
+
this.terminalTurns.add(turn);
|
|
27151
|
+
await runtime.bridge.finishOpenToolCalls(reason === "cancelled" ? "cancelled" : reason === "end_turn" ? "completed" : "failed");
|
|
26769
27152
|
if (runtime.session.activeTurn === turn)
|
|
26770
27153
|
runtime.session.activeTurn = null;
|
|
26771
27154
|
await runtime.client.reportIdle(reason, extra);
|
|
26772
27155
|
}
|
|
26773
27156
|
async reportTurnFailure(runtime, turn, error40) {
|
|
27157
|
+
const { stopReason, ...classification } = classifyTurnError(error40.codexErrorInfo);
|
|
26774
27158
|
const message = error40.additionalDetails ? `${error40.message}
|
|
26775
27159
|
|
|
26776
27160
|
${error40.additionalDetails}` : error40.message;
|
|
@@ -26778,11 +27162,11 @@ ${error40.additionalDetails}` : error40.message;
|
|
|
26778
27162
|
sessionUpdate: "agent_message_chunk",
|
|
26779
27163
|
messageId: `codex-error:${runtime.session.id}:${Date.now()}`,
|
|
26780
27164
|
content: { type: "text", text: message },
|
|
26781
|
-
_meta: { codex: { error: { message: error40.message, codexErrorInfo: error40.codexErrorInfo, additionalDetails: error40.additionalDetails } } }
|
|
27165
|
+
_meta: { codex: { error: { ...classification, message: error40.message, codexErrorInfo: error40.codexErrorInfo, additionalDetails: error40.additionalDetails } } }
|
|
26782
27166
|
});
|
|
26783
|
-
await this.reportIdle(runtime, turn,
|
|
27167
|
+
await this.reportIdle(runtime, turn, stopReason, {
|
|
26784
27168
|
usage: usageOf(runtime.session),
|
|
26785
|
-
_meta: { codex: { error: { message: error40.message, codexErrorInfo: error40.codexErrorInfo } } }
|
|
27169
|
+
_meta: { codex: { error: { ...classification, message: error40.message, codexErrorInfo: error40.codexErrorInfo } } }
|
|
26786
27170
|
});
|
|
26787
27171
|
}
|
|
26788
27172
|
async publishFallbackTitle(runtime, title) {
|
|
@@ -26811,6 +27195,7 @@ ${error40.additionalDetails}` : error40.message;
|
|
|
26811
27195
|
let pending = turn.interrupts.get(key);
|
|
26812
27196
|
if (!pending) {
|
|
26813
27197
|
pending = this.codex.turnInterrupt({ threadId: turn.threadId, turnId }).then(() => {}, (error40) => {
|
|
27198
|
+
turn.interrupts.delete(key);
|
|
26814
27199
|
logger.error("turn/interrupt failed", error40, { sessionId: runtime.session.id, turnId });
|
|
26815
27200
|
});
|
|
26816
27201
|
turn.interrupts.set(key, pending);
|
|
@@ -26824,13 +27209,56 @@ ${error40.additionalDetails}` : error40.message;
|
|
|
26824
27209
|
if (turn.abort.signal.aborted)
|
|
26825
27210
|
this.sendInterrupt(runtime, turn, turnId);
|
|
26826
27211
|
}
|
|
26827
|
-
|
|
26828
|
-
const
|
|
26829
|
-
|
|
26830
|
-
|
|
26831
|
-
|
|
27212
|
+
withSkillsContext(cwd, additionalDirectories, operation) {
|
|
27213
|
+
const run = async () => {
|
|
27214
|
+
const roots = additionalDirectories.map((root) => path6.join(root, ".agents", "skills"));
|
|
27215
|
+
if (roots.length !== this.skillRoots.length || roots.some((root, index) => root !== this.skillRoots[index])) {
|
|
27216
|
+
this.changingSkillRoots = true;
|
|
27217
|
+
try {
|
|
27218
|
+
await this.codex.skillsExtraRootsSet({ extraRoots: roots });
|
|
27219
|
+
} finally {
|
|
27220
|
+
this.changingSkillRoots = false;
|
|
27221
|
+
}
|
|
27222
|
+
this.skillRoots = roots;
|
|
27223
|
+
this.skillSnapshots.clear();
|
|
27224
|
+
}
|
|
27225
|
+
const skills = await this.loadSkills(cwd, additionalDirectories);
|
|
27226
|
+
return await operation(skills);
|
|
27227
|
+
};
|
|
27228
|
+
const result = this.skillsQueue.then(run, run);
|
|
27229
|
+
this.skillsQueue = result.then(() => {}, () => {});
|
|
27230
|
+
return result;
|
|
27231
|
+
}
|
|
27232
|
+
async loadSkills(cwd, additionalDirectories) {
|
|
27233
|
+
const key = JSON.stringify([cwd, ...additionalDirectories]);
|
|
27234
|
+
let skills = this.skillSnapshots.get(key);
|
|
27235
|
+
if (!skills) {
|
|
27236
|
+
const generation = this.skillsGeneration;
|
|
27237
|
+
skills = await this.withCodex(() => this.codex.skillsList({ cwds: [cwd, ...additionalDirectories], forceReload: true }));
|
|
27238
|
+
if (generation === this.skillsGeneration) {
|
|
27239
|
+
if (this.skillSnapshots.size >= 64)
|
|
27240
|
+
this.skillSnapshots.delete(this.skillSnapshots.keys().next().value);
|
|
27241
|
+
this.skillSnapshots.set(key, skills);
|
|
27242
|
+
}
|
|
27243
|
+
}
|
|
27244
|
+
return skills;
|
|
27245
|
+
}
|
|
27246
|
+
async refreshAvailableCommands(runtime) {
|
|
27247
|
+
const run = async () => {
|
|
27248
|
+
const roots = runtime.session.additionalDirectories.map((root) => path6.join(root, ".agents", "skills"));
|
|
27249
|
+
if (runtime.session.closed || roots.length !== this.skillRoots.length || roots.some((root, i) => root !== this.skillRoots[i]))
|
|
27250
|
+
return;
|
|
27251
|
+
return await this.loadSkills(runtime.session.cwd, runtime.session.additionalDirectories);
|
|
27252
|
+
};
|
|
27253
|
+
const pending = this.skillsQueue.then(run, run);
|
|
27254
|
+
this.skillsQueue = pending.then(() => {}, () => {});
|
|
27255
|
+
try {
|
|
27256
|
+
const skills = await pending;
|
|
27257
|
+
if (skills && !runtime.session.closed)
|
|
27258
|
+
await this.publishAvailableCommands(runtime, skills);
|
|
27259
|
+
} catch (error40) {
|
|
27260
|
+
logger.error("refreshing available commands failed", error40, { sessionId: runtime.session.id });
|
|
26832
27261
|
}
|
|
26833
|
-
await this.codex.skillsList({ cwds: [cwd, ...additionalDirectories], forceReload: true });
|
|
26834
27262
|
}
|
|
26835
27263
|
async configuredMcpServerNames(cwd) {
|
|
26836
27264
|
const response = await this.codex.configRead({ includeLayers: true, cwd });
|
|
@@ -26843,13 +27271,13 @@ ${error40.additionalDetails}` : error40.message;
|
|
|
26843
27271
|
}
|
|
26844
27272
|
return names;
|
|
26845
27273
|
}
|
|
26846
|
-
async resolveModelProvider() {
|
|
26847
|
-
const routed =
|
|
27274
|
+
async resolveModelProvider(routing = this.providers) {
|
|
27275
|
+
const routed = routing.modelProvider();
|
|
26848
27276
|
if (routed)
|
|
26849
27277
|
return routed;
|
|
26850
27278
|
const config2 = await this.codex.configRead({ includeLayers: false });
|
|
26851
27279
|
const provider = config2.config["model_provider"];
|
|
26852
|
-
return typeof provider === "string" && provider.length > 0 ? provider :
|
|
27280
|
+
return typeof provider === "string" && provider.length > 0 ? provider : OPENAI_PROVIDER_ID;
|
|
26853
27281
|
}
|
|
26854
27282
|
async withCodex(operation) {
|
|
26855
27283
|
try {
|
|
@@ -26889,7 +27317,7 @@ ${stderr}` : ""}`);
|
|
|
26889
27317
|
this.exitHandled = true;
|
|
26890
27318
|
for (const runtime of this.sessions.values()) {
|
|
26891
27319
|
const turn = runtime.session.activeTurn;
|
|
26892
|
-
if (!turn)
|
|
27320
|
+
if (!turn || this.completedTurns.get(turn) === turn.turnId)
|
|
26893
27321
|
continue;
|
|
26894
27322
|
const stderr = this.process?.recentStderr() || null;
|
|
26895
27323
|
turn.stop.abort(Object.assign(new Error("Connection to Codex was lost"), { additionalDetails: stderr }));
|
|
@@ -26953,6 +27381,19 @@ function seedItem(message) {
|
|
|
26953
27381
|
content: [{ type: message.role === "user" ? "input_text" : "output_text", text: message.text }]
|
|
26954
27382
|
};
|
|
26955
27383
|
}
|
|
27384
|
+
async function within(operation, ms) {
|
|
27385
|
+
let timer;
|
|
27386
|
+
try {
|
|
27387
|
+
return await Promise.race([
|
|
27388
|
+
operation.then(() => true),
|
|
27389
|
+
new Promise((resolve) => {
|
|
27390
|
+
timer = setTimeout(() => resolve(false), ms);
|
|
27391
|
+
})
|
|
27392
|
+
]);
|
|
27393
|
+
} finally {
|
|
27394
|
+
clearTimeout(timer);
|
|
27395
|
+
}
|
|
27396
|
+
}
|
|
26956
27397
|
|
|
26957
27398
|
// src/agent/createAgent.ts
|
|
26958
27399
|
function createAgentApp(options) {
|
|
@@ -26964,7 +27405,7 @@ function createAgentApp(options) {
|
|
|
26964
27405
|
};
|
|
26965
27406
|
return agent({ name: options.info.name }).onConnect((connection) => {
|
|
26966
27407
|
agent2 = new CodexAgent(connection.client, options);
|
|
26967
|
-
}).onRequest(methods.agent.initialize, (ctx) => current().initialize(ctx.params)).onRequest(methods.agent.auth.login, (ctx) => current().login(ctx.params, ctx.requestId)).onRequest(methods.agent.auth.logout, (ctx) => current().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => current().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => current().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => current().disableProvider(ctx.params)).onRequest(methods.agent.session.new, (ctx) => current().newSession(ctx.params)).onRequest(methods.agent.session.resume, (ctx) => current().resumeSession(ctx.params)).onRequest(methods.agent.session.fork, (ctx) => current().forkSession(ctx.params)).onRequest(methods.agent.session.list, (ctx) => current().listSessions(ctx.params)).onRequest(methods.agent.session.close, (ctx) => current().closeSession(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => current().deleteSession(ctx.params)).onRequest("_codex/session_archive", { parse: parseSessionIdParams }, (ctx) => current().archiveSession(ctx.params)).onRequest("_codex/session_unarchive", { parse: parseSessionIdParams }, (ctx) => current().unarchiveSession(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => current().setSessionConfigOption(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => current().prompt(ctx.params)).onNotification(methods.agent.session.cancel, (ctx) => current().cancel(ctx.params));
|
|
27408
|
+
}).onRequest(methods.agent.initialize, (ctx) => current().initialize(ctx.params)).onRequest(methods.agent.auth.login, (ctx) => current().login(ctx.params, ctx.requestId)).onRequest(methods.agent.auth.logout, (ctx) => current().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => current().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => current().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => current().disableProvider(ctx.params)).onRequest(methods.agent.session.new, (ctx) => current().newSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.resume, (ctx) => current().resumeSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.fork, (ctx) => current().forkSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.list, (ctx) => current().listSessions(ctx.params)).onRequest(methods.agent.session.close, (ctx) => current().closeSession(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => current().deleteSession(ctx.params)).onRequest("_codex/session_archive", { parse: parseSessionIdParams }, (ctx) => current().archiveSession(ctx.params)).onRequest("_codex/session_unarchive", { parse: parseSessionIdParams }, (ctx) => current().unarchiveSession(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => current().setSessionConfigOption(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => current().prompt(ctx.params)).onNotification(methods.agent.session.cancel, (ctx) => current().cancel(ctx.params));
|
|
26968
27409
|
}
|
|
26969
27410
|
|
|
26970
27411
|
// src/codex/AppServerClient.ts
|
|
@@ -26984,6 +27425,7 @@ class AppServerClient {
|
|
|
26984
27425
|
earlyTurnCompletions = new Map;
|
|
26985
27426
|
startingThreads = new Map;
|
|
26986
27427
|
startingReviews = 0;
|
|
27428
|
+
modelCatalog = null;
|
|
26987
27429
|
mcpStartupStates = new Map;
|
|
26988
27430
|
mcpStartupWaiters = [];
|
|
26989
27431
|
notificationWaiters = [];
|
|
@@ -27017,6 +27459,9 @@ class AppServerClient {
|
|
|
27017
27459
|
return handler ? await handler.handleUserInput(params) : { answers: {} };
|
|
27018
27460
|
});
|
|
27019
27461
|
}
|
|
27462
|
+
get disconnectSignal() {
|
|
27463
|
+
return this.disconnected.signal;
|
|
27464
|
+
}
|
|
27020
27465
|
attachThread(threadId, handlers) {
|
|
27021
27466
|
this.threadHandlers.set(threadId, handlers.notification);
|
|
27022
27467
|
this.approvalHandlers.set(threadId, handlers.approval);
|
|
@@ -27057,7 +27502,18 @@ class AppServerClient {
|
|
|
27057
27502
|
scope.abort();
|
|
27058
27503
|
}
|
|
27059
27504
|
}
|
|
27505
|
+
observers = new Set;
|
|
27506
|
+
observeNotifications(handler) {
|
|
27507
|
+
this.observers.add(handler);
|
|
27508
|
+
return () => {
|
|
27509
|
+
this.observers.delete(handler);
|
|
27510
|
+
};
|
|
27511
|
+
}
|
|
27060
27512
|
dispatchNotification(notification) {
|
|
27513
|
+
if (notification.method === "account/updated")
|
|
27514
|
+
this.modelCatalog = null;
|
|
27515
|
+
for (const observer of this.observers)
|
|
27516
|
+
observer(notification);
|
|
27061
27517
|
if (this.notificationWaiters.length > 0) {
|
|
27062
27518
|
const remaining = [];
|
|
27063
27519
|
for (const waiter of this.notificationWaiters) {
|
|
@@ -27126,10 +27582,26 @@ class AppServerClient {
|
|
|
27126
27582
|
this.earlyTurnCompletions.delete(key);
|
|
27127
27583
|
return Promise.resolve(early);
|
|
27128
27584
|
}
|
|
27129
|
-
const
|
|
27130
|
-
|
|
27585
|
+
const lifetime = signal ? AbortSignal.any([signal, this.disconnected.signal]) : this.disconnected.signal;
|
|
27586
|
+
return new Promise((resolve, reject) => {
|
|
27587
|
+
const cleanup = () => {
|
|
27588
|
+
lifetime.removeEventListener("abort", aborted2);
|
|
27589
|
+
this.turnCompletionWaiters.delete(key);
|
|
27590
|
+
};
|
|
27591
|
+
const aborted2 = () => {
|
|
27592
|
+
cleanup();
|
|
27593
|
+
reject(lifetime.reason);
|
|
27594
|
+
};
|
|
27595
|
+
if (lifetime.aborted) {
|
|
27596
|
+
reject(lifetime.reason);
|
|
27597
|
+
return;
|
|
27598
|
+
}
|
|
27599
|
+
lifetime.addEventListener("abort", aborted2, { once: true });
|
|
27600
|
+
this.turnCompletionWaiters.set(key, (event) => {
|
|
27601
|
+
cleanup();
|
|
27602
|
+
resolve(event);
|
|
27603
|
+
});
|
|
27131
27604
|
});
|
|
27132
|
-
return abortable(completed, signal ? AbortSignal.any([signal, this.disconnected.signal]) : this.disconnected.signal).finally(() => this.turnCompletionWaiters.delete(key));
|
|
27133
27605
|
}
|
|
27134
27606
|
resolveTurnInterrupted(threadId, turnId) {
|
|
27135
27607
|
this.recordTurnCompleted({
|
|
@@ -27279,13 +27751,34 @@ class AppServerClient {
|
|
|
27279
27751
|
modelList(params) {
|
|
27280
27752
|
return this.send({ method: "model/list", params });
|
|
27281
27753
|
}
|
|
27282
|
-
|
|
27754
|
+
invalidateModels() {
|
|
27755
|
+
this.modelCatalog = null;
|
|
27756
|
+
}
|
|
27757
|
+
allModels(refresh = false) {
|
|
27758
|
+
if (!refresh && this.modelCatalog && this.modelCatalog.expires > performance.now())
|
|
27759
|
+
return this.modelCatalog.pending;
|
|
27760
|
+
const pending = this.loadModels();
|
|
27761
|
+
const entry = { expires: performance.now() + 30000, pending };
|
|
27762
|
+
this.modelCatalog = entry;
|
|
27763
|
+
pending.catch(() => {
|
|
27764
|
+
if (this.modelCatalog === entry)
|
|
27765
|
+
this.modelCatalog = null;
|
|
27766
|
+
});
|
|
27767
|
+
return pending;
|
|
27768
|
+
}
|
|
27769
|
+
async loadModels() {
|
|
27283
27770
|
const models = [];
|
|
27771
|
+
const cursors = new Set;
|
|
27284
27772
|
let cursor = null;
|
|
27285
27773
|
do {
|
|
27286
27774
|
const page = await this.modelList({ cursor, limit: null });
|
|
27287
27775
|
models.push(...page.data);
|
|
27288
27776
|
cursor = page.nextCursor;
|
|
27777
|
+
if (cursor) {
|
|
27778
|
+
if (cursors.has(cursor))
|
|
27779
|
+
throw new Error("Codex model/list returned a repeated pagination cursor; retry after restarting Codex");
|
|
27780
|
+
cursors.add(cursor);
|
|
27781
|
+
}
|
|
27289
27782
|
} while (cursor);
|
|
27290
27783
|
return models;
|
|
27291
27784
|
}
|
|
@@ -27332,9 +27825,10 @@ function threadIdOf(notification) {
|
|
|
27332
27825
|
}
|
|
27333
27826
|
|
|
27334
27827
|
// src/codex/process.ts
|
|
27828
|
+
import { existsSync } from "fs";
|
|
27829
|
+
import path7 from "path";
|
|
27335
27830
|
import { spawn, spawnSync } from "child_process";
|
|
27336
27831
|
import { createRequire } from "module";
|
|
27337
|
-
var rpc = __toESM(require_main(), 1);
|
|
27338
27832
|
|
|
27339
27833
|
// src/codex/transport.ts
|
|
27340
27834
|
var import_node2 = __toESM(require_main(), 1);
|
|
@@ -27342,7 +27836,7 @@ import { StringDecoder } from "string_decoder";
|
|
|
27342
27836
|
function wireLog(value) {
|
|
27343
27837
|
return JSON.stringify(value, (key, nested) => {
|
|
27344
27838
|
const normalized = key.replace(/[_-]/g, "").toLowerCase();
|
|
27345
|
-
if (/^(apikey|accesskey|accesstoken|refreshtoken|idtoken|secretaccesskey|sessiontoken|authorization|proxyauthorization|password|secret|token|cookie|setcookie|headers|httpheaders)$/.test(normalized))
|
|
27839
|
+
if (/^(apikey|accesskey|accesstoken|refreshtoken|idtoken|secretaccesskey|sessiontoken|authorization|proxyauthorization|password|secret|token|cookie|setcookie|headers|httpheaders|env|queryparams)$/.test(normalized))
|
|
27346
27840
|
return "***";
|
|
27347
27841
|
return nested;
|
|
27348
27842
|
});
|
|
@@ -27415,7 +27909,7 @@ function createReader(readable) {
|
|
|
27415
27909
|
const closed = new import_node2.Emitter;
|
|
27416
27910
|
const partial2 = new import_node2.Emitter;
|
|
27417
27911
|
const decoder = new StringDecoder("utf8");
|
|
27418
|
-
let
|
|
27912
|
+
let fragments = [];
|
|
27419
27913
|
let ended = false;
|
|
27420
27914
|
let callback = null;
|
|
27421
27915
|
const deliver = (line) => {
|
|
@@ -27441,16 +27935,21 @@ function createReader(readable) {
|
|
|
27441
27935
|
callback?.(message);
|
|
27442
27936
|
};
|
|
27443
27937
|
const onData = (chunk) => {
|
|
27444
|
-
|
|
27938
|
+
const text2 = typeof chunk === "string" ? chunk : decoder.write(chunk);
|
|
27939
|
+
let offset = 0;
|
|
27445
27940
|
for (;; ) {
|
|
27446
|
-
const newline2 =
|
|
27447
|
-
|
|
27941
|
+
const newline2 = text2.indexOf(`
|
|
27942
|
+
`, offset);
|
|
27448
27943
|
if (newline2 < 0)
|
|
27449
27944
|
break;
|
|
27450
|
-
const
|
|
27451
|
-
|
|
27945
|
+
const part = text2.slice(offset, newline2);
|
|
27946
|
+
const line = fragments.length ? fragments.join("") + part : part;
|
|
27947
|
+
fragments = [];
|
|
27452
27948
|
deliver(line);
|
|
27949
|
+
offset = newline2 + 1;
|
|
27453
27950
|
}
|
|
27951
|
+
if (offset < text2.length)
|
|
27952
|
+
fragments.push(text2.slice(offset));
|
|
27454
27953
|
};
|
|
27455
27954
|
const onClose = () => {
|
|
27456
27955
|
if (ended)
|
|
@@ -27459,9 +27958,9 @@ function createReader(readable) {
|
|
|
27459
27958
|
closed.fire();
|
|
27460
27959
|
};
|
|
27461
27960
|
const onEnd = () => {
|
|
27462
|
-
|
|
27463
|
-
deliver(
|
|
27464
|
-
|
|
27961
|
+
fragments.push(decoder.end());
|
|
27962
|
+
deliver(fragments.join(""));
|
|
27963
|
+
fragments = [];
|
|
27465
27964
|
onClose();
|
|
27466
27965
|
};
|
|
27467
27966
|
const onError = (error40) => errors3.fire(error40);
|
|
@@ -27471,7 +27970,7 @@ function createReader(readable) {
|
|
|
27471
27970
|
readable.off("close", onClose);
|
|
27472
27971
|
readable.off("error", onError);
|
|
27473
27972
|
callback = null;
|
|
27474
|
-
|
|
27973
|
+
fragments = [];
|
|
27475
27974
|
};
|
|
27476
27975
|
return {
|
|
27477
27976
|
listen(receive) {
|
|
@@ -27493,6 +27992,92 @@ function createReader(readable) {
|
|
|
27493
27992
|
}
|
|
27494
27993
|
};
|
|
27495
27994
|
}
|
|
27995
|
+
function createCodexConnection(readable, writable) {
|
|
27996
|
+
const reader = createReader(readable);
|
|
27997
|
+
const writer = createWriter(writable);
|
|
27998
|
+
const closed = new import_node2.Emitter;
|
|
27999
|
+
let queued = 0;
|
|
28000
|
+
let eof = false;
|
|
28001
|
+
let finished = false;
|
|
28002
|
+
let timer;
|
|
28003
|
+
let resolveDrained;
|
|
28004
|
+
const drained = new Promise((resolve) => {
|
|
28005
|
+
resolveDrained = resolve;
|
|
28006
|
+
});
|
|
28007
|
+
const finish = () => {
|
|
28008
|
+
if (finished)
|
|
28009
|
+
return;
|
|
28010
|
+
finished = true;
|
|
28011
|
+
clearTimeout(timer);
|
|
28012
|
+
closed.fire();
|
|
28013
|
+
resolveDrained();
|
|
28014
|
+
};
|
|
28015
|
+
const check2 = () => {
|
|
28016
|
+
if (eof && queued === 0)
|
|
28017
|
+
setImmediate(() => {
|
|
28018
|
+
if (queued === 0)
|
|
28019
|
+
finish();
|
|
28020
|
+
});
|
|
28021
|
+
};
|
|
28022
|
+
reader.onClose(() => {
|
|
28023
|
+
eof = true;
|
|
28024
|
+
timer ??= setTimeout(() => {
|
|
28025
|
+
logger.error("Codex dispatch drain deadline exceeded", new Error(`Undispatched frames: ${queued}`));
|
|
28026
|
+
finish();
|
|
28027
|
+
}, 5000);
|
|
28028
|
+
check2();
|
|
28029
|
+
});
|
|
28030
|
+
const inputFailed = () => {
|
|
28031
|
+
if (finished || timer)
|
|
28032
|
+
return;
|
|
28033
|
+
timer = setTimeout(() => {
|
|
28034
|
+
logger.error("Codex stdout did not close after stdin", new Error("Transport shutdown deadline exceeded"));
|
|
28035
|
+
finish();
|
|
28036
|
+
}, 5000);
|
|
28037
|
+
};
|
|
28038
|
+
writer.onClose(inputFailed);
|
|
28039
|
+
const counted = (message) => !(("method" in message) && message.method === "$/cancelRequest");
|
|
28040
|
+
const connection = import_node2.createMessageConnection({
|
|
28041
|
+
...reader,
|
|
28042
|
+
onClose: closed.event,
|
|
28043
|
+
listen(receive) {
|
|
28044
|
+
return reader.listen((message) => {
|
|
28045
|
+
if (counted(message))
|
|
28046
|
+
queued += 1;
|
|
28047
|
+
receive(message);
|
|
28048
|
+
});
|
|
28049
|
+
}
|
|
28050
|
+
}, {
|
|
28051
|
+
...writer,
|
|
28052
|
+
onClose: () => ({ dispose() {} }),
|
|
28053
|
+
async write(message) {
|
|
28054
|
+
try {
|
|
28055
|
+
await writer.write(message);
|
|
28056
|
+
} catch (error40) {
|
|
28057
|
+
logger.error("Codex write failed; draining stdout before disconnect", error40);
|
|
28058
|
+
inputFailed();
|
|
28059
|
+
await drained;
|
|
28060
|
+
}
|
|
28061
|
+
}
|
|
28062
|
+
}, undefined, {
|
|
28063
|
+
messageStrategy: { handleMessage(message, next) {
|
|
28064
|
+
try {
|
|
28065
|
+
return next(message);
|
|
28066
|
+
} finally {
|
|
28067
|
+
if (counted(message))
|
|
28068
|
+
queued -= 1;
|
|
28069
|
+
check2();
|
|
28070
|
+
}
|
|
28071
|
+
} }
|
|
28072
|
+
});
|
|
28073
|
+
connection.onDispose(() => {
|
|
28074
|
+
finished = true;
|
|
28075
|
+
clearTimeout(timer);
|
|
28076
|
+
resolveDrained();
|
|
28077
|
+
closed.dispose();
|
|
28078
|
+
});
|
|
28079
|
+
return { connection, drained };
|
|
28080
|
+
}
|
|
27496
28081
|
|
|
27497
28082
|
// src/codex/process.ts
|
|
27498
28083
|
var STDERR_RETAIN_BYTES = 8 * 1024;
|
|
@@ -27519,7 +28104,29 @@ function resolveCodexLauncher(codexPath) {
|
|
|
27519
28104
|
return process.platform === "win32" ? { command: `"${codexPath}"`, prefixArgs: [], shell: true } : { command: codexPath, prefixArgs: [], shell: false };
|
|
27520
28105
|
}
|
|
27521
28106
|
try {
|
|
27522
|
-
|
|
28107
|
+
const require2 = createRequire(import.meta.url);
|
|
28108
|
+
const launcher = require2.resolve("@openai/codex/bin/codex.js");
|
|
28109
|
+
const platformRequire = createRequire(launcher);
|
|
28110
|
+
const targets = {
|
|
28111
|
+
"linux-x64": "x86_64-unknown-linux-musl",
|
|
28112
|
+
"linux-arm64": "aarch64-unknown-linux-musl",
|
|
28113
|
+
"darwin-x64": "x86_64-apple-darwin",
|
|
28114
|
+
"darwin-arm64": "aarch64-apple-darwin",
|
|
28115
|
+
"win32-x64": "x86_64-pc-windows-msvc",
|
|
28116
|
+
"win32-arm64": "aarch64-pc-windows-msvc"
|
|
28117
|
+
};
|
|
28118
|
+
const platform2 = `${process.platform}-${process.arch}`;
|
|
28119
|
+
const target = targets[platform2];
|
|
28120
|
+
if (!target)
|
|
28121
|
+
throw new Error(`Unsupported Codex platform: ${platform2}`);
|
|
28122
|
+
let vendor = path7.resolve(path7.dirname(launcher), "..", "vendor");
|
|
28123
|
+
try {
|
|
28124
|
+
vendor = path7.join(path7.dirname(platformRequire.resolve(`@openai/codex-${platform2}/package.json`)), "vendor");
|
|
28125
|
+
} catch {}
|
|
28126
|
+
const command = path7.join(vendor, target, "bin", process.platform === "win32" ? "codex.exe" : "codex");
|
|
28127
|
+
if (!existsSync(command))
|
|
28128
|
+
throw new Error(`Missing Codex platform binary: ${command}`);
|
|
28129
|
+
return { command, prefixArgs: [], shell: false };
|
|
27523
28130
|
} catch (error40) {
|
|
27524
28131
|
throw new Error("No Codex CLI: set CODEX_PATH to an installed Codex executable, or install the optional " + `@openai/codex dependency (${String(error40)})`);
|
|
27525
28132
|
}
|
|
@@ -27536,11 +28143,13 @@ function startCodexProcess(codexPath, env = process.env) {
|
|
|
27536
28143
|
stderr = (stderr + data.toString()).slice(-STDERR_RETAIN_BYTES);
|
|
27537
28144
|
logger.log("[codex stderr]", { data: data.toString() });
|
|
27538
28145
|
});
|
|
27539
|
-
const connection =
|
|
28146
|
+
const { connection, drained } = createCodexConnection(child.stdout, child.stdin);
|
|
27540
28147
|
connection.listen();
|
|
28148
|
+
drained.then(() => connection.dispose());
|
|
27541
28149
|
const exited = new Promise((resolve) => {
|
|
27542
|
-
child.on("
|
|
28150
|
+
child.on("close", async (code, signal) => {
|
|
27543
28151
|
logger.log("codex exited", { code, signal });
|
|
28152
|
+
await drained;
|
|
27544
28153
|
connection.dispose();
|
|
27545
28154
|
resolve(code);
|
|
27546
28155
|
});
|