@nyssance/codex-acp-v2 0.3.4 → 0.4.0
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 +53 -0
- package/README.md +6 -0
- package/dist/index.js +922 -217
- 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.
|
|
21724
|
+
version: "0.4.0",
|
|
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,19 @@ 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
|
+
this.notifySkillsChanged();
|
|
26287
|
+
}
|
|
26288
|
+
if (notification.method === "account/updated")
|
|
26289
|
+
this.refreshAccounts().catch((error40) => logger.error("refreshing account failed", error40));
|
|
26290
|
+
});
|
|
26291
|
+
this.codex.disconnectSignal.addEventListener("abort", stopObserving, { once: true });
|
|
26167
26292
|
}
|
|
26168
26293
|
async initialize(params) {
|
|
26169
26294
|
if (params.protocolVersion !== PROTOCOL_VERSION) {
|
|
@@ -26198,7 +26323,7 @@ class CodexAgent {
|
|
|
26198
26323
|
additionalDirectories: {}
|
|
26199
26324
|
},
|
|
26200
26325
|
providers: {},
|
|
26201
|
-
_meta: { codex: { archive: true, seedHistory: true } }
|
|
26326
|
+
_meta: { codex: { archive: true, seedHistory: true, skills: true, plugins: true } }
|
|
26202
26327
|
},
|
|
26203
26328
|
authMethods: authMethods(this.capabilities, this.env)
|
|
26204
26329
|
};
|
|
@@ -26222,7 +26347,11 @@ class CodexAgent {
|
|
|
26222
26347
|
return {};
|
|
26223
26348
|
}
|
|
26224
26349
|
async refreshAccounts() {
|
|
26350
|
+
this.codex.invalidateModels();
|
|
26351
|
+
const generation = ++this.accountGeneration;
|
|
26225
26352
|
const account = (await this.codex.accountRead({ refreshToken: false })).account;
|
|
26353
|
+
if (generation !== this.accountGeneration)
|
|
26354
|
+
return;
|
|
26226
26355
|
for (const runtime of this.sessions.values())
|
|
26227
26356
|
runtime.session.account = account;
|
|
26228
26357
|
}
|
|
@@ -26232,16 +26361,14 @@ class CodexAgent {
|
|
|
26232
26361
|
}
|
|
26233
26362
|
async setProvider(params) {
|
|
26234
26363
|
this.requireInitialized("providers/set");
|
|
26235
|
-
this.
|
|
26236
|
-
this.providers.set(params);
|
|
26237
|
-
await this.rebindSessions();
|
|
26364
|
+
await this.changeProvider((candidate) => candidate.set(params));
|
|
26238
26365
|
return {};
|
|
26239
26366
|
}
|
|
26240
26367
|
async disableProvider(params) {
|
|
26241
26368
|
this.requireInitialized("providers/disable");
|
|
26242
|
-
|
|
26243
|
-
|
|
26244
|
-
await this.
|
|
26369
|
+
if (params.providerId !== OPENAI_PROVIDER_ID)
|
|
26370
|
+
return {};
|
|
26371
|
+
await this.changeProvider((candidate) => candidate.disable(params));
|
|
26245
26372
|
return {};
|
|
26246
26373
|
}
|
|
26247
26374
|
assertNoActiveTurns(method) {
|
|
@@ -26250,39 +26377,147 @@ class CodexAgent {
|
|
|
26250
26377
|
throw RequestError.invalidRequest({ sessions: busy }, `${method} cannot change routing while a turn is running; cancel it or wait for idle`);
|
|
26251
26378
|
}
|
|
26252
26379
|
}
|
|
26253
|
-
|
|
26254
|
-
|
|
26255
|
-
|
|
26256
|
-
|
|
26257
|
-
|
|
26258
|
-
|
|
26259
|
-
|
|
26260
|
-
|
|
26261
|
-
|
|
26262
|
-
|
|
26263
|
-
|
|
26264
|
-
|
|
26265
|
-
|
|
26266
|
-
|
|
26267
|
-
|
|
26268
|
-
|
|
26269
|
-
|
|
26380
|
+
assertRoutingAvailable() {
|
|
26381
|
+
if (this.switching)
|
|
26382
|
+
throw RequestError.invalidRequest(undefined, "Provider routing is changing; retry after providers/set or providers/disable finishes");
|
|
26383
|
+
}
|
|
26384
|
+
async admission(operation, sessionId) {
|
|
26385
|
+
this.assertRoutingAvailable();
|
|
26386
|
+
if (sessionId && this.sessionMutations.has(sessionId))
|
|
26387
|
+
throw RequestError.invalidRequest({ sessionId }, "Session lifecycle or configuration work is in progress; retry when it finishes");
|
|
26388
|
+
if (sessionId)
|
|
26389
|
+
this.sessionMutations.add(sessionId);
|
|
26390
|
+
this.admissions += 1;
|
|
26391
|
+
try {
|
|
26392
|
+
return await operation();
|
|
26393
|
+
} finally {
|
|
26394
|
+
this.admissions -= 1;
|
|
26395
|
+
if (sessionId)
|
|
26396
|
+
this.sessionMutations.delete(sessionId);
|
|
26397
|
+
}
|
|
26398
|
+
}
|
|
26399
|
+
async changeProvider(change) {
|
|
26400
|
+
const pending = this.providerQueue.then(async () => {
|
|
26401
|
+
this.assertNoActiveTurns("providers/set or providers/disable");
|
|
26402
|
+
if (this.admissions > 0)
|
|
26403
|
+
throw RequestError.invalidRequest(undefined, "Session configuration or lifecycle work is in progress; retry the provider change when it finishes");
|
|
26404
|
+
this.switching = true;
|
|
26405
|
+
const candidate = this.providers.copy();
|
|
26406
|
+
const attempted = [];
|
|
26407
|
+
const staged = new Map;
|
|
26408
|
+
try {
|
|
26409
|
+
change(candidate);
|
|
26410
|
+
const catalog = await this.withCodex(() => this.codex.allModels(true));
|
|
26411
|
+
const modelProvider = await this.resolveModelProvider(candidate);
|
|
26412
|
+
for (const runtime of this.sessions.values()) {
|
|
26413
|
+
try {
|
|
26414
|
+
await this.codex.threadTurnsList({ threadId: runtime.session.id, limit: 1, itemsView: "notLoaded" });
|
|
26415
|
+
} catch (error40) {
|
|
26416
|
+
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");
|
|
26417
|
+
}
|
|
26418
|
+
}
|
|
26419
|
+
for (const runtime of this.sessions.values()) {
|
|
26420
|
+
const config2 = { ...runtime.config };
|
|
26421
|
+
const providers = candidate.threadConfig()["model_providers"];
|
|
26422
|
+
if (providers === undefined)
|
|
26423
|
+
delete config2["model_providers"];
|
|
26424
|
+
else
|
|
26425
|
+
config2["model_providers"] = providers;
|
|
26426
|
+
const nextCatalog = candidate.catalog(catalog);
|
|
26427
|
+
const requestedModel = candidate.active?.model ?? (findModel(nextCatalog, runtime.session.model.model) ? runtime.session.model.model : null);
|
|
26428
|
+
const selection = resolveModelSelection(nextCatalog, requestedModel, requestedModel === runtime.session.model.model ? runtime.session.model.effort : null);
|
|
26429
|
+
attempted.push(runtime);
|
|
26430
|
+
await this.codex.threadUnsubscribe({ threadId: runtime.session.id });
|
|
26431
|
+
const thread = await this.withCodex(() => this.codex.threadResume({
|
|
26432
|
+
threadId: runtime.session.id,
|
|
26433
|
+
cwd: runtime.session.cwd,
|
|
26434
|
+
config: config2,
|
|
26435
|
+
modelProvider,
|
|
26436
|
+
model: selection.model,
|
|
26437
|
+
excludeTurns: true
|
|
26438
|
+
}));
|
|
26439
|
+
staged.set(runtime, { config: config2, catalog: nextCatalog, model: resolveModelSelection(nextCatalog, candidate.active?.model ?? thread.model, thread.reasoningEffort) });
|
|
26440
|
+
}
|
|
26441
|
+
this.providers = candidate;
|
|
26442
|
+
for (const [runtime, next] of staged) {
|
|
26443
|
+
runtime.config = next.config;
|
|
26444
|
+
runtime.modelProvider = modelProvider;
|
|
26445
|
+
runtime.session.catalog = next.catalog;
|
|
26446
|
+
runtime.session.model = next.model;
|
|
26447
|
+
runtime.stale = false;
|
|
26448
|
+
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));
|
|
26449
|
+
}
|
|
26450
|
+
} catch (error40) {
|
|
26451
|
+
if (attempted.length === 0 && error40 instanceof RequestError)
|
|
26452
|
+
throw error40;
|
|
26453
|
+
const staleSessions = [];
|
|
26454
|
+
for (const runtime of attempted) {
|
|
26455
|
+
try {
|
|
26456
|
+
await this.codex.threadUnsubscribe({ threadId: runtime.session.id });
|
|
26457
|
+
await this.codex.threadResume({
|
|
26458
|
+
threadId: runtime.session.id,
|
|
26459
|
+
cwd: runtime.session.cwd,
|
|
26460
|
+
config: runtime.config,
|
|
26461
|
+
modelProvider: runtime.modelProvider,
|
|
26462
|
+
model: runtime.session.model.model,
|
|
26463
|
+
excludeTurns: true
|
|
26464
|
+
});
|
|
26465
|
+
} catch (rollbackError) {
|
|
26466
|
+
runtime.stale = true;
|
|
26467
|
+
staleSessions.push(runtime.session.id);
|
|
26468
|
+
logger.error("Provider rollback failed", rollbackError, { sessionId: runtime.session.id });
|
|
26469
|
+
runtime.client.update({ sessionUpdate: "config_option_update", configOptions: sessionConfigOptions(runtime.session), _meta: { codex: { routing: { stale: true } } } }).catch(() => {});
|
|
26470
|
+
}
|
|
26471
|
+
}
|
|
26472
|
+
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"}`);
|
|
26473
|
+
} finally {
|
|
26474
|
+
this.switching = false;
|
|
26475
|
+
}
|
|
26476
|
+
});
|
|
26477
|
+
this.providerQueue = pending.catch(() => {});
|
|
26478
|
+
await pending;
|
|
26270
26479
|
}
|
|
26271
|
-
async newSession(params) {
|
|
26272
|
-
const runtime = await this.openSession({ kind: "new", request: params });
|
|
26480
|
+
async newSession(params, signal) {
|
|
26481
|
+
const runtime = await this.openSession({ kind: "new", request: params }, signal);
|
|
26273
26482
|
return { sessionId: runtime.session.id, configOptions: sessionConfigOptions(runtime.session) };
|
|
26274
26483
|
}
|
|
26275
|
-
async resumeSession(params) {
|
|
26276
|
-
const runtime = await this.openSession({ kind: "resume", request: params });
|
|
26484
|
+
async resumeSession(params, signal) {
|
|
26485
|
+
const runtime = await this.openSession({ kind: "resume", request: params }, signal);
|
|
26277
26486
|
return { configOptions: sessionConfigOptions(runtime.session) };
|
|
26278
26487
|
}
|
|
26279
|
-
async forkSession(params) {
|
|
26280
|
-
const runtime = await this.openSession({ kind: "fork", request: params });
|
|
26488
|
+
async forkSession(params, signal) {
|
|
26489
|
+
const runtime = await this.openSession({ kind: "fork", request: params }, signal);
|
|
26281
26490
|
return { sessionId: runtime.session.id, configOptions: sessionConfigOptions(runtime.session) };
|
|
26282
26491
|
}
|
|
26283
|
-
async openSession(open2) {
|
|
26492
|
+
async openSession(open2, signal) {
|
|
26493
|
+
if (signal?.aborted)
|
|
26494
|
+
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
26495
|
+
const pending = this.admission(async () => {
|
|
26496
|
+
const runtime = await this.openSessionInternal(open2, signal);
|
|
26497
|
+
if (signal?.aborted) {
|
|
26498
|
+
await this.closeRuntime(runtime.session.id);
|
|
26499
|
+
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
26500
|
+
}
|
|
26501
|
+
return runtime;
|
|
26502
|
+
}, open2.kind === "new" ? undefined : open2.request.sessionId);
|
|
26503
|
+
try {
|
|
26504
|
+
return signal ? await abortable(pending, signal) : await pending;
|
|
26505
|
+
} catch (error40) {
|
|
26506
|
+
if (signal?.aborted)
|
|
26507
|
+
throw RequestError.requestCancelled(undefined, "Session opening was cancelled; any thread returned later will be unsubscribed");
|
|
26508
|
+
throw error40;
|
|
26509
|
+
}
|
|
26510
|
+
}
|
|
26511
|
+
assertUnsubscribeSettled(sessionId) {
|
|
26512
|
+
if (this.pendingUnsubscribes.has(sessionId)) {
|
|
26513
|
+
throw RequestError.invalidRequest({ sessionId }, "A previous unsubscribe is still pending; retry after Codex responds or reconnect the agent");
|
|
26514
|
+
}
|
|
26515
|
+
}
|
|
26516
|
+
async openSessionInternal(open2, signal) {
|
|
26284
26517
|
const method = { new: "session/new", resume: "session/resume", fork: "session/fork" }[open2.kind];
|
|
26285
26518
|
const capabilities = this.requireInitialized(method);
|
|
26519
|
+
if (open2.kind !== "new")
|
|
26520
|
+
this.assertUnsubscribeSettled(open2.request.sessionId);
|
|
26286
26521
|
const { request } = open2;
|
|
26287
26522
|
if (typeof request.cwd !== "string" || !path6.isAbsolute(request.cwd)) {
|
|
26288
26523
|
throw RequestError.invalidParams({ cwd: request.cwd }, "cwd must be an absolute path");
|
|
@@ -26294,17 +26529,21 @@ class CodexAgent {
|
|
|
26294
26529
|
const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories);
|
|
26295
26530
|
if (open2.kind === "resume" && this.sessions.has(open2.request.sessionId)) {
|
|
26296
26531
|
await this.closeRuntime(open2.request.sessionId);
|
|
26532
|
+
this.assertUnsubscribeSettled(open2.request.sessionId);
|
|
26297
26533
|
}
|
|
26298
26534
|
const mcpServers = request.mcpServers ?? [];
|
|
26299
|
-
|
|
26535
|
+
let accountVersion = this.accountGeneration;
|
|
26536
|
+
const account = await this.withCodex(() => this.codex.accountRead({ refreshToken: false }));
|
|
26537
|
+
if (this.providers.active === null && account.requiresOpenaiAuth && account.account === null) {
|
|
26300
26538
|
throw RequestError.authRequired(undefined, "Log in to Codex first (auth/login)");
|
|
26301
26539
|
}
|
|
26302
|
-
await this.withCodex(() => this.refreshSkills(request.cwd, additionalDirectories));
|
|
26303
26540
|
const existingMcp = mcpServers.length > 0 ? await this.withCodex(() => this.configuredMcpServerNames(request.cwd)) : new Set;
|
|
26304
26541
|
const config2 = buildThreadConfig(this.providers.threadConfig(), request.cwd, additionalDirectories, mcpServers, existingMcp);
|
|
26305
26542
|
const mcpStartupGeneration = this.codex.mcpStartupGeneration;
|
|
26306
26543
|
const modelProvider = await this.withCodex(() => this.resolveModelProvider());
|
|
26307
|
-
const thread = await this.withCodex(async () => {
|
|
26544
|
+
const { thread, skills } = await this.withSkillsContext(request.cwd, additionalDirectories, async (skills2) => ({ skills: skills2, thread: await this.withCodex(async () => {
|
|
26545
|
+
if (signal?.aborted)
|
|
26546
|
+
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
26308
26547
|
switch (open2.kind) {
|
|
26309
26548
|
case "new":
|
|
26310
26549
|
return await this.codex.threadStart({ config: config2, cwd: request.cwd, modelProvider });
|
|
@@ -26313,13 +26552,32 @@ class CodexAgent {
|
|
|
26313
26552
|
case "fork":
|
|
26314
26553
|
return await this.codex.threadFork({ threadId: open2.request.sessionId, config: config2, cwd: request.cwd, modelProvider, excludeTurns: true });
|
|
26315
26554
|
}
|
|
26316
|
-
});
|
|
26555
|
+
}) }));
|
|
26317
26556
|
const sessionId = thread.thread.id;
|
|
26557
|
+
let removeCancellation = () => {};
|
|
26558
|
+
const openingCompletions = new Map;
|
|
26559
|
+
const openingStarts = new Set;
|
|
26560
|
+
const stopObservingOpen = this.codex.observeNotifications((notification) => {
|
|
26561
|
+
if (notification.method === "turn/completed" && notification.params.threadId === sessionId) {
|
|
26562
|
+
if (openingCompletions.size >= 32)
|
|
26563
|
+
openingCompletions.delete(openingCompletions.keys().next().value);
|
|
26564
|
+
openingCompletions.set(notification.params.turn.id, notification.params);
|
|
26565
|
+
}
|
|
26566
|
+
if (notification.method === "turn/started" && notification.params.threadId === sessionId && this.sessions.has(sessionId)) {
|
|
26567
|
+
if (openingStarts.size >= 32)
|
|
26568
|
+
openingStarts.delete(openingStarts.values().next().value);
|
|
26569
|
+
openingStarts.add(notification.params.turn.id);
|
|
26570
|
+
}
|
|
26571
|
+
});
|
|
26318
26572
|
try {
|
|
26319
|
-
|
|
26320
|
-
|
|
26321
|
-
|
|
26322
|
-
|
|
26573
|
+
if (signal?.aborted)
|
|
26574
|
+
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
26575
|
+
const codexCatalog = await this.withCodex(() => this.codex.allModels());
|
|
26576
|
+
let openedAccount = account.account;
|
|
26577
|
+
while (accountVersion !== this.accountGeneration) {
|
|
26578
|
+
accountVersion = this.accountGeneration;
|
|
26579
|
+
openedAccount = (await this.withCodex(() => this.codex.accountRead({ refreshToken: false }))).account;
|
|
26580
|
+
}
|
|
26323
26581
|
const gateway = this.providers.active;
|
|
26324
26582
|
const catalog = this.providers.catalog(codexCatalog);
|
|
26325
26583
|
const model = resolveModelSelection(catalog, gateway?.model ?? thread.model, thread.reasoningEffort);
|
|
@@ -26333,7 +26591,7 @@ class CodexAgent {
|
|
|
26333
26591
|
mode: initialAgentMode(this.env),
|
|
26334
26592
|
collaborationMode: DEFAULT_COLLABORATION_MODE,
|
|
26335
26593
|
fastMode: thread.serviceTier === FAST_SERVICE_TIER,
|
|
26336
|
-
account:
|
|
26594
|
+
account: openedAccount,
|
|
26337
26595
|
title: null,
|
|
26338
26596
|
titleIsExplicit: false,
|
|
26339
26597
|
activeTurn: null,
|
|
@@ -26341,7 +26599,16 @@ class CodexAgent {
|
|
|
26341
26599
|
contextWindow: null,
|
|
26342
26600
|
closed: false
|
|
26343
26601
|
};
|
|
26344
|
-
const runtime = this.installRuntime(session, capabilities);
|
|
26602
|
+
const runtime = this.installRuntime(session, capabilities, config2, thread.modelProvider);
|
|
26603
|
+
const cancelOpening = () => {
|
|
26604
|
+
runtime.session.closed = true;
|
|
26605
|
+
runtime.lifetime.abort();
|
|
26606
|
+
runtime.client.dispose();
|
|
26607
|
+
};
|
|
26608
|
+
signal?.addEventListener("abort", cancelOpening, { once: true });
|
|
26609
|
+
removeCancellation = () => signal?.removeEventListener("abort", cancelOpening);
|
|
26610
|
+
if (signal?.aborted)
|
|
26611
|
+
cancelOpening();
|
|
26345
26612
|
const replay = open2.kind === "fork" || open2.kind === "resume" && open2.request.replayFrom?.type === "start";
|
|
26346
26613
|
if (open2.kind !== "new") {
|
|
26347
26614
|
await this.replayHistory(runtime, replay, thread.thread);
|
|
@@ -26350,9 +26617,17 @@ class CodexAgent {
|
|
|
26350
26617
|
await this.withCodex(() => this.codex.threadInjectItems({ threadId: sessionId, items: seed.map(seedItem) }));
|
|
26351
26618
|
}
|
|
26352
26619
|
}
|
|
26620
|
+
if (thread.thread.status.type === "active" && runtime.session.activeTurn === null) {
|
|
26621
|
+
const latest = await this.withCodex(() => this.codex.threadTurnsList({ threadId: session.id, limit: 1, sortDirection: "desc", itemsView: "full" }));
|
|
26622
|
+
const active = latest.data.find((turn) => turn.status === "inProgress");
|
|
26623
|
+
if (active && !(openingStarts.has(active.id) && openingCompletions.has(active.id))) {
|
|
26624
|
+
const completed = openingCompletions.get(active.id);
|
|
26625
|
+
this.observeForeignTurn(runtime, active.id, completed ? Promise.resolve(completed) : undefined, active.items);
|
|
26626
|
+
}
|
|
26627
|
+
}
|
|
26353
26628
|
if (mcpServers.length > 0)
|
|
26354
26629
|
this.reportMcpStartup(runtime, mcpStartupGeneration);
|
|
26355
|
-
this.publishAvailableCommands(runtime);
|
|
26630
|
+
this.publishAvailableCommands(runtime, skills);
|
|
26356
26631
|
return runtime;
|
|
26357
26632
|
} catch (error40) {
|
|
26358
26633
|
const runtime = this.sessions.get(sessionId);
|
|
@@ -26364,23 +26639,26 @@ class CodexAgent {
|
|
|
26364
26639
|
}
|
|
26365
26640
|
this.sessions.delete(sessionId);
|
|
26366
26641
|
this.codex.detachThread(sessionId);
|
|
26367
|
-
await this.
|
|
26642
|
+
await within(this.unsubscribeThread(sessionId), this.closeGraceMs).catch(() => {});
|
|
26368
26643
|
throw error40;
|
|
26644
|
+
} finally {
|
|
26645
|
+
removeCancellation();
|
|
26646
|
+
stopObservingOpen();
|
|
26369
26647
|
}
|
|
26370
26648
|
}
|
|
26371
|
-
installRuntime(session, capabilities) {
|
|
26649
|
+
installRuntime(session, capabilities, config2, modelProvider) {
|
|
26372
26650
|
const client = new ClientSession(session.id, this.link, capabilities);
|
|
26373
26651
|
const bridge = new EventBridge(client, session);
|
|
26374
26652
|
const turnContext = new TurnContext(session.id);
|
|
26375
26653
|
const signal = () => {
|
|
26376
26654
|
const turn = session.activeTurn;
|
|
26377
|
-
return
|
|
26655
|
+
return AbortSignal.any([this.codex.disconnectSignal, ...turn ? [turn.abort.signal, turn.stop.signal] : []]);
|
|
26378
26656
|
};
|
|
26379
26657
|
const approval = new CodexApprovalHandler(client, turnContext, signal);
|
|
26380
26658
|
const elicitation = new CodexElicitationHandler(client, turnContext, signal);
|
|
26381
|
-
const runtime = { session, client, bridge, turnContext, elicitation, lifetime: new AbortController, queue: Promise.resolve() };
|
|
26659
|
+
const runtime = { config: config2, modelProvider, stale: false, session, client, bridge, turnContext, elicitation, lifetime: new AbortController, queue: Promise.resolve() };
|
|
26382
26660
|
const drained = (operation) => async (params) => {
|
|
26383
|
-
await this.drain(runtime);
|
|
26661
|
+
await abortable(this.drain(runtime), this.codex.disconnectSignal);
|
|
26384
26662
|
return await operation(params);
|
|
26385
26663
|
};
|
|
26386
26664
|
this.codex.attachThread(session.id, {
|
|
@@ -26399,7 +26677,23 @@ class CodexAgent {
|
|
|
26399
26677
|
return runtime;
|
|
26400
26678
|
}
|
|
26401
26679
|
enqueue(runtime, notification) {
|
|
26680
|
+
if (runtime.lifetime.signal.aborted)
|
|
26681
|
+
return;
|
|
26682
|
+
if (notification.method === "turn/started" && runtime.session.activeTurn === null) {
|
|
26683
|
+
this.observeForeignTurn(runtime, notification.params.turn.id);
|
|
26684
|
+
}
|
|
26685
|
+
const turn = runtime.session.activeTurn;
|
|
26686
|
+
if (notification.method.startsWith("item/") && "turnId" in notification.params && typeof notification.params.turnId === "string") {
|
|
26687
|
+
if (!turn || turn.turnId !== null && notification.params.turnId !== turn.turnId)
|
|
26688
|
+
return;
|
|
26689
|
+
}
|
|
26690
|
+
if (turn && notification.method === "turn/completed")
|
|
26691
|
+
this.completedTurns.set(turn, notification.params.turn.id);
|
|
26692
|
+
if (runtime.lifetime.signal.aborted)
|
|
26693
|
+
return;
|
|
26402
26694
|
const run = async () => {
|
|
26695
|
+
if (runtime.lifetime.signal.aborted)
|
|
26696
|
+
return;
|
|
26403
26697
|
try {
|
|
26404
26698
|
runtime.turnContext.observe(notification);
|
|
26405
26699
|
await runtime.elicitation.observe(notification);
|
|
@@ -26410,11 +26704,21 @@ class CodexAgent {
|
|
|
26410
26704
|
};
|
|
26411
26705
|
runtime.queue = runtime.queue.then(run, run);
|
|
26412
26706
|
}
|
|
26707
|
+
observeForeignTurn(runtime, turnId, completion, items = []) {
|
|
26708
|
+
if (runtime.session.activeTurn || runtime.session.closed)
|
|
26709
|
+
return;
|
|
26710
|
+
const turn = createActiveTurn(runtime.session.id);
|
|
26711
|
+
runtime.session.activeTurn = turn;
|
|
26712
|
+
this.turnStarted(runtime, turn, turnId, runtime.session.id);
|
|
26713
|
+
runtime.client.reportRunning();
|
|
26714
|
+
const completed = completion ?? this.codex.awaitTurnCompleted(runtime.session.id, turnId, turn.stop.signal);
|
|
26715
|
+
this.runPrompt(runtime, turn, { sessionId: runtime.session.id, prompt: [] }, completed, items);
|
|
26716
|
+
}
|
|
26413
26717
|
async drain(runtime) {
|
|
26414
26718
|
let current;
|
|
26415
26719
|
do {
|
|
26416
26720
|
current = runtime.queue;
|
|
26417
|
-
await current;
|
|
26721
|
+
await abortable(current, runtime.client.signal);
|
|
26418
26722
|
} while (runtime.queue !== current);
|
|
26419
26723
|
}
|
|
26420
26724
|
async replayHistory(runtime, replay, thread) {
|
|
@@ -26437,12 +26741,18 @@ class CodexAgent {
|
|
|
26437
26741
|
await publishTitle(firstPage.data);
|
|
26438
26742
|
return;
|
|
26439
26743
|
}
|
|
26744
|
+
const cursors = new Set;
|
|
26440
26745
|
let cursor = null;
|
|
26441
26746
|
do {
|
|
26442
26747
|
const page = await this.turnPage(session.id, cursor);
|
|
26443
26748
|
await publishTitle(page.data);
|
|
26444
26749
|
await client.updateAll(historyUpdates(page.data));
|
|
26445
26750
|
cursor = page.nextCursor;
|
|
26751
|
+
if (cursor !== null) {
|
|
26752
|
+
if (cursors.has(cursor))
|
|
26753
|
+
throw RequestError.internalError({ sessionId: session.id }, "Codex history pagination repeated a cursor; retry after restarting Codex");
|
|
26754
|
+
cursors.add(cursor);
|
|
26755
|
+
}
|
|
26446
26756
|
} while (cursor !== null && !session.closed);
|
|
26447
26757
|
await publishTitle([]);
|
|
26448
26758
|
}
|
|
@@ -26467,12 +26777,13 @@ class CodexAgent {
|
|
|
26467
26777
|
logger.error("MCP startup reporting failed", error40, { sessionId: runtime.session.id });
|
|
26468
26778
|
}
|
|
26469
26779
|
}
|
|
26470
|
-
async publishAvailableCommands(runtime) {
|
|
26780
|
+
async publishAvailableCommands(runtime, snapshot) {
|
|
26471
26781
|
try {
|
|
26472
|
-
const skills = await this.codex.skillsList({ cwds: [runtime.session.cwd, ...runtime.session.additionalDirectories] });
|
|
26782
|
+
const skills = snapshot ?? await this.codex.skillsList({ cwds: [runtime.session.cwd, ...runtime.session.additionalDirectories] });
|
|
26473
26783
|
if (runtime.session.closed)
|
|
26474
26784
|
return;
|
|
26475
26785
|
await runtime.client.update({ sessionUpdate: "available_commands_update", availableCommands: availableCommands(skills.data) });
|
|
26786
|
+
this.publishedSkills.set(runtime, skills);
|
|
26476
26787
|
} catch (error40) {
|
|
26477
26788
|
logger.error("publishing available commands failed", error40, { sessionId: runtime.session.id });
|
|
26478
26789
|
}
|
|
@@ -26499,58 +26810,147 @@ class CodexAgent {
|
|
|
26499
26810
|
};
|
|
26500
26811
|
}
|
|
26501
26812
|
async closeSession(params) {
|
|
26813
|
+
return await this.admission(() => this.closeSessionInternal(params), params.sessionId);
|
|
26814
|
+
}
|
|
26815
|
+
async closeSessionInternal(params) {
|
|
26502
26816
|
this.requireInitialized("session/close");
|
|
26503
|
-
await this.closeRuntime(params.sessionId);
|
|
26504
|
-
return {};
|
|
26817
|
+
return await this.closeRuntime(params.sessionId);
|
|
26505
26818
|
}
|
|
26506
26819
|
async deleteSession(params) {
|
|
26820
|
+
return await this.admission(() => this.deleteSessionInternal(params), params.sessionId);
|
|
26821
|
+
}
|
|
26822
|
+
async deleteSessionInternal(params) {
|
|
26507
26823
|
this.requireInitialized("session/delete");
|
|
26508
26824
|
await this.closeRuntime(params.sessionId);
|
|
26509
26825
|
await this.withCodex(() => this.codex.threadDelete({ threadId: params.sessionId }));
|
|
26510
26826
|
return {};
|
|
26511
26827
|
}
|
|
26512
26828
|
async archiveSession(params) {
|
|
26829
|
+
return await this.admission(() => this.archiveSessionInternal(params), params.sessionId);
|
|
26830
|
+
}
|
|
26831
|
+
async archiveSessionInternal(params) {
|
|
26513
26832
|
this.requireInitialized("_codex/session_archive");
|
|
26514
26833
|
await this.closeRuntime(params.sessionId);
|
|
26515
26834
|
await this.withCodex(() => this.codex.threadArchive({ threadId: params.sessionId }));
|
|
26516
26835
|
return {};
|
|
26517
26836
|
}
|
|
26518
26837
|
async unarchiveSession(params) {
|
|
26838
|
+
return await this.admission(() => this.unarchiveSessionInternal(params), params.sessionId);
|
|
26839
|
+
}
|
|
26840
|
+
async unarchiveSessionInternal(params) {
|
|
26519
26841
|
this.requireInitialized("_codex/session_unarchive");
|
|
26520
26842
|
await this.withCodex(() => this.codex.threadUnarchive({ threadId: params.sessionId }));
|
|
26521
26843
|
return {};
|
|
26522
26844
|
}
|
|
26845
|
+
async skillsList(params) {
|
|
26846
|
+
this.requireInitialized("_codex/skills_list");
|
|
26847
|
+
return await this.withCodex(() => this.codex.skillsList(params));
|
|
26848
|
+
}
|
|
26849
|
+
async skillsConfigWrite(params) {
|
|
26850
|
+
this.requireInitialized("_codex/skills_config_write");
|
|
26851
|
+
const response = await this.withCodex(() => this.codex.skillsConfigWrite(params));
|
|
26852
|
+
this.notifySkillsChanged();
|
|
26853
|
+
return response;
|
|
26854
|
+
}
|
|
26855
|
+
async pluginList(params) {
|
|
26856
|
+
this.requireInitialized("_codex/plugin_list");
|
|
26857
|
+
return await this.withCodex(() => this.codex.pluginList(params));
|
|
26858
|
+
}
|
|
26859
|
+
async pluginInstalled(params) {
|
|
26860
|
+
this.requireInitialized("_codex/plugin_installed");
|
|
26861
|
+
return await this.withCodex(() => this.codex.pluginInstalled(params));
|
|
26862
|
+
}
|
|
26863
|
+
async pluginInstall(params) {
|
|
26864
|
+
this.requireInitialized("_codex/plugin_install");
|
|
26865
|
+
const response = await this.withCodex(() => this.codex.pluginInstall(params));
|
|
26866
|
+
this.notifySkillsChanged();
|
|
26867
|
+
return response;
|
|
26868
|
+
}
|
|
26869
|
+
async pluginUninstall(params) {
|
|
26870
|
+
this.requireInitialized("_codex/plugin_uninstall");
|
|
26871
|
+
await this.withCodex(() => this.codex.pluginUninstall(params));
|
|
26872
|
+
this.notifySkillsChanged();
|
|
26873
|
+
return {};
|
|
26874
|
+
}
|
|
26875
|
+
async pluginRead(params) {
|
|
26876
|
+
this.requireInitialized("_codex/plugin_read");
|
|
26877
|
+
return await this.withCodex(() => this.codex.pluginRead(params));
|
|
26878
|
+
}
|
|
26879
|
+
async marketplaceAdd(params) {
|
|
26880
|
+
this.requireInitialized("_codex/marketplace_add");
|
|
26881
|
+
const response = await this.withCodex(() => this.codex.marketplaceAdd(params));
|
|
26882
|
+
this.notifySkillsChanged();
|
|
26883
|
+
return response;
|
|
26884
|
+
}
|
|
26885
|
+
async marketplaceRemove(params) {
|
|
26886
|
+
this.requireInitialized("_codex/marketplace_remove");
|
|
26887
|
+
await this.withCodex(() => this.codex.marketplaceRemove(params));
|
|
26888
|
+
this.notifySkillsChanged();
|
|
26889
|
+
return {};
|
|
26890
|
+
}
|
|
26891
|
+
async marketplaceUpgrade(params) {
|
|
26892
|
+
this.requireInitialized("_codex/marketplace_upgrade");
|
|
26893
|
+
const response = await this.withCodex(() => this.codex.marketplaceUpgrade(params));
|
|
26894
|
+
this.notifySkillsChanged();
|
|
26895
|
+
return response;
|
|
26896
|
+
}
|
|
26897
|
+
notifySkillsChanged() {
|
|
26898
|
+
this.link.notify("_codex/skills_changed", {}).catch((error40) => logger.error("skills change notification failed", error40));
|
|
26899
|
+
}
|
|
26523
26900
|
async closeRuntime(sessionId) {
|
|
26901
|
+
const deadline = performance.now() + this.closeGraceMs;
|
|
26524
26902
|
const runtime = this.sessions.get(sessionId);
|
|
26525
26903
|
if (!runtime)
|
|
26526
|
-
return;
|
|
26904
|
+
return {};
|
|
26527
26905
|
runtime.session.closed = true;
|
|
26528
26906
|
runtime.lifetime.abort();
|
|
26529
26907
|
const turn = runtime.session.activeTurn;
|
|
26530
26908
|
if (turn) {
|
|
26531
26909
|
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);
|
|
26910
|
+
await within(turn.finished, Math.max(0, this.closeGraceMs / 2));
|
|
26911
|
+
if (runtime.session.activeTurn === turn) {
|
|
26912
|
+
await within(this.reportIdle(runtime, turn, "cancelled", { usage: usageOf(runtime.session) }), Math.max(0, (deadline - performance.now()) / 2)).catch(() => {});
|
|
26543
26913
|
}
|
|
26914
|
+
turn.stop.abort();
|
|
26544
26915
|
}
|
|
26545
|
-
this.sessions.
|
|
26916
|
+
if (this.sessions.get(sessionId) === runtime)
|
|
26917
|
+
this.sessions.delete(sessionId);
|
|
26546
26918
|
this.codex.detachThread(sessionId);
|
|
26547
|
-
runtime.bridge.dispose();
|
|
26548
26919
|
runtime.client.dispose();
|
|
26549
|
-
|
|
26920
|
+
runtime.bridge.dispose();
|
|
26921
|
+
if (turn) {
|
|
26922
|
+
turn.resolveStarted(null);
|
|
26923
|
+
runtime.session.activeTurn = null;
|
|
26924
|
+
turn.resolveFinished();
|
|
26925
|
+
}
|
|
26926
|
+
const unsubscribe = this.unsubscribeThread(sessionId);
|
|
26927
|
+
let remoteUnsubscribe = "confirmed";
|
|
26928
|
+
try {
|
|
26929
|
+
if (!await within(unsubscribe, Math.max(0, deadline - performance.now()))) {
|
|
26930
|
+
remoteUnsubscribe = "timed_out";
|
|
26931
|
+
}
|
|
26932
|
+
} catch (error40) {
|
|
26933
|
+
remoteUnsubscribe = "failed";
|
|
26550
26934
|
logger.error("thread/unsubscribe failed", error40, { sessionId });
|
|
26551
|
-
}
|
|
26935
|
+
}
|
|
26936
|
+
if (remoteUnsubscribe !== "confirmed")
|
|
26937
|
+
logger.log("session close detached locally", { sessionId, remoteUnsubscribe });
|
|
26938
|
+
return { _meta: { codex: { close: { attempted: true, localDetached: true, remoteUnsubscribe } } } };
|
|
26939
|
+
}
|
|
26940
|
+
unsubscribeThread(sessionId) {
|
|
26941
|
+
const unsubscribe = this.codex.threadUnsubscribe({ threadId: sessionId });
|
|
26942
|
+
this.pendingUnsubscribes.set(sessionId, unsubscribe);
|
|
26943
|
+
const release = () => {
|
|
26944
|
+
if (this.pendingUnsubscribes.get(sessionId) === unsubscribe)
|
|
26945
|
+
this.pendingUnsubscribes.delete(sessionId);
|
|
26946
|
+
};
|
|
26947
|
+
unsubscribe.then(release, release);
|
|
26948
|
+
return unsubscribe;
|
|
26552
26949
|
}
|
|
26553
26950
|
async setSessionConfigOption(params) {
|
|
26951
|
+
return await this.admission(() => this.setSessionConfigOptionInternal(params), params.sessionId);
|
|
26952
|
+
}
|
|
26953
|
+
async setSessionConfigOptionInternal(params) {
|
|
26554
26954
|
const runtime = this.runtime(params.sessionId, "session/set_config_option");
|
|
26555
26955
|
await this.withCodex(() => applyConfigOption(runtime.session, this.codex, params));
|
|
26556
26956
|
const configOptions = sessionConfigOptions(runtime.session);
|
|
@@ -26565,11 +26965,19 @@ class CodexAgent {
|
|
|
26565
26965
|
return runtime;
|
|
26566
26966
|
}
|
|
26567
26967
|
async prompt(params) {
|
|
26968
|
+
return await this.promptInternal(params);
|
|
26969
|
+
}
|
|
26970
|
+
async promptInternal(params, allowReentry = false) {
|
|
26971
|
+
this.assertRoutingAvailable();
|
|
26972
|
+
if (!allowReentry && this.sessionMutations.has(params.sessionId))
|
|
26973
|
+
throw RequestError.invalidRequest({ sessionId: params.sessionId }, "Session lifecycle or configuration work is in progress; retry when it finishes");
|
|
26568
26974
|
const runtime = this.runtime(params.sessionId, "session/prompt");
|
|
26569
|
-
|
|
26975
|
+
if (runtime.stale)
|
|
26976
|
+
throw RequestError.invalidRequest({ sessionId: params.sessionId, _meta: { codex: { routing: { stale: true } } } }, "Session routing is stale; resume the session or successfully change providers before prompting");
|
|
26570
26977
|
if (!Array.isArray(params.prompt) || params.prompt.length === 0) {
|
|
26571
26978
|
throw RequestError.invalidParams(undefined, "prompt must contain at least one content block");
|
|
26572
26979
|
}
|
|
26980
|
+
const { session } = runtime;
|
|
26573
26981
|
const model = findModel(session.catalog, session.model.model);
|
|
26574
26982
|
if (!modelSupportsImages(model) && params.prompt.some((block) => block.type === "image")) {
|
|
26575
26983
|
throw RequestError.invalidParams({ model: session.model.model }, "The current model does not support image input");
|
|
@@ -26592,23 +27000,28 @@ class CodexAgent {
|
|
|
26592
27000
|
await this.codex.turnSteer({ threadId: runtime.session.id, expectedTurnId: turnId, input: toUserInput(params.prompt) });
|
|
26593
27001
|
return { _meta: { codex: { steered: turnId } } };
|
|
26594
27002
|
} catch (error40) {
|
|
27003
|
+
if (this.completedTurns.get(turn) === turnId || runtime.session.activeTurn !== turn) {
|
|
27004
|
+
await turn.finished;
|
|
27005
|
+
return await this.promptInternal(params, true);
|
|
27006
|
+
}
|
|
26595
27007
|
throw RequestError.invalidRequest({ sessionId: params.sessionId, turnId }, `Could not steer the running turn: ${errorMessage(error40)}`);
|
|
26596
27008
|
}
|
|
26597
27009
|
}
|
|
26598
|
-
async runPrompt(runtime, turn, params) {
|
|
27010
|
+
async runPrompt(runtime, turn, params, observed, items = []) {
|
|
26599
27011
|
const { session, bridge } = runtime;
|
|
26600
27012
|
bridge.beginTurn();
|
|
27013
|
+
if (items.length)
|
|
27014
|
+
runtime.queue = runtime.queue.then(() => bridge.restore(items));
|
|
26601
27015
|
try {
|
|
26602
27016
|
const command = parseCommand(params.prompt);
|
|
26603
27017
|
const outcome = command ? resolveCommand(command, session) : { kind: "prompt" };
|
|
26604
|
-
let completed = await abortable(this.executePrompt(runtime, turn, command, outcome, params), turn.stop.signal);
|
|
27018
|
+
let completed = await abortable(observed ?? this.executePrompt(runtime, turn, command, outcome, params), turn.stop.signal);
|
|
26605
27019
|
await this.drain(runtime);
|
|
26606
27020
|
await bridge.flush();
|
|
26607
|
-
if (completed?.turn.status === "completed" && !turn.abort.signal.aborted) {
|
|
27021
|
+
if (completed?.turn.status === "completed" && !turn.abort.signal.aborted && !this.exitHandled) {
|
|
26608
27022
|
completed = await abortable(this.maybeImplementPlan(runtime, turn, completed), turn.stop.signal);
|
|
26609
27023
|
}
|
|
26610
27024
|
if (completed?.turn.status === "interrupted" || turn.abort.signal.aborted) {
|
|
26611
|
-
await bridge.cancelOpenToolCalls();
|
|
26612
27025
|
await this.reportIdle(runtime, turn, "cancelled", { usage: usageOf(session) });
|
|
26613
27026
|
return;
|
|
26614
27027
|
}
|
|
@@ -26624,8 +27037,13 @@ class CodexAgent {
|
|
|
26624
27037
|
await this.publishFallbackTitle(runtime, promptTitle(params.prompt));
|
|
26625
27038
|
await this.reportIdle(runtime, turn, "end_turn", { usage: usageOf(session) });
|
|
26626
27039
|
} catch (error40) {
|
|
27040
|
+
if (this.terminalTurns.has(turn)) {
|
|
27041
|
+
logger.error("Terminal update failed", error40, { sessionId: session.id });
|
|
27042
|
+
return;
|
|
27043
|
+
}
|
|
27044
|
+
if (turn.stop.signal.aborted && session.closed)
|
|
27045
|
+
return;
|
|
26627
27046
|
if (turn.abort.signal.aborted || session.closed) {
|
|
26628
|
-
await bridge.cancelOpenToolCalls().catch(() => {});
|
|
26629
27047
|
await this.reportIdle(runtime, turn, "cancelled", { usage: usageOf(session) }).catch(() => {});
|
|
26630
27048
|
return;
|
|
26631
27049
|
}
|
|
@@ -26671,23 +27089,42 @@ class CodexAgent {
|
|
|
26671
27089
|
const { session } = runtime;
|
|
26672
27090
|
const model = findModel(session.catalog, session.model.model);
|
|
26673
27091
|
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
|
-
|
|
27092
|
+
let startSent = false;
|
|
27093
|
+
const beforeStart = new AbortController;
|
|
27094
|
+
const cancelBeforeStart = () => {
|
|
27095
|
+
if (!startSent)
|
|
27096
|
+
beforeStart.abort(turn.abort.signal.reason);
|
|
27097
|
+
};
|
|
27098
|
+
turn.abort.signal.addEventListener("abort", cancelBeforeStart, { once: true });
|
|
27099
|
+
if (turn.abort.signal.aborted)
|
|
27100
|
+
cancelBeforeStart();
|
|
27101
|
+
try {
|
|
27102
|
+
const result = await abortable(this.withSkillsContext(session.cwd, session.additionalDirectories, async (skills) => {
|
|
27103
|
+
if (this.publishedSkills.get(runtime) !== skills)
|
|
27104
|
+
this.publishAvailableCommands(runtime, skills);
|
|
27105
|
+
if (turn.abort.signal.aborted || turn.stop.signal.aborted)
|
|
27106
|
+
return { completed: Promise.resolve(interruptedTurn(session.id)) };
|
|
27107
|
+
startSent = true;
|
|
27108
|
+
const completed = this.withCodex(() => this.codex.runTurn({
|
|
27109
|
+
threadId: session.id,
|
|
27110
|
+
input: toUserInput(prompt),
|
|
27111
|
+
approvalPolicy: session.mode.approvalPolicy,
|
|
27112
|
+
approvalsReviewer: session.mode.approvalsReviewer,
|
|
27113
|
+
sandboxPolicy: withWritableRoots(session.mode.sandboxPolicy, session.additionalDirectories),
|
|
27114
|
+
model: session.model.model,
|
|
27115
|
+
effort: session.model.effort,
|
|
27116
|
+
summary: disableSummary ? "none" : "auto",
|
|
27117
|
+
serviceTier: session.fastMode ? FAST_SERVICE_TIER : null
|
|
27118
|
+
}, (turnId) => {
|
|
27119
|
+
this.turnStarted(runtime, turn, turnId, session.id);
|
|
27120
|
+
}, turn.stop.signal));
|
|
27121
|
+
await Promise.race([turn.started, completed]);
|
|
27122
|
+
return { completed };
|
|
27123
|
+
}), beforeStart.signal);
|
|
27124
|
+
return await result.completed;
|
|
27125
|
+
} finally {
|
|
27126
|
+
turn.abort.signal.removeEventListener("abort", cancelBeforeStart);
|
|
27127
|
+
}
|
|
26691
27128
|
}
|
|
26692
27129
|
async runCompaction(runtime, turn) {
|
|
26693
27130
|
const threadId = runtime.session.id;
|
|
@@ -26711,6 +27148,7 @@ class CodexAgent {
|
|
|
26711
27148
|
const plan = runtime.bridge.takeCompletedPlan();
|
|
26712
27149
|
if (!plan || runtime.session.collaborationMode !== PLAN_COLLABORATION_MODE)
|
|
26713
27150
|
return completed;
|
|
27151
|
+
this.completedTurns.delete(turn);
|
|
26714
27152
|
const signal = AbortSignal.any([turn.abort.signal, turn.stop.signal]);
|
|
26715
27153
|
const approved = await this.requestPlanApproval(runtime, plan, signal);
|
|
26716
27154
|
if (!approved || signal.aborted)
|
|
@@ -26766,11 +27204,16 @@ class CodexAgent {
|
|
|
26766
27204
|
}
|
|
26767
27205
|
}
|
|
26768
27206
|
async reportIdle(runtime, turn, reason, extra) {
|
|
27207
|
+
if (this.terminalTurns.has(turn))
|
|
27208
|
+
return;
|
|
27209
|
+
this.terminalTurns.add(turn);
|
|
27210
|
+
await runtime.bridge.finishOpenToolCalls(reason === "cancelled" ? "cancelled" : reason === "end_turn" ? "completed" : "failed");
|
|
26769
27211
|
if (runtime.session.activeTurn === turn)
|
|
26770
27212
|
runtime.session.activeTurn = null;
|
|
26771
27213
|
await runtime.client.reportIdle(reason, extra);
|
|
26772
27214
|
}
|
|
26773
27215
|
async reportTurnFailure(runtime, turn, error40) {
|
|
27216
|
+
const { stopReason, ...classification } = classifyTurnError(error40.codexErrorInfo);
|
|
26774
27217
|
const message = error40.additionalDetails ? `${error40.message}
|
|
26775
27218
|
|
|
26776
27219
|
${error40.additionalDetails}` : error40.message;
|
|
@@ -26778,11 +27221,11 @@ ${error40.additionalDetails}` : error40.message;
|
|
|
26778
27221
|
sessionUpdate: "agent_message_chunk",
|
|
26779
27222
|
messageId: `codex-error:${runtime.session.id}:${Date.now()}`,
|
|
26780
27223
|
content: { type: "text", text: message },
|
|
26781
|
-
_meta: { codex: { error: { message: error40.message, codexErrorInfo: error40.codexErrorInfo, additionalDetails: error40.additionalDetails } } }
|
|
27224
|
+
_meta: { codex: { error: { ...classification, message: error40.message, codexErrorInfo: error40.codexErrorInfo, additionalDetails: error40.additionalDetails } } }
|
|
26782
27225
|
});
|
|
26783
|
-
await this.reportIdle(runtime, turn,
|
|
27226
|
+
await this.reportIdle(runtime, turn, stopReason, {
|
|
26784
27227
|
usage: usageOf(runtime.session),
|
|
26785
|
-
_meta: { codex: { error: { message: error40.message, codexErrorInfo: error40.codexErrorInfo } } }
|
|
27228
|
+
_meta: { codex: { error: { ...classification, message: error40.message, codexErrorInfo: error40.codexErrorInfo } } }
|
|
26786
27229
|
});
|
|
26787
27230
|
}
|
|
26788
27231
|
async publishFallbackTitle(runtime, title) {
|
|
@@ -26811,6 +27254,7 @@ ${error40.additionalDetails}` : error40.message;
|
|
|
26811
27254
|
let pending = turn.interrupts.get(key);
|
|
26812
27255
|
if (!pending) {
|
|
26813
27256
|
pending = this.codex.turnInterrupt({ threadId: turn.threadId, turnId }).then(() => {}, (error40) => {
|
|
27257
|
+
turn.interrupts.delete(key);
|
|
26814
27258
|
logger.error("turn/interrupt failed", error40, { sessionId: runtime.session.id, turnId });
|
|
26815
27259
|
});
|
|
26816
27260
|
turn.interrupts.set(key, pending);
|
|
@@ -26824,13 +27268,56 @@ ${error40.additionalDetails}` : error40.message;
|
|
|
26824
27268
|
if (turn.abort.signal.aborted)
|
|
26825
27269
|
this.sendInterrupt(runtime, turn, turnId);
|
|
26826
27270
|
}
|
|
26827
|
-
|
|
26828
|
-
const
|
|
26829
|
-
|
|
26830
|
-
|
|
26831
|
-
|
|
27271
|
+
withSkillsContext(cwd, additionalDirectories, operation) {
|
|
27272
|
+
const run = async () => {
|
|
27273
|
+
const roots = additionalDirectories.map((root) => path6.join(root, ".agents", "skills"));
|
|
27274
|
+
if (roots.length !== this.skillRoots.length || roots.some((root, index) => root !== this.skillRoots[index])) {
|
|
27275
|
+
this.changingSkillRoots = true;
|
|
27276
|
+
try {
|
|
27277
|
+
await this.codex.skillsExtraRootsSet({ extraRoots: roots });
|
|
27278
|
+
} finally {
|
|
27279
|
+
this.changingSkillRoots = false;
|
|
27280
|
+
}
|
|
27281
|
+
this.skillRoots = roots;
|
|
27282
|
+
this.skillSnapshots.clear();
|
|
27283
|
+
}
|
|
27284
|
+
const skills = await this.loadSkills(cwd, additionalDirectories);
|
|
27285
|
+
return await operation(skills);
|
|
27286
|
+
};
|
|
27287
|
+
const result = this.skillsQueue.then(run, run);
|
|
27288
|
+
this.skillsQueue = result.then(() => {}, () => {});
|
|
27289
|
+
return result;
|
|
27290
|
+
}
|
|
27291
|
+
async loadSkills(cwd, additionalDirectories) {
|
|
27292
|
+
const key = JSON.stringify([cwd, ...additionalDirectories]);
|
|
27293
|
+
let skills = this.skillSnapshots.get(key);
|
|
27294
|
+
if (!skills) {
|
|
27295
|
+
const generation = this.skillsGeneration;
|
|
27296
|
+
skills = await this.withCodex(() => this.codex.skillsList({ cwds: [cwd, ...additionalDirectories], forceReload: true }));
|
|
27297
|
+
if (generation === this.skillsGeneration) {
|
|
27298
|
+
if (this.skillSnapshots.size >= 64)
|
|
27299
|
+
this.skillSnapshots.delete(this.skillSnapshots.keys().next().value);
|
|
27300
|
+
this.skillSnapshots.set(key, skills);
|
|
27301
|
+
}
|
|
27302
|
+
}
|
|
27303
|
+
return skills;
|
|
27304
|
+
}
|
|
27305
|
+
async refreshAvailableCommands(runtime) {
|
|
27306
|
+
const run = async () => {
|
|
27307
|
+
const roots = runtime.session.additionalDirectories.map((root) => path6.join(root, ".agents", "skills"));
|
|
27308
|
+
if (runtime.session.closed || roots.length !== this.skillRoots.length || roots.some((root, i) => root !== this.skillRoots[i]))
|
|
27309
|
+
return;
|
|
27310
|
+
return await this.loadSkills(runtime.session.cwd, runtime.session.additionalDirectories);
|
|
27311
|
+
};
|
|
27312
|
+
const pending = this.skillsQueue.then(run, run);
|
|
27313
|
+
this.skillsQueue = pending.then(() => {}, () => {});
|
|
27314
|
+
try {
|
|
27315
|
+
const skills = await pending;
|
|
27316
|
+
if (skills && !runtime.session.closed)
|
|
27317
|
+
await this.publishAvailableCommands(runtime, skills);
|
|
27318
|
+
} catch (error40) {
|
|
27319
|
+
logger.error("refreshing available commands failed", error40, { sessionId: runtime.session.id });
|
|
26832
27320
|
}
|
|
26833
|
-
await this.codex.skillsList({ cwds: [cwd, ...additionalDirectories], forceReload: true });
|
|
26834
27321
|
}
|
|
26835
27322
|
async configuredMcpServerNames(cwd) {
|
|
26836
27323
|
const response = await this.codex.configRead({ includeLayers: true, cwd });
|
|
@@ -26843,13 +27330,13 @@ ${error40.additionalDetails}` : error40.message;
|
|
|
26843
27330
|
}
|
|
26844
27331
|
return names;
|
|
26845
27332
|
}
|
|
26846
|
-
async resolveModelProvider() {
|
|
26847
|
-
const routed =
|
|
27333
|
+
async resolveModelProvider(routing = this.providers) {
|
|
27334
|
+
const routed = routing.modelProvider();
|
|
26848
27335
|
if (routed)
|
|
26849
27336
|
return routed;
|
|
26850
27337
|
const config2 = await this.codex.configRead({ includeLayers: false });
|
|
26851
27338
|
const provider = config2.config["model_provider"];
|
|
26852
|
-
return typeof provider === "string" && provider.length > 0 ? provider :
|
|
27339
|
+
return typeof provider === "string" && provider.length > 0 ? provider : OPENAI_PROVIDER_ID;
|
|
26853
27340
|
}
|
|
26854
27341
|
async withCodex(operation) {
|
|
26855
27342
|
try {
|
|
@@ -26889,7 +27376,7 @@ ${stderr}` : ""}`);
|
|
|
26889
27376
|
this.exitHandled = true;
|
|
26890
27377
|
for (const runtime of this.sessions.values()) {
|
|
26891
27378
|
const turn = runtime.session.activeTurn;
|
|
26892
|
-
if (!turn)
|
|
27379
|
+
if (!turn || this.completedTurns.get(turn) === turn.turnId)
|
|
26893
27380
|
continue;
|
|
26894
27381
|
const stderr = this.process?.recentStderr() || null;
|
|
26895
27382
|
turn.stop.abort(Object.assign(new Error("Connection to Codex was lost"), { additionalDetails: stderr }));
|
|
@@ -26920,6 +27407,16 @@ function interruptedTurn(threadId) {
|
|
|
26920
27407
|
turn: { id: "", items: [], itemsView: "notLoaded", status: "interrupted", error: null, startedAt: null, completedAt: null, durationMs: null }
|
|
26921
27408
|
};
|
|
26922
27409
|
}
|
|
27410
|
+
function objectParams() {
|
|
27411
|
+
return (raw) => {
|
|
27412
|
+
if (raw === undefined || raw === null)
|
|
27413
|
+
return {};
|
|
27414
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
27415
|
+
throw RequestError.invalidParams({ params: raw }, "expected a params object");
|
|
27416
|
+
}
|
|
27417
|
+
return raw;
|
|
27418
|
+
};
|
|
27419
|
+
}
|
|
26923
27420
|
function parseSessionIdParams(raw) {
|
|
26924
27421
|
const sessionId = raw?.sessionId;
|
|
26925
27422
|
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
@@ -26953,6 +27450,19 @@ function seedItem(message) {
|
|
|
26953
27450
|
content: [{ type: message.role === "user" ? "input_text" : "output_text", text: message.text }]
|
|
26954
27451
|
};
|
|
26955
27452
|
}
|
|
27453
|
+
async function within(operation, ms) {
|
|
27454
|
+
let timer;
|
|
27455
|
+
try {
|
|
27456
|
+
return await Promise.race([
|
|
27457
|
+
operation.then(() => true),
|
|
27458
|
+
new Promise((resolve) => {
|
|
27459
|
+
timer = setTimeout(() => resolve(false), ms);
|
|
27460
|
+
})
|
|
27461
|
+
]);
|
|
27462
|
+
} finally {
|
|
27463
|
+
clearTimeout(timer);
|
|
27464
|
+
}
|
|
27465
|
+
}
|
|
26956
27466
|
|
|
26957
27467
|
// src/agent/createAgent.ts
|
|
26958
27468
|
function createAgentApp(options) {
|
|
@@ -26964,7 +27474,7 @@ function createAgentApp(options) {
|
|
|
26964
27474
|
};
|
|
26965
27475
|
return agent({ name: options.info.name }).onConnect((connection) => {
|
|
26966
27476
|
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));
|
|
27477
|
+
}).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("_codex/skills_list", { parse: objectParams() }, (ctx) => current().skillsList(ctx.params)).onRequest("_codex/skills_config_write", { parse: objectParams() }, (ctx) => current().skillsConfigWrite(ctx.params)).onRequest("_codex/plugin_list", { parse: objectParams() }, (ctx) => current().pluginList(ctx.params)).onRequest("_codex/plugin_installed", { parse: objectParams() }, (ctx) => current().pluginInstalled(ctx.params)).onRequest("_codex/plugin_install", { parse: objectParams() }, (ctx) => current().pluginInstall(ctx.params)).onRequest("_codex/plugin_uninstall", { parse: objectParams() }, (ctx) => current().pluginUninstall(ctx.params)).onRequest("_codex/plugin_read", { parse: objectParams() }, (ctx) => current().pluginRead(ctx.params)).onRequest("_codex/marketplace_add", { parse: objectParams() }, (ctx) => current().marketplaceAdd(ctx.params)).onRequest("_codex/marketplace_remove", { parse: objectParams() }, (ctx) => current().marketplaceRemove(ctx.params)).onRequest("_codex/marketplace_upgrade", { parse: objectParams() }, (ctx) => current().marketplaceUpgrade(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
27478
|
}
|
|
26969
27479
|
|
|
26970
27480
|
// src/codex/AppServerClient.ts
|
|
@@ -26984,6 +27494,7 @@ class AppServerClient {
|
|
|
26984
27494
|
earlyTurnCompletions = new Map;
|
|
26985
27495
|
startingThreads = new Map;
|
|
26986
27496
|
startingReviews = 0;
|
|
27497
|
+
modelCatalog = null;
|
|
26987
27498
|
mcpStartupStates = new Map;
|
|
26988
27499
|
mcpStartupWaiters = [];
|
|
26989
27500
|
notificationWaiters = [];
|
|
@@ -27017,6 +27528,9 @@ class AppServerClient {
|
|
|
27017
27528
|
return handler ? await handler.handleUserInput(params) : { answers: {} };
|
|
27018
27529
|
});
|
|
27019
27530
|
}
|
|
27531
|
+
get disconnectSignal() {
|
|
27532
|
+
return this.disconnected.signal;
|
|
27533
|
+
}
|
|
27020
27534
|
attachThread(threadId, handlers) {
|
|
27021
27535
|
this.threadHandlers.set(threadId, handlers.notification);
|
|
27022
27536
|
this.approvalHandlers.set(threadId, handlers.approval);
|
|
@@ -27057,7 +27571,18 @@ class AppServerClient {
|
|
|
27057
27571
|
scope.abort();
|
|
27058
27572
|
}
|
|
27059
27573
|
}
|
|
27574
|
+
observers = new Set;
|
|
27575
|
+
observeNotifications(handler) {
|
|
27576
|
+
this.observers.add(handler);
|
|
27577
|
+
return () => {
|
|
27578
|
+
this.observers.delete(handler);
|
|
27579
|
+
};
|
|
27580
|
+
}
|
|
27060
27581
|
dispatchNotification(notification) {
|
|
27582
|
+
if (notification.method === "account/updated")
|
|
27583
|
+
this.modelCatalog = null;
|
|
27584
|
+
for (const observer of this.observers)
|
|
27585
|
+
observer(notification);
|
|
27061
27586
|
if (this.notificationWaiters.length > 0) {
|
|
27062
27587
|
const remaining = [];
|
|
27063
27588
|
for (const waiter of this.notificationWaiters) {
|
|
@@ -27126,10 +27651,26 @@ class AppServerClient {
|
|
|
27126
27651
|
this.earlyTurnCompletions.delete(key);
|
|
27127
27652
|
return Promise.resolve(early);
|
|
27128
27653
|
}
|
|
27129
|
-
const
|
|
27130
|
-
|
|
27654
|
+
const lifetime = signal ? AbortSignal.any([signal, this.disconnected.signal]) : this.disconnected.signal;
|
|
27655
|
+
return new Promise((resolve, reject) => {
|
|
27656
|
+
const cleanup = () => {
|
|
27657
|
+
lifetime.removeEventListener("abort", aborted2);
|
|
27658
|
+
this.turnCompletionWaiters.delete(key);
|
|
27659
|
+
};
|
|
27660
|
+
const aborted2 = () => {
|
|
27661
|
+
cleanup();
|
|
27662
|
+
reject(lifetime.reason);
|
|
27663
|
+
};
|
|
27664
|
+
if (lifetime.aborted) {
|
|
27665
|
+
reject(lifetime.reason);
|
|
27666
|
+
return;
|
|
27667
|
+
}
|
|
27668
|
+
lifetime.addEventListener("abort", aborted2, { once: true });
|
|
27669
|
+
this.turnCompletionWaiters.set(key, (event) => {
|
|
27670
|
+
cleanup();
|
|
27671
|
+
resolve(event);
|
|
27672
|
+
});
|
|
27131
27673
|
});
|
|
27132
|
-
return abortable(completed, signal ? AbortSignal.any([signal, this.disconnected.signal]) : this.disconnected.signal).finally(() => this.turnCompletionWaiters.delete(key));
|
|
27133
27674
|
}
|
|
27134
27675
|
resolveTurnInterrupted(threadId, turnId) {
|
|
27135
27676
|
this.recordTurnCompleted({
|
|
@@ -27279,13 +27820,34 @@ class AppServerClient {
|
|
|
27279
27820
|
modelList(params) {
|
|
27280
27821
|
return this.send({ method: "model/list", params });
|
|
27281
27822
|
}
|
|
27282
|
-
|
|
27823
|
+
invalidateModels() {
|
|
27824
|
+
this.modelCatalog = null;
|
|
27825
|
+
}
|
|
27826
|
+
allModels(refresh = false) {
|
|
27827
|
+
if (!refresh && this.modelCatalog && this.modelCatalog.expires > performance.now())
|
|
27828
|
+
return this.modelCatalog.pending;
|
|
27829
|
+
const pending = this.loadModels();
|
|
27830
|
+
const entry = { expires: performance.now() + 30000, pending };
|
|
27831
|
+
this.modelCatalog = entry;
|
|
27832
|
+
pending.catch(() => {
|
|
27833
|
+
if (this.modelCatalog === entry)
|
|
27834
|
+
this.modelCatalog = null;
|
|
27835
|
+
});
|
|
27836
|
+
return pending;
|
|
27837
|
+
}
|
|
27838
|
+
async loadModels() {
|
|
27283
27839
|
const models = [];
|
|
27840
|
+
const cursors = new Set;
|
|
27284
27841
|
let cursor = null;
|
|
27285
27842
|
do {
|
|
27286
27843
|
const page = await this.modelList({ cursor, limit: null });
|
|
27287
27844
|
models.push(...page.data);
|
|
27288
27845
|
cursor = page.nextCursor;
|
|
27846
|
+
if (cursor) {
|
|
27847
|
+
if (cursors.has(cursor))
|
|
27848
|
+
throw new Error("Codex model/list returned a repeated pagination cursor; retry after restarting Codex");
|
|
27849
|
+
cursors.add(cursor);
|
|
27850
|
+
}
|
|
27289
27851
|
} while (cursor);
|
|
27290
27852
|
return models;
|
|
27291
27853
|
}
|
|
@@ -27295,6 +27857,33 @@ class AppServerClient {
|
|
|
27295
27857
|
skillsExtraRootsSet(params) {
|
|
27296
27858
|
return this.send({ method: "skills/extraRoots/set", params });
|
|
27297
27859
|
}
|
|
27860
|
+
skillsConfigWrite(params) {
|
|
27861
|
+
return this.send({ method: "skills/config/write", params });
|
|
27862
|
+
}
|
|
27863
|
+
pluginList(params) {
|
|
27864
|
+
return this.send({ method: "plugin/list", params });
|
|
27865
|
+
}
|
|
27866
|
+
pluginInstalled(params) {
|
|
27867
|
+
return this.send({ method: "plugin/installed", params });
|
|
27868
|
+
}
|
|
27869
|
+
pluginInstall(params) {
|
|
27870
|
+
return this.send({ method: "plugin/install", params });
|
|
27871
|
+
}
|
|
27872
|
+
pluginUninstall(params) {
|
|
27873
|
+
return this.send({ method: "plugin/uninstall", params });
|
|
27874
|
+
}
|
|
27875
|
+
pluginRead(params) {
|
|
27876
|
+
return this.send({ method: "plugin/read", params });
|
|
27877
|
+
}
|
|
27878
|
+
marketplaceAdd(params) {
|
|
27879
|
+
return this.send({ method: "marketplace/add", params });
|
|
27880
|
+
}
|
|
27881
|
+
marketplaceRemove(params) {
|
|
27882
|
+
return this.send({ method: "marketplace/remove", params });
|
|
27883
|
+
}
|
|
27884
|
+
marketplaceUpgrade(params) {
|
|
27885
|
+
return this.send({ method: "marketplace/upgrade", params });
|
|
27886
|
+
}
|
|
27298
27887
|
accountRead(params) {
|
|
27299
27888
|
return this.send({ method: "account/read", params });
|
|
27300
27889
|
}
|
|
@@ -27332,9 +27921,10 @@ function threadIdOf(notification) {
|
|
|
27332
27921
|
}
|
|
27333
27922
|
|
|
27334
27923
|
// src/codex/process.ts
|
|
27924
|
+
import { existsSync } from "fs";
|
|
27925
|
+
import path7 from "path";
|
|
27335
27926
|
import { spawn, spawnSync } from "child_process";
|
|
27336
27927
|
import { createRequire } from "module";
|
|
27337
|
-
var rpc = __toESM(require_main(), 1);
|
|
27338
27928
|
|
|
27339
27929
|
// src/codex/transport.ts
|
|
27340
27930
|
var import_node2 = __toESM(require_main(), 1);
|
|
@@ -27342,7 +27932,7 @@ import { StringDecoder } from "string_decoder";
|
|
|
27342
27932
|
function wireLog(value) {
|
|
27343
27933
|
return JSON.stringify(value, (key, nested) => {
|
|
27344
27934
|
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))
|
|
27935
|
+
if (/^(apikey|accesskey|accesstoken|refreshtoken|idtoken|secretaccesskey|sessiontoken|authorization|proxyauthorization|password|secret|token|cookie|setcookie|headers|httpheaders|env|queryparams)$/.test(normalized))
|
|
27346
27936
|
return "***";
|
|
27347
27937
|
return nested;
|
|
27348
27938
|
});
|
|
@@ -27415,7 +28005,7 @@ function createReader(readable) {
|
|
|
27415
28005
|
const closed = new import_node2.Emitter;
|
|
27416
28006
|
const partial2 = new import_node2.Emitter;
|
|
27417
28007
|
const decoder = new StringDecoder("utf8");
|
|
27418
|
-
let
|
|
28008
|
+
let fragments = [];
|
|
27419
28009
|
let ended = false;
|
|
27420
28010
|
let callback = null;
|
|
27421
28011
|
const deliver = (line) => {
|
|
@@ -27441,16 +28031,21 @@ function createReader(readable) {
|
|
|
27441
28031
|
callback?.(message);
|
|
27442
28032
|
};
|
|
27443
28033
|
const onData = (chunk) => {
|
|
27444
|
-
|
|
28034
|
+
const text2 = typeof chunk === "string" ? chunk : decoder.write(chunk);
|
|
28035
|
+
let offset = 0;
|
|
27445
28036
|
for (;; ) {
|
|
27446
|
-
const newline2 =
|
|
27447
|
-
|
|
28037
|
+
const newline2 = text2.indexOf(`
|
|
28038
|
+
`, offset);
|
|
27448
28039
|
if (newline2 < 0)
|
|
27449
28040
|
break;
|
|
27450
|
-
const
|
|
27451
|
-
|
|
28041
|
+
const part = text2.slice(offset, newline2);
|
|
28042
|
+
const line = fragments.length ? fragments.join("") + part : part;
|
|
28043
|
+
fragments = [];
|
|
27452
28044
|
deliver(line);
|
|
28045
|
+
offset = newline2 + 1;
|
|
27453
28046
|
}
|
|
28047
|
+
if (offset < text2.length)
|
|
28048
|
+
fragments.push(text2.slice(offset));
|
|
27454
28049
|
};
|
|
27455
28050
|
const onClose = () => {
|
|
27456
28051
|
if (ended)
|
|
@@ -27459,9 +28054,9 @@ function createReader(readable) {
|
|
|
27459
28054
|
closed.fire();
|
|
27460
28055
|
};
|
|
27461
28056
|
const onEnd = () => {
|
|
27462
|
-
|
|
27463
|
-
deliver(
|
|
27464
|
-
|
|
28057
|
+
fragments.push(decoder.end());
|
|
28058
|
+
deliver(fragments.join(""));
|
|
28059
|
+
fragments = [];
|
|
27465
28060
|
onClose();
|
|
27466
28061
|
};
|
|
27467
28062
|
const onError = (error40) => errors3.fire(error40);
|
|
@@ -27471,7 +28066,7 @@ function createReader(readable) {
|
|
|
27471
28066
|
readable.off("close", onClose);
|
|
27472
28067
|
readable.off("error", onError);
|
|
27473
28068
|
callback = null;
|
|
27474
|
-
|
|
28069
|
+
fragments = [];
|
|
27475
28070
|
};
|
|
27476
28071
|
return {
|
|
27477
28072
|
listen(receive) {
|
|
@@ -27493,6 +28088,92 @@ function createReader(readable) {
|
|
|
27493
28088
|
}
|
|
27494
28089
|
};
|
|
27495
28090
|
}
|
|
28091
|
+
function createCodexConnection(readable, writable) {
|
|
28092
|
+
const reader = createReader(readable);
|
|
28093
|
+
const writer = createWriter(writable);
|
|
28094
|
+
const closed = new import_node2.Emitter;
|
|
28095
|
+
let queued = 0;
|
|
28096
|
+
let eof = false;
|
|
28097
|
+
let finished = false;
|
|
28098
|
+
let timer;
|
|
28099
|
+
let resolveDrained;
|
|
28100
|
+
const drained = new Promise((resolve) => {
|
|
28101
|
+
resolveDrained = resolve;
|
|
28102
|
+
});
|
|
28103
|
+
const finish = () => {
|
|
28104
|
+
if (finished)
|
|
28105
|
+
return;
|
|
28106
|
+
finished = true;
|
|
28107
|
+
clearTimeout(timer);
|
|
28108
|
+
closed.fire();
|
|
28109
|
+
resolveDrained();
|
|
28110
|
+
};
|
|
28111
|
+
const check2 = () => {
|
|
28112
|
+
if (eof && queued === 0)
|
|
28113
|
+
setImmediate(() => {
|
|
28114
|
+
if (queued === 0)
|
|
28115
|
+
finish();
|
|
28116
|
+
});
|
|
28117
|
+
};
|
|
28118
|
+
reader.onClose(() => {
|
|
28119
|
+
eof = true;
|
|
28120
|
+
timer ??= setTimeout(() => {
|
|
28121
|
+
logger.error("Codex dispatch drain deadline exceeded", new Error(`Undispatched frames: ${queued}`));
|
|
28122
|
+
finish();
|
|
28123
|
+
}, 5000);
|
|
28124
|
+
check2();
|
|
28125
|
+
});
|
|
28126
|
+
const inputFailed = () => {
|
|
28127
|
+
if (finished || timer)
|
|
28128
|
+
return;
|
|
28129
|
+
timer = setTimeout(() => {
|
|
28130
|
+
logger.error("Codex stdout did not close after stdin", new Error("Transport shutdown deadline exceeded"));
|
|
28131
|
+
finish();
|
|
28132
|
+
}, 5000);
|
|
28133
|
+
};
|
|
28134
|
+
writer.onClose(inputFailed);
|
|
28135
|
+
const counted = (message) => !(("method" in message) && message.method === "$/cancelRequest");
|
|
28136
|
+
const connection = import_node2.createMessageConnection({
|
|
28137
|
+
...reader,
|
|
28138
|
+
onClose: closed.event,
|
|
28139
|
+
listen(receive) {
|
|
28140
|
+
return reader.listen((message) => {
|
|
28141
|
+
if (counted(message))
|
|
28142
|
+
queued += 1;
|
|
28143
|
+
receive(message);
|
|
28144
|
+
});
|
|
28145
|
+
}
|
|
28146
|
+
}, {
|
|
28147
|
+
...writer,
|
|
28148
|
+
onClose: () => ({ dispose() {} }),
|
|
28149
|
+
async write(message) {
|
|
28150
|
+
try {
|
|
28151
|
+
await writer.write(message);
|
|
28152
|
+
} catch (error40) {
|
|
28153
|
+
logger.error("Codex write failed; draining stdout before disconnect", error40);
|
|
28154
|
+
inputFailed();
|
|
28155
|
+
await drained;
|
|
28156
|
+
}
|
|
28157
|
+
}
|
|
28158
|
+
}, undefined, {
|
|
28159
|
+
messageStrategy: { handleMessage(message, next) {
|
|
28160
|
+
try {
|
|
28161
|
+
return next(message);
|
|
28162
|
+
} finally {
|
|
28163
|
+
if (counted(message))
|
|
28164
|
+
queued -= 1;
|
|
28165
|
+
check2();
|
|
28166
|
+
}
|
|
28167
|
+
} }
|
|
28168
|
+
});
|
|
28169
|
+
connection.onDispose(() => {
|
|
28170
|
+
finished = true;
|
|
28171
|
+
clearTimeout(timer);
|
|
28172
|
+
resolveDrained();
|
|
28173
|
+
closed.dispose();
|
|
28174
|
+
});
|
|
28175
|
+
return { connection, drained };
|
|
28176
|
+
}
|
|
27496
28177
|
|
|
27497
28178
|
// src/codex/process.ts
|
|
27498
28179
|
var STDERR_RETAIN_BYTES = 8 * 1024;
|
|
@@ -27519,7 +28200,29 @@ function resolveCodexLauncher(codexPath) {
|
|
|
27519
28200
|
return process.platform === "win32" ? { command: `"${codexPath}"`, prefixArgs: [], shell: true } : { command: codexPath, prefixArgs: [], shell: false };
|
|
27520
28201
|
}
|
|
27521
28202
|
try {
|
|
27522
|
-
|
|
28203
|
+
const require2 = createRequire(import.meta.url);
|
|
28204
|
+
const launcher = require2.resolve("@openai/codex/bin/codex.js");
|
|
28205
|
+
const platformRequire = createRequire(launcher);
|
|
28206
|
+
const targets = {
|
|
28207
|
+
"linux-x64": "x86_64-unknown-linux-musl",
|
|
28208
|
+
"linux-arm64": "aarch64-unknown-linux-musl",
|
|
28209
|
+
"darwin-x64": "x86_64-apple-darwin",
|
|
28210
|
+
"darwin-arm64": "aarch64-apple-darwin",
|
|
28211
|
+
"win32-x64": "x86_64-pc-windows-msvc",
|
|
28212
|
+
"win32-arm64": "aarch64-pc-windows-msvc"
|
|
28213
|
+
};
|
|
28214
|
+
const platform2 = `${process.platform}-${process.arch}`;
|
|
28215
|
+
const target = targets[platform2];
|
|
28216
|
+
if (!target)
|
|
28217
|
+
throw new Error(`Unsupported Codex platform: ${platform2}`);
|
|
28218
|
+
let vendor = path7.resolve(path7.dirname(launcher), "..", "vendor");
|
|
28219
|
+
try {
|
|
28220
|
+
vendor = path7.join(path7.dirname(platformRequire.resolve(`@openai/codex-${platform2}/package.json`)), "vendor");
|
|
28221
|
+
} catch {}
|
|
28222
|
+
const command = path7.join(vendor, target, "bin", process.platform === "win32" ? "codex.exe" : "codex");
|
|
28223
|
+
if (!existsSync(command))
|
|
28224
|
+
throw new Error(`Missing Codex platform binary: ${command}`);
|
|
28225
|
+
return { command, prefixArgs: [], shell: false };
|
|
27523
28226
|
} catch (error40) {
|
|
27524
28227
|
throw new Error("No Codex CLI: set CODEX_PATH to an installed Codex executable, or install the optional " + `@openai/codex dependency (${String(error40)})`);
|
|
27525
28228
|
}
|
|
@@ -27536,11 +28239,13 @@ function startCodexProcess(codexPath, env = process.env) {
|
|
|
27536
28239
|
stderr = (stderr + data.toString()).slice(-STDERR_RETAIN_BYTES);
|
|
27537
28240
|
logger.log("[codex stderr]", { data: data.toString() });
|
|
27538
28241
|
});
|
|
27539
|
-
const connection =
|
|
28242
|
+
const { connection, drained } = createCodexConnection(child.stdout, child.stdin);
|
|
27540
28243
|
connection.listen();
|
|
28244
|
+
drained.then(() => connection.dispose());
|
|
27541
28245
|
const exited = new Promise((resolve) => {
|
|
27542
|
-
child.on("
|
|
28246
|
+
child.on("close", async (code, signal) => {
|
|
27543
28247
|
logger.log("codex exited", { code, signal });
|
|
28248
|
+
await drained;
|
|
27544
28249
|
connection.dispose();
|
|
27545
28250
|
resolve(code);
|
|
27546
28251
|
});
|