replicas-engine 0.1.224 → 0.1.226
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.js +1904 -1486
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -20,6 +20,11 @@ import { readFileSync } from "fs";
|
|
|
20
20
|
import { homedir } from "os";
|
|
21
21
|
import { join } from "path";
|
|
22
22
|
|
|
23
|
+
// ../shared/src/type-guards.ts
|
|
24
|
+
function isRecord(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
// ../shared/src/agent.ts
|
|
24
29
|
var CODEX_REASONING_EFFORT_BY_THINKING_LEVEL = {
|
|
25
30
|
low: "low",
|
|
@@ -35,6 +40,7 @@ function codexReasoningEffortForThinkingLevel(thinkingLevel) {
|
|
|
35
40
|
var ACCEPTED_USER_MESSAGE_SOURCE = "replicas-chat-turn-accepted";
|
|
36
41
|
var USER_MESSAGE_ID_PAYLOAD_KEY = "replicasMessageId";
|
|
37
42
|
var CODEX_ASP_ITEM_ID_PAYLOAD_KEY = "codexAspItemId";
|
|
43
|
+
var CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE = "codex-asp-transcript-updated";
|
|
38
44
|
var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
|
|
39
45
|
var COMPACTION_STATUS_EVENT_TYPE = "compaction-status";
|
|
40
46
|
var CHAT_GOAL_EVENT_TYPE = "chat-goal";
|
|
@@ -1648,7 +1654,7 @@ var DEFAULT_WARM_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
|
1648
1654
|
var MAX_WARM_HOOK_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
1649
1655
|
var DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS = 1e5;
|
|
1650
1656
|
var HOOK_EXEC_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
1651
|
-
function
|
|
1657
|
+
function isRecord2(value) {
|
|
1652
1658
|
return typeof value === "object" && value !== null;
|
|
1653
1659
|
}
|
|
1654
1660
|
function clampWarmHookTimeoutMs(timeoutMs) {
|
|
@@ -1672,7 +1678,7 @@ function parseWarmHookConfig(value, filename = "replicas.json") {
|
|
|
1672
1678
|
if (typeof value === "string") {
|
|
1673
1679
|
return value;
|
|
1674
1680
|
}
|
|
1675
|
-
if (!
|
|
1681
|
+
if (!isRecord2(value)) {
|
|
1676
1682
|
throw new Error(`Invalid ${filename}: "warmHook" must be a string or object`);
|
|
1677
1683
|
}
|
|
1678
1684
|
if (!Array.isArray(value.commands) || !value.commands.every((entry) => typeof entry === "string")) {
|
|
@@ -1708,11 +1714,11 @@ function resolveWarmHookConfig(value) {
|
|
|
1708
1714
|
|
|
1709
1715
|
// ../shared/src/replicas-config.ts
|
|
1710
1716
|
var REPLICAS_CONFIG_FILENAMES = ["replicas.json", "replicas.yaml", "replicas.yml"];
|
|
1711
|
-
function
|
|
1717
|
+
function isRecord3(value) {
|
|
1712
1718
|
return typeof value === "object" && value !== null;
|
|
1713
1719
|
}
|
|
1714
1720
|
function parseReplicasConfig(value, filename = "replicas.json") {
|
|
1715
|
-
if (!
|
|
1721
|
+
if (!isRecord3(value)) {
|
|
1716
1722
|
throw new Error(`Invalid ${filename}: expected an object`);
|
|
1717
1723
|
}
|
|
1718
1724
|
const config = {};
|
|
@@ -1729,7 +1735,7 @@ function parseReplicasConfig(value, filename = "replicas.json") {
|
|
|
1729
1735
|
config.systemPrompt = value.systemPrompt;
|
|
1730
1736
|
}
|
|
1731
1737
|
if ("startHook" in value) {
|
|
1732
|
-
if (!
|
|
1738
|
+
if (!isRecord3(value.startHook)) {
|
|
1733
1739
|
throw new Error(`Invalid ${filename}: "startHook" must be an object with "commands" array`);
|
|
1734
1740
|
}
|
|
1735
1741
|
const { commands, timeout, separate } = value.startHook;
|
|
@@ -1767,7 +1773,7 @@ function isClaudeAuthErrorText(text) {
|
|
|
1767
1773
|
}
|
|
1768
1774
|
|
|
1769
1775
|
// ../shared/src/engine/environment.ts
|
|
1770
|
-
var DAYTONA_SNAPSHOT_ID = "28-05-2026-royal-york-
|
|
1776
|
+
var DAYTONA_SNAPSHOT_ID = "28-05-2026-royal-york-v3";
|
|
1771
1777
|
|
|
1772
1778
|
// ../shared/src/engine/types.ts
|
|
1773
1779
|
var DEFAULT_CHAT_TITLES = {
|
|
@@ -1806,6 +1812,15 @@ var IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
|
1806
1812
|
|
|
1807
1813
|
// ../shared/src/engine/v1.ts
|
|
1808
1814
|
var MERGED_MESSAGE_SEPARATOR = "\n\n<!-- replicas:merged -->\n\n";
|
|
1815
|
+
function normalizeCodexAspTranscriptStatus(status, failed = false) {
|
|
1816
|
+
if (failed || status === "failed") return "failed";
|
|
1817
|
+
if (status === "completed") return "completed";
|
|
1818
|
+
return "in_progress";
|
|
1819
|
+
}
|
|
1820
|
+
function isCodexAspTranscript(value) {
|
|
1821
|
+
if (!isRecord(value)) return false;
|
|
1822
|
+
return typeof value.threadId === "string" && typeof value.updatedAt === "string" && Array.isArray(value.turns);
|
|
1823
|
+
}
|
|
1809
1824
|
|
|
1810
1825
|
// ../shared/src/routes/codex.ts
|
|
1811
1826
|
var CODEX_AUTH_ENV_KEYS = [
|
|
@@ -1879,14 +1894,6 @@ var AUDIT_LOG_ACTION = {
|
|
|
1879
1894
|
};
|
|
1880
1895
|
var AUDIT_LOG_ACTIONS = Object.values(AUDIT_LOG_ACTION);
|
|
1881
1896
|
|
|
1882
|
-
// ../shared/src/object-store/types.ts
|
|
1883
|
-
var MEDIA_KIND = {
|
|
1884
|
-
IMAGE: "image",
|
|
1885
|
-
VIDEO: "video",
|
|
1886
|
-
AUDIO: "audio"
|
|
1887
|
-
};
|
|
1888
|
-
var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
|
|
1889
|
-
|
|
1890
1897
|
// ../shared/src/agent-event-utils.ts
|
|
1891
1898
|
function getUserMessage(event) {
|
|
1892
1899
|
return event.type === "event_msg" && event.payload.type === "user_message" && typeof event.payload.message === "string" ? event.payload.message : null;
|
|
@@ -1899,9 +1906,12 @@ function getUserMessageItemId(event) {
|
|
|
1899
1906
|
const itemId = event.payload[CODEX_ASP_ITEM_ID_PAYLOAD_KEY];
|
|
1900
1907
|
return typeof itemId === "string" ? itemId : null;
|
|
1901
1908
|
}
|
|
1909
|
+
function parseTimestampMs(timestamp) {
|
|
1910
|
+
const value = Date.parse(timestamp);
|
|
1911
|
+
return Number.isFinite(value) ? value : 0;
|
|
1912
|
+
}
|
|
1902
1913
|
function getEventTimestampMs(event) {
|
|
1903
|
-
|
|
1904
|
-
return Number.isNaN(value) ? 0 : value;
|
|
1914
|
+
return parseTimestampMs(event.timestamp);
|
|
1905
1915
|
}
|
|
1906
1916
|
function areSameUserMessageEvents(a, b) {
|
|
1907
1917
|
const aMessage = getUserMessage(a);
|
|
@@ -1915,21 +1925,17 @@ function areSameUserMessageEvents(a, b) {
|
|
|
1915
1925
|
if (aItemId || bItemId) return aItemId === bItemId;
|
|
1916
1926
|
return Math.abs(getEventTimestampMs(a) - getEventTimestampMs(b)) <= 3e4;
|
|
1917
1927
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
return merged;
|
|
1932
|
-
}
|
|
1928
|
+
|
|
1929
|
+
// ../shared/src/display-message/parsers/codex-asp-parser.ts
|
|
1930
|
+
var DUPLICATE_WINDOW_MS = 5 * 60 * 1e3;
|
|
1931
|
+
|
|
1932
|
+
// ../shared/src/object-store/types.ts
|
|
1933
|
+
var MEDIA_KIND = {
|
|
1934
|
+
IMAGE: "image",
|
|
1935
|
+
VIDEO: "video",
|
|
1936
|
+
AUDIO: "audio"
|
|
1937
|
+
};
|
|
1938
|
+
var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
|
|
1933
1939
|
|
|
1934
1940
|
// src/runtime-env-loader.ts
|
|
1935
1941
|
function loadRuntimeEnvFile() {
|
|
@@ -2013,8 +2019,7 @@ function loadEngineEnv() {
|
|
|
2013
2019
|
AWS_REGION: readEnv("AWS_REGION"),
|
|
2014
2020
|
REPLICAS_CLAUDE_AUTH_METHOD: parseClaudeAuthMethod(readEnv("REPLICAS_CLAUDE_AUTH_METHOD")),
|
|
2015
2021
|
REPLICAS_CODEX_AUTH_METHOD: parseCodexAuthMethod(readEnv("REPLICAS_CODEX_AUTH_METHOD")),
|
|
2016
|
-
REPLICAS_ENV_SYSTEM_PROMPT: readEnv("REPLICAS_ENV_SYSTEM_PROMPT")
|
|
2017
|
-
CODEX_ASP_ENABLED: readEnv("CODEX_ASP_ENABLED")?.toLowerCase() === "true"
|
|
2022
|
+
REPLICAS_ENV_SYSTEM_PROMPT: readEnv("REPLICAS_ENV_SYSTEM_PROMPT")
|
|
2018
2023
|
};
|
|
2019
2024
|
if (!IS_WARMING_MODE && !env.WORKSPACE_ID) {
|
|
2020
2025
|
console.error("WORKSPACE_ID is not set \u2014 this is required in normal (non-warming) mode");
|
|
@@ -2423,7 +2428,7 @@ import { join as join3 } from "path";
|
|
|
2423
2428
|
import { homedir as homedir3 } from "os";
|
|
2424
2429
|
|
|
2425
2430
|
// src/utils/type-guards.ts
|
|
2426
|
-
function
|
|
2431
|
+
function isRecord4(value) {
|
|
2427
2432
|
return typeof value === "object" && value !== null;
|
|
2428
2433
|
}
|
|
2429
2434
|
|
|
@@ -2455,10 +2460,10 @@ async function updateEngineState(updater) {
|
|
|
2455
2460
|
});
|
|
2456
2461
|
}
|
|
2457
2462
|
function isEngineRepoDiff(value) {
|
|
2458
|
-
return
|
|
2463
|
+
return isRecord4(value) && typeof value.added === "number" && typeof value.removed === "number";
|
|
2459
2464
|
}
|
|
2460
2465
|
function coerceRepoState(value) {
|
|
2461
|
-
if (!
|
|
2466
|
+
if (!isRecord4(value)) {
|
|
2462
2467
|
return null;
|
|
2463
2468
|
}
|
|
2464
2469
|
if (typeof value.name !== "string") return null;
|
|
@@ -2484,11 +2489,11 @@ function coerceRepoState(value) {
|
|
|
2484
2489
|
};
|
|
2485
2490
|
}
|
|
2486
2491
|
function coerceEngineState(value) {
|
|
2487
|
-
if (!
|
|
2492
|
+
if (!isRecord4(value)) {
|
|
2488
2493
|
return {};
|
|
2489
2494
|
}
|
|
2490
2495
|
const partial = {};
|
|
2491
|
-
if (
|
|
2496
|
+
if (isRecord4(value.repos)) {
|
|
2492
2497
|
const repos = {};
|
|
2493
2498
|
for (const [repoName, repoState] of Object.entries(value.repos)) {
|
|
2494
2499
|
const coerced = coerceRepoState(repoState);
|
|
@@ -3700,10 +3705,10 @@ import { homedir as homedir11 } from "os";
|
|
|
3700
3705
|
// src/utils/jsonl-reader.ts
|
|
3701
3706
|
import { readFile as readFile6 } from "fs/promises";
|
|
3702
3707
|
function isJsonlEvent(value) {
|
|
3703
|
-
if (!
|
|
3708
|
+
if (!isRecord4(value)) {
|
|
3704
3709
|
return false;
|
|
3705
3710
|
}
|
|
3706
|
-
return typeof value.timestamp === "string" && typeof value.type === "string" &&
|
|
3711
|
+
return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord4(value.payload);
|
|
3707
3712
|
}
|
|
3708
3713
|
function parseJsonlEvents(lines) {
|
|
3709
3714
|
const events = [];
|
|
@@ -5349,765 +5354,279 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5349
5354
|
}
|
|
5350
5355
|
};
|
|
5351
5356
|
|
|
5352
|
-
// src/managers/codex-
|
|
5353
|
-
import {
|
|
5354
|
-
import {
|
|
5355
|
-
import { existsSync as existsSync6 } from "fs";
|
|
5356
|
-
import { join as join14 } from "path";
|
|
5357
|
-
import { homedir as homedir12 } from "os";
|
|
5358
|
-
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
5359
|
-
|
|
5360
|
-
// src/utils/codex-quota.ts
|
|
5361
|
-
function buildCodexRateLimitsSnapshot(fields) {
|
|
5362
|
-
if (fields.unlimited === true) return null;
|
|
5363
|
-
let state = "ok";
|
|
5364
|
-
if (fields.hasCredits === false) {
|
|
5365
|
-
state = "out_of_credits";
|
|
5366
|
-
} else if (fields.rateLimitResetType !== null) {
|
|
5367
|
-
state = "rate_limited";
|
|
5368
|
-
}
|
|
5369
|
-
return {
|
|
5370
|
-
state,
|
|
5371
|
-
balance: fields.balance,
|
|
5372
|
-
rateLimitResetType: fields.rateLimitResetType,
|
|
5373
|
-
planType: fields.planType
|
|
5374
|
-
};
|
|
5375
|
-
}
|
|
5376
|
-
function extractCodexRateLimitsSnapshotFromJsonl(parsed) {
|
|
5377
|
-
if (!isRecord3(parsed)) return null;
|
|
5378
|
-
const payload = isRecord3(parsed.payload) ? parsed.payload : parsed;
|
|
5379
|
-
const rateLimits = isRecord3(payload.rate_limits) ? payload.rate_limits : null;
|
|
5380
|
-
if (!rateLimits) return null;
|
|
5381
|
-
const credits = isRecord3(rateLimits.credits) ? rateLimits.credits : null;
|
|
5382
|
-
const hasCredits = credits && typeof credits.has_credits === "boolean" ? credits.has_credits : null;
|
|
5383
|
-
const unlimited = credits && typeof credits.unlimited === "boolean" ? credits.unlimited : null;
|
|
5384
|
-
const balance = credits && typeof credits.balance === "string" ? credits.balance : null;
|
|
5385
|
-
const rateLimitResetType = typeof rateLimits.rate_limit_reached_type === "string" && rateLimits.rate_limit_reached_type.length > 0 ? rateLimits.rate_limit_reached_type : null;
|
|
5386
|
-
const planType = typeof rateLimits.plan_type === "string" ? rateLimits.plan_type : null;
|
|
5387
|
-
return buildCodexRateLimitsSnapshot({
|
|
5388
|
-
unlimited,
|
|
5389
|
-
hasCredits,
|
|
5390
|
-
balance,
|
|
5391
|
-
rateLimitResetType,
|
|
5392
|
-
planType
|
|
5393
|
-
});
|
|
5394
|
-
}
|
|
5395
|
-
var CodexQuotaStatusTracker = class {
|
|
5396
|
-
lastEmittedQuotaState = "ok";
|
|
5397
|
-
latestQuotaSnapshot = null;
|
|
5398
|
-
quotaBlocked = false;
|
|
5399
|
-
get blocked() {
|
|
5400
|
-
return this.quotaBlocked;
|
|
5401
|
-
}
|
|
5402
|
-
get latestSnapshot() {
|
|
5403
|
-
return this.latestQuotaSnapshot;
|
|
5404
|
-
}
|
|
5405
|
-
prime(snapshot) {
|
|
5406
|
-
this.latestQuotaSnapshot = snapshot;
|
|
5407
|
-
this.quotaBlocked = snapshot.state === "out_of_credits";
|
|
5408
|
-
}
|
|
5409
|
-
apply(snapshot, force = false) {
|
|
5410
|
-
const stateChanged = snapshot.state !== this.lastEmittedQuotaState;
|
|
5411
|
-
if (!stateChanged && !force) {
|
|
5412
|
-
return null;
|
|
5413
|
-
}
|
|
5414
|
-
this.lastEmittedQuotaState = snapshot.state;
|
|
5415
|
-
this.quotaBlocked = snapshot.state === "out_of_credits";
|
|
5416
|
-
this.latestQuotaSnapshot = snapshot;
|
|
5417
|
-
const payload = {
|
|
5418
|
-
state: snapshot.state,
|
|
5419
|
-
balance: snapshot.balance,
|
|
5420
|
-
rateLimitResetType: snapshot.rateLimitResetType,
|
|
5421
|
-
planType: snapshot.planType
|
|
5422
|
-
};
|
|
5423
|
-
const event = {
|
|
5424
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5425
|
-
type: CODEX_QUOTA_STATUS_EVENT_TYPE,
|
|
5426
|
-
payload
|
|
5427
|
-
};
|
|
5428
|
-
return event;
|
|
5429
|
-
}
|
|
5430
|
-
};
|
|
5357
|
+
// src/managers/codex-asp/app-server-process.ts
|
|
5358
|
+
import { spawn } from "child_process";
|
|
5359
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
5431
5360
|
|
|
5432
|
-
// src/
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
return
|
|
5361
|
+
// src/managers/codex-asp/asp-client.ts
|
|
5362
|
+
import { EventEmitter } from "events";
|
|
5363
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
5364
|
+
function hasOwn(record, key) {
|
|
5365
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
5437
5366
|
}
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5367
|
+
var AspClient = class {
|
|
5368
|
+
stdin;
|
|
5369
|
+
stdout;
|
|
5370
|
+
emitter = new EventEmitter();
|
|
5371
|
+
pending = /* @__PURE__ */ new Map();
|
|
5372
|
+
nextId = 1;
|
|
5373
|
+
lineBuffer = "";
|
|
5374
|
+
disposed = false;
|
|
5375
|
+
get isDisposed() {
|
|
5376
|
+
return this.disposed;
|
|
5445
5377
|
}
|
|
5446
|
-
return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord3(value.payload);
|
|
5447
|
-
}
|
|
5448
|
-
function sleep(ms) {
|
|
5449
|
-
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
5450
|
-
}
|
|
5451
|
-
var CodexManager = class extends CodingAgentManager {
|
|
5452
|
-
codex;
|
|
5453
|
-
currentThreadId = null;
|
|
5454
|
-
currentThread = null;
|
|
5455
|
-
activeAbortController = null;
|
|
5456
|
-
quotaStatus = new CodexQuotaStatusTracker();
|
|
5457
5378
|
constructor(options) {
|
|
5458
|
-
|
|
5459
|
-
this.
|
|
5460
|
-
this.
|
|
5379
|
+
this.stdin = options.stdin;
|
|
5380
|
+
this.stdout = options.stdout;
|
|
5381
|
+
this.stdout.setEncoding("utf8");
|
|
5382
|
+
this.stdout.on("data", this.handleStdoutData);
|
|
5383
|
+
this.stdin.on("error", this.handleStdinError);
|
|
5461
5384
|
}
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
return new Codex({
|
|
5465
|
-
env: buildCodexAgentEnv(),
|
|
5466
|
-
...codexApiKey ? { apiKey: codexApiKey } : {}
|
|
5467
|
-
});
|
|
5385
|
+
on(event, listener) {
|
|
5386
|
+
this.emitter.on(event, listener);
|
|
5468
5387
|
}
|
|
5469
|
-
|
|
5470
|
-
this.
|
|
5471
|
-
this.currentThread = null;
|
|
5388
|
+
off(event, listener) {
|
|
5389
|
+
this.emitter.off(event, listener);
|
|
5472
5390
|
}
|
|
5473
|
-
async
|
|
5474
|
-
if (this.
|
|
5475
|
-
|
|
5476
|
-
console.log(`[CodexManager] Restored thread ID from persisted state: ${this.currentThreadId}`);
|
|
5391
|
+
async request(method, params, opts) {
|
|
5392
|
+
if (this.disposed) {
|
|
5393
|
+
throw new Error(`Cannot send ${method}: ASP client disposed`);
|
|
5477
5394
|
}
|
|
5395
|
+
const id = this.nextId;
|
|
5396
|
+
this.nextId += 1;
|
|
5397
|
+
const promise = new Promise((resolve3, reject) => {
|
|
5398
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
5399
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
5400
|
+
this.pending.delete(id);
|
|
5401
|
+
reject(new Error(`ASP request timed out for ${method}`));
|
|
5402
|
+
}, timeoutMs) : null;
|
|
5403
|
+
this.pending.set(id, { resolve: resolve3, reject, method, timer });
|
|
5404
|
+
});
|
|
5405
|
+
this.write({ method, id, params });
|
|
5406
|
+
return promise;
|
|
5478
5407
|
}
|
|
5479
|
-
|
|
5480
|
-
if (
|
|
5408
|
+
notify(method, params) {
|
|
5409
|
+
if (this.disposed) {
|
|
5410
|
+
return;
|
|
5411
|
+
}
|
|
5481
5412
|
try {
|
|
5482
|
-
|
|
5483
|
-
if (!sessionFile) return;
|
|
5484
|
-
const content = await readFile7(sessionFile, "utf-8");
|
|
5485
|
-
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
5486
|
-
let latest = null;
|
|
5487
|
-
for (const line of lines) {
|
|
5488
|
-
try {
|
|
5489
|
-
const parsed = JSON.parse(line);
|
|
5490
|
-
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
5491
|
-
if (snapshot) {
|
|
5492
|
-
latest = snapshot;
|
|
5493
|
-
}
|
|
5494
|
-
} catch {
|
|
5495
|
-
}
|
|
5496
|
-
}
|
|
5497
|
-
if (latest) {
|
|
5498
|
-
this.emitQuotaStatus(latest);
|
|
5499
|
-
}
|
|
5413
|
+
this.write(params === void 0 ? { method } : { method, params });
|
|
5500
5414
|
} catch (error) {
|
|
5501
|
-
console.warn(
|
|
5415
|
+
console.warn(`[AspClient] Failed to send notification ${method}:`, error);
|
|
5502
5416
|
}
|
|
5503
5417
|
}
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5418
|
+
respond(id, result) {
|
|
5419
|
+
if (this.disposed) {
|
|
5420
|
+
return;
|
|
5421
|
+
}
|
|
5422
|
+
try {
|
|
5423
|
+
this.write({ id, result });
|
|
5424
|
+
} catch (error) {
|
|
5425
|
+
console.warn(`[AspClient] Failed to send response ${String(id)}:`, error);
|
|
5426
|
+
}
|
|
5507
5427
|
}
|
|
5508
|
-
|
|
5509
|
-
if (this.
|
|
5510
|
-
|
|
5428
|
+
dispose(reason = new Error("ASP client disposed")) {
|
|
5429
|
+
if (this.disposed) {
|
|
5430
|
+
return;
|
|
5511
5431
|
}
|
|
5432
|
+
this.disposed = true;
|
|
5433
|
+
this.stdout.off("data", this.handleStdoutData);
|
|
5434
|
+
this.stdin.removeListener("error", this.handleStdinError);
|
|
5435
|
+
for (const [id, pending] of this.pending) {
|
|
5436
|
+
if (pending.timer) {
|
|
5437
|
+
clearTimeout(pending.timer);
|
|
5438
|
+
}
|
|
5439
|
+
pending.reject(new Error(`${reason.message} while waiting for ${pending.method}`));
|
|
5440
|
+
this.pending.delete(id);
|
|
5441
|
+
}
|
|
5442
|
+
this.lineBuffer = "";
|
|
5443
|
+
this.emitter.emit("dispose", reason);
|
|
5444
|
+
this.emitter.removeAllListeners();
|
|
5512
5445
|
}
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
let config = {};
|
|
5522
|
-
if (existsSync6(CODEX_CONFIG_PATH)) {
|
|
5523
|
-
try {
|
|
5524
|
-
const existingContent = await readFile7(CODEX_CONFIG_PATH, "utf-8");
|
|
5525
|
-
const parsed = parseToml(existingContent);
|
|
5526
|
-
if (isRecord3(parsed)) {
|
|
5527
|
-
config = parsed;
|
|
5528
|
-
}
|
|
5529
|
-
} catch (parseError) {
|
|
5530
|
-
console.warn("[CodexManager] Failed to parse existing config.toml, starting fresh:", parseError);
|
|
5531
|
-
}
|
|
5532
|
-
}
|
|
5533
|
-
if (developerInstructions) {
|
|
5534
|
-
config.developer_instructions = developerInstructions;
|
|
5535
|
-
} else {
|
|
5536
|
-
delete config.developer_instructions;
|
|
5446
|
+
handleStdoutData = (chunk) => {
|
|
5447
|
+
this.lineBuffer += chunk.toString();
|
|
5448
|
+
let newlineIndex = this.lineBuffer.indexOf("\n");
|
|
5449
|
+
while (newlineIndex >= 0) {
|
|
5450
|
+
const line = this.lineBuffer.slice(0, newlineIndex).trim();
|
|
5451
|
+
this.lineBuffer = this.lineBuffer.slice(newlineIndex + 1);
|
|
5452
|
+
if (line.length > 0) {
|
|
5453
|
+
this.handleLine(line);
|
|
5537
5454
|
}
|
|
5538
|
-
|
|
5539
|
-
await writeFile8(CODEX_CONFIG_PATH, tomlContent, "utf-8");
|
|
5540
|
-
console.log("[CodexManager] Updated config.toml with developer_instructions");
|
|
5541
|
-
} catch (error) {
|
|
5542
|
-
console.error("[CodexManager] Failed to update config.toml:", error);
|
|
5455
|
+
newlineIndex = this.lineBuffer.indexOf("\n");
|
|
5543
5456
|
}
|
|
5544
|
-
}
|
|
5545
|
-
|
|
5457
|
+
};
|
|
5458
|
+
handleStdinError = (error) => {
|
|
5459
|
+
this.dispose(new Error(`ASP stdin error: ${error.message}`));
|
|
5460
|
+
};
|
|
5461
|
+
handleLine(line) {
|
|
5462
|
+
let parsed;
|
|
5546
5463
|
try {
|
|
5547
|
-
|
|
5464
|
+
parsed = JSON.parse(line);
|
|
5548
5465
|
} catch (error) {
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
if (refreshed) {
|
|
5552
|
-
this.resetCodexClient();
|
|
5553
|
-
await this.executeCodexTurn(request);
|
|
5554
|
-
return;
|
|
5555
|
-
}
|
|
5556
|
-
}
|
|
5557
|
-
throw error;
|
|
5466
|
+
console.warn("[AspClient] Failed to parse ASP JSON line:", error);
|
|
5467
|
+
return;
|
|
5558
5468
|
}
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
await this.flushQuotaSnapshotFromCurrentSession();
|
|
5563
|
-
if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
|
|
5564
|
-
this.emitQuotaStatus(this.quotaStatus.latestSnapshot, true);
|
|
5565
|
-
try {
|
|
5566
|
-
await this.onTurnComplete();
|
|
5567
|
-
} catch (error) {
|
|
5568
|
-
console.error("[CodexManager] onTurnComplete failed during quota-blocked turn:", error);
|
|
5569
|
-
}
|
|
5570
|
-
return;
|
|
5571
|
-
}
|
|
5469
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
5470
|
+
console.warn("[AspClient] Ignoring non-object ASP message");
|
|
5471
|
+
return;
|
|
5572
5472
|
}
|
|
5573
|
-
const
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
try {
|
|
5586
|
-
if (images && images.length > 0) {
|
|
5587
|
-
const normalizedImages = await normalizeImages(images);
|
|
5588
|
-
tempImagePaths = await saveNormalizedImagesToTempFiles(normalizedImages);
|
|
5589
|
-
}
|
|
5590
|
-
const developerInstructions = this.buildCombinedInstructions(customInstructions);
|
|
5591
|
-
await this.updateCodexConfig(developerInstructions);
|
|
5592
|
-
const sandboxMode = "danger-full-access";
|
|
5593
|
-
const webSearchMode = "live";
|
|
5594
|
-
const codexReasoningEffort = codexReasoningEffortForThinkingLevel(thinkingLevel);
|
|
5595
|
-
const additionalDirectories = await getAgentAdditionalDirectories();
|
|
5596
|
-
const threadOptions = {
|
|
5597
|
-
workingDirectory: this.workingDirectory,
|
|
5598
|
-
skipGitRepoCheck: true,
|
|
5599
|
-
sandboxMode,
|
|
5600
|
-
model: model || DEFAULT_MODEL,
|
|
5601
|
-
webSearchMode,
|
|
5602
|
-
additionalDirectories,
|
|
5603
|
-
...codexReasoningEffort ? { modelReasoningEffort: codexReasoningEffort } : {}
|
|
5604
|
-
};
|
|
5605
|
-
abortController = new AbortController();
|
|
5606
|
-
this.activeAbortController = abortController;
|
|
5607
|
-
if (this.currentThreadId) {
|
|
5608
|
-
this.currentThread = this.codex.resumeThread(this.currentThreadId, threadOptions);
|
|
5609
|
-
} else {
|
|
5610
|
-
this.currentThread = this.codex.startThread(threadOptions);
|
|
5611
|
-
const { events } = await this.currentThread.runStreamed("Hello", { signal: abortController.signal });
|
|
5612
|
-
for await (const event of events) {
|
|
5613
|
-
if (event.type === "thread.started") {
|
|
5614
|
-
this.currentThreadId = event.thread_id;
|
|
5615
|
-
await this.onSaveSessionId(this.currentThreadId);
|
|
5616
|
-
console.log(`[CodexManager] Captured and persisted thread ID: ${this.currentThreadId}`);
|
|
5617
|
-
}
|
|
5618
|
-
}
|
|
5619
|
-
if (!this.currentThreadId && this.currentThread.id) {
|
|
5620
|
-
this.currentThreadId = this.currentThread.id;
|
|
5621
|
-
await this.onSaveSessionId(this.currentThreadId);
|
|
5622
|
-
console.log(`[CodexManager] Captured and persisted thread ID from thread.id: ${this.currentThreadId}`);
|
|
5623
|
-
}
|
|
5624
|
-
}
|
|
5625
|
-
stopTail = this.currentThreadId ? await this.startSessionTail(this.currentThreadId) : null;
|
|
5626
|
-
let input;
|
|
5627
|
-
if (tempImagePaths.length > 0) {
|
|
5628
|
-
const inputItems = [
|
|
5629
|
-
{ type: "text", text: message },
|
|
5630
|
-
...tempImagePaths.map((path4) => ({ type: "local_image", path: path4 }))
|
|
5631
|
-
];
|
|
5632
|
-
input = inputItems;
|
|
5633
|
-
} else {
|
|
5634
|
-
input = message;
|
|
5635
|
-
}
|
|
5636
|
-
try {
|
|
5637
|
-
const { events } = await this.currentThread.runStreamed(input, { signal: abortController.signal });
|
|
5638
|
-
const linearForwarder = new LinearEventForwarder(linearSessionId);
|
|
5639
|
-
for await (const event of events) {
|
|
5640
|
-
if (linearSessionId) {
|
|
5641
|
-
linearForwarder.sendPlan(extractPlanFromCodexEvent(event));
|
|
5642
|
-
linearForwarder.sendEvent(convertCodexEvent(event, linearSessionId));
|
|
5643
|
-
}
|
|
5644
|
-
}
|
|
5645
|
-
linearForwarder.flushThoughtAsResponse();
|
|
5646
|
-
} catch (error) {
|
|
5647
|
-
await this.flushQuotaSnapshotFromCurrentSession();
|
|
5648
|
-
if (this.quotaStatus.blocked) {
|
|
5649
|
-
return;
|
|
5650
|
-
}
|
|
5651
|
-
throw error;
|
|
5652
|
-
}
|
|
5653
|
-
} finally {
|
|
5654
|
-
if (stopTail) {
|
|
5655
|
-
await stopTail();
|
|
5656
|
-
}
|
|
5657
|
-
await removeTempImageFiles(tempImagePaths);
|
|
5658
|
-
try {
|
|
5659
|
-
await this.onTurnComplete();
|
|
5660
|
-
} catch (error) {
|
|
5661
|
-
console.error("[CodexManager] onTurnComplete failed:", error);
|
|
5662
|
-
}
|
|
5663
|
-
this.activeAbortController = null;
|
|
5473
|
+
const message = parsed;
|
|
5474
|
+
const hasRequestId = typeof message.id === "number" || typeof message.id === "string";
|
|
5475
|
+
if (hasRequestId && (hasOwn(message, "result") || hasOwn(message, "error"))) {
|
|
5476
|
+
this.handleResponse(message);
|
|
5477
|
+
return;
|
|
5478
|
+
}
|
|
5479
|
+
if (hasRequestId && typeof message.method === "string") {
|
|
5480
|
+
this.emitter.emit("serverRequest", message);
|
|
5481
|
+
return;
|
|
5482
|
+
}
|
|
5483
|
+
if (!hasOwn(message, "id") && typeof message.method === "string") {
|
|
5484
|
+
this.emitter.emit("notification", message);
|
|
5664
5485
|
}
|
|
5665
5486
|
}
|
|
5666
|
-
|
|
5667
|
-
if (
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
events: []
|
|
5671
|
-
};
|
|
5487
|
+
handleResponse(message) {
|
|
5488
|
+
if (typeof message.id !== "number") {
|
|
5489
|
+
console.warn("[AspClient] Ignoring response with non-numeric request id");
|
|
5490
|
+
return;
|
|
5672
5491
|
}
|
|
5673
|
-
const
|
|
5674
|
-
if (!
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
events: []
|
|
5678
|
-
};
|
|
5492
|
+
const pending = this.pending.get(message.id);
|
|
5493
|
+
if (!pending) {
|
|
5494
|
+
console.warn(`[AspClient] Ignoring response for unknown request id ${message.id}`);
|
|
5495
|
+
return;
|
|
5679
5496
|
}
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5497
|
+
this.pending.delete(message.id);
|
|
5498
|
+
if (pending.timer) {
|
|
5499
|
+
clearTimeout(pending.timer);
|
|
5500
|
+
}
|
|
5501
|
+
if (hasOwn(message, "error")) {
|
|
5502
|
+
pending.reject(this.createRpcError(pending.method, message.error));
|
|
5503
|
+
return;
|
|
5504
|
+
}
|
|
5505
|
+
pending.resolve(message.result);
|
|
5685
5506
|
}
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
try {
|
|
5690
|
-
const now = /* @__PURE__ */ new Date();
|
|
5691
|
-
const year = now.getFullYear();
|
|
5692
|
-
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
5693
|
-
const day = String(now.getDate()).padStart(2, "0");
|
|
5694
|
-
const todayDir = join14(sessionsDir, String(year), month, day);
|
|
5695
|
-
const file = await this.findFileInDirectory(todayDir, threadId);
|
|
5696
|
-
if (file) return file;
|
|
5697
|
-
for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
|
|
5698
|
-
const date = new Date(now);
|
|
5699
|
-
date.setDate(date.getDate() - daysAgo);
|
|
5700
|
-
const searchYear = date.getFullYear();
|
|
5701
|
-
const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
|
|
5702
|
-
const searchDay = String(date.getDate()).padStart(2, "0");
|
|
5703
|
-
const searchDir = join14(sessionsDir, String(searchYear), searchMonth, searchDay);
|
|
5704
|
-
const file2 = await this.findFileInDirectory(searchDir, threadId);
|
|
5705
|
-
if (file2) return file2;
|
|
5706
|
-
}
|
|
5707
|
-
return null;
|
|
5708
|
-
} catch (error) {
|
|
5709
|
-
return null;
|
|
5507
|
+
createRpcError(method, error) {
|
|
5508
|
+
if (typeof error !== "object" || error === null || Array.isArray(error)) {
|
|
5509
|
+
return new Error(`ASP request failed for ${method}`);
|
|
5710
5510
|
}
|
|
5511
|
+
const rpcError = error;
|
|
5512
|
+
const code = typeof rpcError.code === "number" ? ` ${rpcError.code}` : "";
|
|
5513
|
+
const message = typeof rpcError.message === "string" ? rpcError.message : "Unknown ASP error";
|
|
5514
|
+
const data = hasOwn(rpcError, "data") ? ` data=${JSON.stringify(rpcError.data)}` : "";
|
|
5515
|
+
return new Error(`ASP request failed for ${method}:${code} ${message}${data}`);
|
|
5711
5516
|
}
|
|
5712
|
-
|
|
5517
|
+
write(message) {
|
|
5713
5518
|
try {
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
if (
|
|
5717
|
-
|
|
5718
|
-
const stats = await stat2(fullPath);
|
|
5719
|
-
if (stats.isFile()) {
|
|
5720
|
-
return fullPath;
|
|
5721
|
-
}
|
|
5519
|
+
this.stdin.write(`${JSON.stringify(message)}
|
|
5520
|
+
`, (error) => {
|
|
5521
|
+
if (error) {
|
|
5522
|
+
this.dispose(new Error(`ASP write failed: ${error.message}`));
|
|
5722
5523
|
}
|
|
5723
|
-
}
|
|
5724
|
-
return null;
|
|
5524
|
+
});
|
|
5725
5525
|
} catch (error) {
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
async waitForSessionFile(threadId, timeoutMs = 5e3) {
|
|
5730
|
-
const start = Date.now();
|
|
5731
|
-
while (Date.now() - start < timeoutMs) {
|
|
5732
|
-
const sessionFile = await this.findSessionFile(threadId);
|
|
5733
|
-
if (sessionFile) {
|
|
5734
|
-
return sessionFile;
|
|
5735
|
-
}
|
|
5736
|
-
await sleep(100);
|
|
5737
|
-
}
|
|
5738
|
-
return null;
|
|
5739
|
-
}
|
|
5740
|
-
// @openai/codex-sdk doesn't expose manual /compact (TUI-only); we only mirror the auto-compaction rollout entries to the UI.
|
|
5741
|
-
trackNativeCompaction(event) {
|
|
5742
|
-
if (event.type === "compacted") {
|
|
5743
|
-
this.setCompacting(false);
|
|
5744
|
-
return;
|
|
5745
|
-
}
|
|
5746
|
-
if (event.type !== "event_msg") return;
|
|
5747
|
-
const msg = event.payload.msg;
|
|
5748
|
-
if (!msg) return;
|
|
5749
|
-
const itemType = msg.payload?.item?.type;
|
|
5750
|
-
if (itemType !== "context_compaction") return;
|
|
5751
|
-
if (msg.type === "item_started") {
|
|
5752
|
-
this.setCompacting(true);
|
|
5753
|
-
} else if (msg.type === "item_completed") {
|
|
5754
|
-
this.setCompacting(false);
|
|
5755
|
-
}
|
|
5756
|
-
}
|
|
5757
|
-
async startSessionTail(threadId) {
|
|
5758
|
-
const sessionFile = await this.waitForSessionFile(threadId);
|
|
5759
|
-
if (!sessionFile) {
|
|
5760
|
-
return async () => {
|
|
5761
|
-
};
|
|
5526
|
+
const writeError = error instanceof Error ? error : new Error("ASP write failed");
|
|
5527
|
+
this.dispose(writeError);
|
|
5528
|
+
throw writeError;
|
|
5762
5529
|
}
|
|
5763
|
-
let active = true;
|
|
5764
|
-
const seenLines = /* @__PURE__ */ new Set();
|
|
5765
|
-
const seedSeenLines = async () => {
|
|
5766
|
-
try {
|
|
5767
|
-
const content = await readFile7(sessionFile, "utf-8");
|
|
5768
|
-
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
5769
|
-
let latest = null;
|
|
5770
|
-
for (const line of lines) {
|
|
5771
|
-
seenLines.add(line);
|
|
5772
|
-
try {
|
|
5773
|
-
const parsed = JSON.parse(line);
|
|
5774
|
-
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
5775
|
-
if (snapshot) latest = snapshot;
|
|
5776
|
-
} catch {
|
|
5777
|
-
}
|
|
5778
|
-
}
|
|
5779
|
-
if (latest) {
|
|
5780
|
-
this.quotaStatus.prime(latest);
|
|
5781
|
-
}
|
|
5782
|
-
} catch {
|
|
5783
|
-
}
|
|
5784
|
-
};
|
|
5785
|
-
await seedSeenLines();
|
|
5786
|
-
const pump = async () => {
|
|
5787
|
-
let emitted = 0;
|
|
5788
|
-
try {
|
|
5789
|
-
const content = await readFile7(sessionFile, "utf-8");
|
|
5790
|
-
const lines = content.split("\n");
|
|
5791
|
-
const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
|
|
5792
|
-
for (const line of completeLines) {
|
|
5793
|
-
const trimmed = line.trim();
|
|
5794
|
-
if (!trimmed || seenLines.has(trimmed)) {
|
|
5795
|
-
continue;
|
|
5796
|
-
}
|
|
5797
|
-
seenLines.add(trimmed);
|
|
5798
|
-
try {
|
|
5799
|
-
const parsed = JSON.parse(trimmed);
|
|
5800
|
-
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
5801
|
-
if (snapshot) {
|
|
5802
|
-
this.emitQuotaStatus(snapshot);
|
|
5803
|
-
}
|
|
5804
|
-
if (isJsonlEvent2(parsed)) {
|
|
5805
|
-
this.trackNativeCompaction(parsed);
|
|
5806
|
-
this.onEvent(parsed);
|
|
5807
|
-
emitted += 1;
|
|
5808
|
-
}
|
|
5809
|
-
} catch {
|
|
5810
|
-
}
|
|
5811
|
-
}
|
|
5812
|
-
} catch {
|
|
5813
|
-
}
|
|
5814
|
-
return emitted;
|
|
5815
|
-
};
|
|
5816
|
-
const loop = (async () => {
|
|
5817
|
-
while (active) {
|
|
5818
|
-
await pump();
|
|
5819
|
-
await sleep(100);
|
|
5820
|
-
}
|
|
5821
|
-
await pump();
|
|
5822
|
-
})();
|
|
5823
|
-
return async () => {
|
|
5824
|
-
active = false;
|
|
5825
|
-
await loop;
|
|
5826
|
-
const deadline = Date.now() + 1500;
|
|
5827
|
-
while (Date.now() < deadline) {
|
|
5828
|
-
const emitted = await pump();
|
|
5829
|
-
if (emitted > 0) {
|
|
5830
|
-
continue;
|
|
5831
|
-
}
|
|
5832
|
-
await sleep(100);
|
|
5833
|
-
}
|
|
5834
|
-
};
|
|
5835
5530
|
}
|
|
5836
5531
|
};
|
|
5837
5532
|
|
|
5838
5533
|
// src/managers/codex-asp/app-server-process.ts
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
var
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
constructor(options) {
|
|
5860
|
-
this.stdin = options.stdin;
|
|
5861
|
-
this.stdout = options.stdout;
|
|
5862
|
-
this.stdout.setEncoding("utf8");
|
|
5863
|
-
this.stdout.on("data", this.handleStdoutData);
|
|
5864
|
-
this.stdin.on("error", this.handleStdinError);
|
|
5534
|
+
var DEFAULT_CODEX_BINARY = "codex";
|
|
5535
|
+
var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
|
|
5536
|
+
var ENGINE_PACKAGE_VERSION = "0.1.213";
|
|
5537
|
+
var INITIALIZE_METHOD = "initialize";
|
|
5538
|
+
var INITIALIZED_NOTIFICATION = "initialized";
|
|
5539
|
+
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
5540
|
+
var AppServerProcess = class {
|
|
5541
|
+
binary;
|
|
5542
|
+
args;
|
|
5543
|
+
env;
|
|
5544
|
+
cwd;
|
|
5545
|
+
emitter = new EventEmitter2();
|
|
5546
|
+
child = null;
|
|
5547
|
+
client = null;
|
|
5548
|
+
shuttingDown = false;
|
|
5549
|
+
constructor(options = {}) {
|
|
5550
|
+
this.binary = options.binary ?? DEFAULT_CODEX_BINARY;
|
|
5551
|
+
this.args = options.args ?? DEFAULT_CODEX_ARGS;
|
|
5552
|
+
this.env = options.env ?? buildCodexAgentEnv();
|
|
5553
|
+
this.cwd = options.cwd ?? ENGINE_ENV.WORKSPACE_ROOT;
|
|
5865
5554
|
}
|
|
5866
5555
|
on(event, listener) {
|
|
5867
5556
|
this.emitter.on(event, listener);
|
|
5868
5557
|
}
|
|
5869
|
-
|
|
5870
|
-
this.
|
|
5871
|
-
|
|
5872
|
-
async request(method, params, opts) {
|
|
5873
|
-
if (this.disposed) {
|
|
5874
|
-
throw new Error(`Cannot send ${method}: ASP client disposed`);
|
|
5558
|
+
async start() {
|
|
5559
|
+
if (this.child && this.client) {
|
|
5560
|
+
return { client: this.client };
|
|
5875
5561
|
}
|
|
5876
|
-
|
|
5877
|
-
this.
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5562
|
+
this.shuttingDown = false;
|
|
5563
|
+
const child = spawn(this.binary, this.args, {
|
|
5564
|
+
cwd: this.cwd,
|
|
5565
|
+
env: this.env,
|
|
5566
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5567
|
+
});
|
|
5568
|
+
this.child = child;
|
|
5569
|
+
child.stderr.setEncoding("utf8");
|
|
5570
|
+
child.stderr.on("data", (chunk) => {
|
|
5571
|
+
for (const line of chunk.toString().split("\n")) {
|
|
5572
|
+
if (line.trim().length > 0) {
|
|
5573
|
+
console.error(`[codex-app-server] ${line}`);
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
});
|
|
5577
|
+
child.on("exit", (code, signal) => {
|
|
5578
|
+
this.client?.dispose();
|
|
5579
|
+
this.client = null;
|
|
5580
|
+
this.child = null;
|
|
5581
|
+
if (!this.shuttingDown) {
|
|
5582
|
+
console.warn(`[AppServerProcess] codex app-server exited unexpectedly code=${code ?? "null"} signal=${signal ?? "null"}`);
|
|
5583
|
+
this.emitter.emit("exit", code, signal);
|
|
5584
|
+
}
|
|
5585
|
+
});
|
|
5586
|
+
const client = new AspClient({ stdin: child.stdin, stdout: child.stdout });
|
|
5587
|
+
this.client = client;
|
|
5588
|
+
let cleanupEarlyFailureHandlers = () => {
|
|
5589
|
+
};
|
|
5590
|
+
const earlyFailure = new Promise((_resolve, reject) => {
|
|
5591
|
+
const onError = (error) => {
|
|
5592
|
+
reject(error);
|
|
5593
|
+
};
|
|
5594
|
+
const onExit = (code, signal) => {
|
|
5595
|
+
reject(new Error(`codex app-server exited before initialize completed code=${code ?? "null"} signal=${signal ?? "null"}`));
|
|
5596
|
+
};
|
|
5597
|
+
child.once("error", onError);
|
|
5598
|
+
child.once("exit", onExit);
|
|
5599
|
+
cleanupEarlyFailureHandlers = () => {
|
|
5600
|
+
child.off("error", onError);
|
|
5601
|
+
child.off("exit", onExit);
|
|
5602
|
+
};
|
|
5885
5603
|
});
|
|
5886
|
-
this.write({ method, id, params });
|
|
5887
|
-
return promise;
|
|
5888
|
-
}
|
|
5889
|
-
notify(method, params) {
|
|
5890
|
-
if (this.disposed) {
|
|
5891
|
-
return;
|
|
5892
|
-
}
|
|
5893
5604
|
try {
|
|
5894
|
-
|
|
5605
|
+
const initializeParams = {
|
|
5606
|
+
clientInfo: {
|
|
5607
|
+
name: "replicas_engine",
|
|
5608
|
+
title: "Replicas Engine",
|
|
5609
|
+
version: ENGINE_PACKAGE_VERSION
|
|
5610
|
+
},
|
|
5611
|
+
capabilities: {
|
|
5612
|
+
experimentalApi: true,
|
|
5613
|
+
requestAttestation: false,
|
|
5614
|
+
optOutNotificationMethods: null
|
|
5615
|
+
}
|
|
5616
|
+
};
|
|
5617
|
+
await Promise.race([
|
|
5618
|
+
client.request(INITIALIZE_METHOD, initializeParams),
|
|
5619
|
+
earlyFailure
|
|
5620
|
+
]);
|
|
5621
|
+
cleanupEarlyFailureHandlers();
|
|
5622
|
+
client.notify(INITIALIZED_NOTIFICATION);
|
|
5623
|
+
await this.loginWithConfiguredApiKey(client);
|
|
5624
|
+
return { client };
|
|
5895
5625
|
} catch (error) {
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
if (this.disposed) {
|
|
5901
|
-
return;
|
|
5902
|
-
}
|
|
5903
|
-
try {
|
|
5904
|
-
this.write({ id, result });
|
|
5905
|
-
} catch (error) {
|
|
5906
|
-
console.warn(`[AspClient] Failed to send response ${String(id)}:`, error);
|
|
5907
|
-
}
|
|
5908
|
-
}
|
|
5909
|
-
dispose(reason = new Error("ASP client disposed")) {
|
|
5910
|
-
if (this.disposed) {
|
|
5911
|
-
return;
|
|
5912
|
-
}
|
|
5913
|
-
this.disposed = true;
|
|
5914
|
-
this.stdout.off("data", this.handleStdoutData);
|
|
5915
|
-
this.stdin.removeListener("error", this.handleStdinError);
|
|
5916
|
-
for (const [id, pending] of this.pending) {
|
|
5917
|
-
if (pending.timer) {
|
|
5918
|
-
clearTimeout(pending.timer);
|
|
5919
|
-
}
|
|
5920
|
-
pending.reject(new Error(`${reason.message} while waiting for ${pending.method}`));
|
|
5921
|
-
this.pending.delete(id);
|
|
5922
|
-
}
|
|
5923
|
-
this.lineBuffer = "";
|
|
5924
|
-
this.emitter.emit("dispose", reason);
|
|
5925
|
-
this.emitter.removeAllListeners();
|
|
5926
|
-
}
|
|
5927
|
-
handleStdoutData = (chunk) => {
|
|
5928
|
-
this.lineBuffer += chunk.toString();
|
|
5929
|
-
let newlineIndex = this.lineBuffer.indexOf("\n");
|
|
5930
|
-
while (newlineIndex >= 0) {
|
|
5931
|
-
const line = this.lineBuffer.slice(0, newlineIndex).trim();
|
|
5932
|
-
this.lineBuffer = this.lineBuffer.slice(newlineIndex + 1);
|
|
5933
|
-
if (line.length > 0) {
|
|
5934
|
-
this.handleLine(line);
|
|
5935
|
-
}
|
|
5936
|
-
newlineIndex = this.lineBuffer.indexOf("\n");
|
|
5937
|
-
}
|
|
5938
|
-
};
|
|
5939
|
-
handleStdinError = (error) => {
|
|
5940
|
-
this.dispose(new Error(`ASP stdin error: ${error.message}`));
|
|
5941
|
-
};
|
|
5942
|
-
handleLine(line) {
|
|
5943
|
-
let parsed;
|
|
5944
|
-
try {
|
|
5945
|
-
parsed = JSON.parse(line);
|
|
5946
|
-
} catch (error) {
|
|
5947
|
-
console.warn("[AspClient] Failed to parse ASP JSON line:", error);
|
|
5948
|
-
return;
|
|
5949
|
-
}
|
|
5950
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
5951
|
-
console.warn("[AspClient] Ignoring non-object ASP message");
|
|
5952
|
-
return;
|
|
5953
|
-
}
|
|
5954
|
-
const message = parsed;
|
|
5955
|
-
const hasRequestId = typeof message.id === "number" || typeof message.id === "string";
|
|
5956
|
-
if (hasRequestId && (hasOwn(message, "result") || hasOwn(message, "error"))) {
|
|
5957
|
-
this.handleResponse(message);
|
|
5958
|
-
return;
|
|
5959
|
-
}
|
|
5960
|
-
if (hasRequestId && typeof message.method === "string") {
|
|
5961
|
-
this.emitter.emit("serverRequest", message);
|
|
5962
|
-
return;
|
|
5963
|
-
}
|
|
5964
|
-
if (!hasOwn(message, "id") && typeof message.method === "string") {
|
|
5965
|
-
this.emitter.emit("notification", message);
|
|
5966
|
-
}
|
|
5967
|
-
}
|
|
5968
|
-
handleResponse(message) {
|
|
5969
|
-
if (typeof message.id !== "number") {
|
|
5970
|
-
console.warn("[AspClient] Ignoring response with non-numeric request id");
|
|
5971
|
-
return;
|
|
5972
|
-
}
|
|
5973
|
-
const pending = this.pending.get(message.id);
|
|
5974
|
-
if (!pending) {
|
|
5975
|
-
console.warn(`[AspClient] Ignoring response for unknown request id ${message.id}`);
|
|
5976
|
-
return;
|
|
5977
|
-
}
|
|
5978
|
-
this.pending.delete(message.id);
|
|
5979
|
-
if (pending.timer) {
|
|
5980
|
-
clearTimeout(pending.timer);
|
|
5981
|
-
}
|
|
5982
|
-
if (hasOwn(message, "error")) {
|
|
5983
|
-
pending.reject(this.createRpcError(pending.method, message.error));
|
|
5984
|
-
return;
|
|
5985
|
-
}
|
|
5986
|
-
pending.resolve(message.result);
|
|
5987
|
-
}
|
|
5988
|
-
createRpcError(method, error) {
|
|
5989
|
-
if (typeof error !== "object" || error === null || Array.isArray(error)) {
|
|
5990
|
-
return new Error(`ASP request failed for ${method}`);
|
|
5991
|
-
}
|
|
5992
|
-
const rpcError = error;
|
|
5993
|
-
const code = typeof rpcError.code === "number" ? ` ${rpcError.code}` : "";
|
|
5994
|
-
const message = typeof rpcError.message === "string" ? rpcError.message : "Unknown ASP error";
|
|
5995
|
-
const data = hasOwn(rpcError, "data") ? ` data=${JSON.stringify(rpcError.data)}` : "";
|
|
5996
|
-
return new Error(`ASP request failed for ${method}:${code} ${message}${data}`);
|
|
5997
|
-
}
|
|
5998
|
-
write(message) {
|
|
5999
|
-
try {
|
|
6000
|
-
this.stdin.write(`${JSON.stringify(message)}
|
|
6001
|
-
`, (error) => {
|
|
6002
|
-
if (error) {
|
|
6003
|
-
this.dispose(new Error(`ASP write failed: ${error.message}`));
|
|
6004
|
-
}
|
|
6005
|
-
});
|
|
6006
|
-
} catch (error) {
|
|
6007
|
-
const writeError = error instanceof Error ? error : new Error("ASP write failed");
|
|
6008
|
-
this.dispose(writeError);
|
|
6009
|
-
throw writeError;
|
|
6010
|
-
}
|
|
6011
|
-
}
|
|
6012
|
-
};
|
|
6013
|
-
|
|
6014
|
-
// src/managers/codex-asp/app-server-process.ts
|
|
6015
|
-
var DEFAULT_CODEX_BINARY = "codex";
|
|
6016
|
-
var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
|
|
6017
|
-
var ENGINE_PACKAGE_VERSION = "0.1.213";
|
|
6018
|
-
var INITIALIZE_METHOD = "initialize";
|
|
6019
|
-
var INITIALIZED_NOTIFICATION = "initialized";
|
|
6020
|
-
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
6021
|
-
var AppServerProcess = class {
|
|
6022
|
-
binary;
|
|
6023
|
-
args;
|
|
6024
|
-
env;
|
|
6025
|
-
cwd;
|
|
6026
|
-
emitter = new EventEmitter2();
|
|
6027
|
-
child = null;
|
|
6028
|
-
client = null;
|
|
6029
|
-
shuttingDown = false;
|
|
6030
|
-
constructor(options = {}) {
|
|
6031
|
-
this.binary = options.binary ?? DEFAULT_CODEX_BINARY;
|
|
6032
|
-
this.args = options.args ?? DEFAULT_CODEX_ARGS;
|
|
6033
|
-
this.env = options.env ?? buildCodexAgentEnv();
|
|
6034
|
-
this.cwd = options.cwd ?? ENGINE_ENV.WORKSPACE_ROOT;
|
|
6035
|
-
}
|
|
6036
|
-
on(event, listener) {
|
|
6037
|
-
this.emitter.on(event, listener);
|
|
6038
|
-
}
|
|
6039
|
-
async start() {
|
|
6040
|
-
if (this.child && this.client) {
|
|
6041
|
-
return { client: this.client };
|
|
6042
|
-
}
|
|
6043
|
-
this.shuttingDown = false;
|
|
6044
|
-
const child = spawn(this.binary, this.args, {
|
|
6045
|
-
cwd: this.cwd,
|
|
6046
|
-
env: this.env,
|
|
6047
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
6048
|
-
});
|
|
6049
|
-
this.child = child;
|
|
6050
|
-
child.stderr.setEncoding("utf8");
|
|
6051
|
-
child.stderr.on("data", (chunk) => {
|
|
6052
|
-
for (const line of chunk.toString().split("\n")) {
|
|
6053
|
-
if (line.trim().length > 0) {
|
|
6054
|
-
console.error(`[codex-app-server] ${line}`);
|
|
6055
|
-
}
|
|
6056
|
-
}
|
|
6057
|
-
});
|
|
6058
|
-
child.on("exit", (code, signal) => {
|
|
6059
|
-
this.client?.dispose();
|
|
6060
|
-
this.client = null;
|
|
6061
|
-
this.child = null;
|
|
6062
|
-
if (!this.shuttingDown) {
|
|
6063
|
-
console.warn(`[AppServerProcess] codex app-server exited unexpectedly code=${code ?? "null"} signal=${signal ?? "null"}`);
|
|
6064
|
-
this.emitter.emit("exit", code, signal);
|
|
6065
|
-
}
|
|
6066
|
-
});
|
|
6067
|
-
const client = new AspClient({ stdin: child.stdin, stdout: child.stdout });
|
|
6068
|
-
this.client = client;
|
|
6069
|
-
let cleanupEarlyFailureHandlers = () => {
|
|
6070
|
-
};
|
|
6071
|
-
const earlyFailure = new Promise((_resolve, reject) => {
|
|
6072
|
-
const onError = (error) => {
|
|
6073
|
-
reject(error);
|
|
6074
|
-
};
|
|
6075
|
-
const onExit = (code, signal) => {
|
|
6076
|
-
reject(new Error(`codex app-server exited before initialize completed code=${code ?? "null"} signal=${signal ?? "null"}`));
|
|
6077
|
-
};
|
|
6078
|
-
child.once("error", onError);
|
|
6079
|
-
child.once("exit", onExit);
|
|
6080
|
-
cleanupEarlyFailureHandlers = () => {
|
|
6081
|
-
child.off("error", onError);
|
|
6082
|
-
child.off("exit", onExit);
|
|
6083
|
-
};
|
|
6084
|
-
});
|
|
6085
|
-
try {
|
|
6086
|
-
const initializeParams = {
|
|
6087
|
-
clientInfo: {
|
|
6088
|
-
name: "replicas_engine",
|
|
6089
|
-
title: "Replicas Engine",
|
|
6090
|
-
version: ENGINE_PACKAGE_VERSION
|
|
6091
|
-
},
|
|
6092
|
-
capabilities: {
|
|
6093
|
-
experimentalApi: true,
|
|
6094
|
-
requestAttestation: false,
|
|
6095
|
-
optOutNotificationMethods: null
|
|
6096
|
-
}
|
|
6097
|
-
};
|
|
6098
|
-
await Promise.race([
|
|
6099
|
-
client.request(INITIALIZE_METHOD, initializeParams),
|
|
6100
|
-
earlyFailure
|
|
6101
|
-
]);
|
|
6102
|
-
cleanupEarlyFailureHandlers();
|
|
6103
|
-
client.notify(INITIALIZED_NOTIFICATION);
|
|
6104
|
-
await this.loginWithConfiguredApiKey(client);
|
|
6105
|
-
return { client };
|
|
6106
|
-
} catch (error) {
|
|
6107
|
-
cleanupEarlyFailureHandlers();
|
|
6108
|
-
client.dispose();
|
|
6109
|
-
await this.killAfterFailedStart();
|
|
6110
|
-
throw error;
|
|
5626
|
+
cleanupEarlyFailureHandlers();
|
|
5627
|
+
client.dispose();
|
|
5628
|
+
await this.killAfterFailedStart();
|
|
5629
|
+
throw error;
|
|
6111
5630
|
}
|
|
6112
5631
|
}
|
|
6113
5632
|
async stop() {
|
|
@@ -6193,807 +5712,1609 @@ async function restartCodexAspHost() {
|
|
|
6193
5712
|
if (process2) {
|
|
6194
5713
|
await process2.stop();
|
|
6195
5714
|
}
|
|
6196
|
-
})();
|
|
6197
|
-
try {
|
|
6198
|
-
await restartPromise;
|
|
6199
|
-
} finally {
|
|
6200
|
-
restartPromise = null;
|
|
5715
|
+
})();
|
|
5716
|
+
try {
|
|
5717
|
+
await restartPromise;
|
|
5718
|
+
} finally {
|
|
5719
|
+
restartPromise = null;
|
|
5720
|
+
}
|
|
5721
|
+
}
|
|
5722
|
+
|
|
5723
|
+
// src/utils/codex-quota.ts
|
|
5724
|
+
function buildCodexRateLimitsSnapshot(fields) {
|
|
5725
|
+
if (fields.unlimited === true) return null;
|
|
5726
|
+
let state = "ok";
|
|
5727
|
+
if (fields.hasCredits === false) {
|
|
5728
|
+
state = "out_of_credits";
|
|
5729
|
+
} else if (fields.rateLimitResetType !== null) {
|
|
5730
|
+
state = "rate_limited";
|
|
5731
|
+
}
|
|
5732
|
+
return {
|
|
5733
|
+
state,
|
|
5734
|
+
balance: fields.balance,
|
|
5735
|
+
rateLimitResetType: fields.rateLimitResetType,
|
|
5736
|
+
planType: fields.planType
|
|
5737
|
+
};
|
|
5738
|
+
}
|
|
5739
|
+
function extractCodexRateLimitsSnapshotFromJsonl(parsed) {
|
|
5740
|
+
if (!isRecord4(parsed)) return null;
|
|
5741
|
+
const payload = isRecord4(parsed.payload) ? parsed.payload : parsed;
|
|
5742
|
+
const rateLimits = isRecord4(payload.rate_limits) ? payload.rate_limits : null;
|
|
5743
|
+
if (!rateLimits) return null;
|
|
5744
|
+
const credits = isRecord4(rateLimits.credits) ? rateLimits.credits : null;
|
|
5745
|
+
const hasCredits = credits && typeof credits.has_credits === "boolean" ? credits.has_credits : null;
|
|
5746
|
+
const unlimited = credits && typeof credits.unlimited === "boolean" ? credits.unlimited : null;
|
|
5747
|
+
const balance = credits && typeof credits.balance === "string" ? credits.balance : null;
|
|
5748
|
+
const rateLimitResetType = typeof rateLimits.rate_limit_reached_type === "string" && rateLimits.rate_limit_reached_type.length > 0 ? rateLimits.rate_limit_reached_type : null;
|
|
5749
|
+
const planType = typeof rateLimits.plan_type === "string" ? rateLimits.plan_type : null;
|
|
5750
|
+
return buildCodexRateLimitsSnapshot({
|
|
5751
|
+
unlimited,
|
|
5752
|
+
hasCredits,
|
|
5753
|
+
balance,
|
|
5754
|
+
rateLimitResetType,
|
|
5755
|
+
planType
|
|
5756
|
+
});
|
|
5757
|
+
}
|
|
5758
|
+
var CodexQuotaStatusTracker = class {
|
|
5759
|
+
lastEmittedQuotaState = "ok";
|
|
5760
|
+
latestQuotaSnapshot = null;
|
|
5761
|
+
quotaBlocked = false;
|
|
5762
|
+
get blocked() {
|
|
5763
|
+
return this.quotaBlocked;
|
|
5764
|
+
}
|
|
5765
|
+
get latestSnapshot() {
|
|
5766
|
+
return this.latestQuotaSnapshot;
|
|
5767
|
+
}
|
|
5768
|
+
prime(snapshot) {
|
|
5769
|
+
this.latestQuotaSnapshot = snapshot;
|
|
5770
|
+
this.quotaBlocked = snapshot.state === "out_of_credits";
|
|
5771
|
+
}
|
|
5772
|
+
apply(snapshot, force = false) {
|
|
5773
|
+
const stateChanged = snapshot.state !== this.lastEmittedQuotaState;
|
|
5774
|
+
if (!stateChanged && !force) {
|
|
5775
|
+
return null;
|
|
5776
|
+
}
|
|
5777
|
+
this.lastEmittedQuotaState = snapshot.state;
|
|
5778
|
+
this.quotaBlocked = snapshot.state === "out_of_credits";
|
|
5779
|
+
this.latestQuotaSnapshot = snapshot;
|
|
5780
|
+
const payload = {
|
|
5781
|
+
state: snapshot.state,
|
|
5782
|
+
balance: snapshot.balance,
|
|
5783
|
+
rateLimitResetType: snapshot.rateLimitResetType,
|
|
5784
|
+
planType: snapshot.planType
|
|
5785
|
+
};
|
|
5786
|
+
const event = {
|
|
5787
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5788
|
+
type: CODEX_QUOTA_STATUS_EVENT_TYPE,
|
|
5789
|
+
payload
|
|
5790
|
+
};
|
|
5791
|
+
return event;
|
|
5792
|
+
}
|
|
5793
|
+
};
|
|
5794
|
+
|
|
5795
|
+
// src/utils/codex-auth.ts
|
|
5796
|
+
function isCodexAuthError(error) {
|
|
5797
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5798
|
+
const lower = msg.toLowerCase();
|
|
5799
|
+
return lower.includes("unauthorized") || lower.includes("authentication") || lower.includes("invalid api key") || lower.includes("incorrect api key") || lower.includes("api key") && lower.includes("invalid") || lower.includes("401") || lower.includes('codexerrorinfo="unauthorized"') || lower.includes("codexerrorinfo=unauthorized");
|
|
5800
|
+
}
|
|
5801
|
+
|
|
5802
|
+
// src/managers/codex-asp/mappers.ts
|
|
5803
|
+
var DEFAULT_MODEL = "gpt-5.5";
|
|
5804
|
+
var THREAD_START_METHOD = "thread/start";
|
|
5805
|
+
var THREAD_RESUME_METHOD = "thread/resume";
|
|
5806
|
+
var THREAD_READ_METHOD = "thread/read";
|
|
5807
|
+
var THREAD_GOAL_SET_METHOD = "thread/goal/set";
|
|
5808
|
+
var THREAD_GOAL_GET_METHOD = "thread/goal/get";
|
|
5809
|
+
var THREAD_GOAL_CLEAR_METHOD = "thread/goal/clear";
|
|
5810
|
+
var TURN_START_METHOD = "turn/start";
|
|
5811
|
+
var TURN_INTERRUPT_METHOD = "turn/interrupt";
|
|
5812
|
+
var ACCOUNT_RATE_LIMITS_READ_METHOD = "account/rateLimits/read";
|
|
5813
|
+
function toReasoningEffort(thinkingLevel) {
|
|
5814
|
+
return codexReasoningEffortForThinkingLevel(thinkingLevel);
|
|
5815
|
+
}
|
|
5816
|
+
function extractRateLimitsSnapshot(rateLimits) {
|
|
5817
|
+
const credits = rateLimits.credits;
|
|
5818
|
+
return buildCodexRateLimitsSnapshot({
|
|
5819
|
+
unlimited: credits?.unlimited ?? null,
|
|
5820
|
+
hasCredits: credits?.hasCredits ?? null,
|
|
5821
|
+
balance: credits?.balance ?? null,
|
|
5822
|
+
rateLimitResetType: rateLimits.rateLimitReachedType || null,
|
|
5823
|
+
planType: rateLimits.planType
|
|
5824
|
+
});
|
|
5825
|
+
}
|
|
5826
|
+
function timestampFromSeconds(value) {
|
|
5827
|
+
return new Date((value ?? Date.now() / 1e3) * 1e3).toISOString();
|
|
5828
|
+
}
|
|
5829
|
+
function timestampFromMilliseconds(value) {
|
|
5830
|
+
return new Date(value ?? Date.now()).toISOString();
|
|
5831
|
+
}
|
|
5832
|
+
function stringifyToolOutput(value) {
|
|
5833
|
+
if (value === null || value === void 0) return void 0;
|
|
5834
|
+
if (typeof value === "string") return value;
|
|
5835
|
+
if (typeof value === "object" && "content" in value && Array.isArray(value.content)) {
|
|
5836
|
+
const text = value.content.map((item) => {
|
|
5837
|
+
if (typeof item === "string") return item;
|
|
5838
|
+
if (item && typeof item === "object" && "text" in item && typeof item.text === "string") {
|
|
5839
|
+
return item.text;
|
|
5840
|
+
}
|
|
5841
|
+
return "";
|
|
5842
|
+
}).filter(Boolean).join("\n");
|
|
5843
|
+
if (text) return text;
|
|
5844
|
+
}
|
|
5845
|
+
try {
|
|
5846
|
+
return JSON.stringify(value);
|
|
5847
|
+
} catch {
|
|
5848
|
+
return String(value);
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
function transcriptPatchOperation(change) {
|
|
5852
|
+
return {
|
|
5853
|
+
action: change.kind.type,
|
|
5854
|
+
path: change.path,
|
|
5855
|
+
...change.kind.type === "update" && change.kind.move_path ? { moveTo: change.kind.move_path } : {},
|
|
5856
|
+
...change.diff.trim().length > 0 ? { diff: change.diff } : {}
|
|
5857
|
+
};
|
|
5858
|
+
}
|
|
5859
|
+
function transcriptItemsForTurn(turn) {
|
|
5860
|
+
const userItems = turn.items.filter((item) => item.type === "userMessage");
|
|
5861
|
+
const otherItems = turn.items.filter((item) => item.type !== "userMessage");
|
|
5862
|
+
return [...userItems, ...otherItems];
|
|
5863
|
+
}
|
|
5864
|
+
function itemToTranscriptItem(item, timestamp, status) {
|
|
5865
|
+
if (item.type === "userMessage") {
|
|
5866
|
+
const content = item.content.filter((input) => input.type === "text").map((input) => input.text).join("\n");
|
|
5867
|
+
return {
|
|
5868
|
+
type: "userMessage",
|
|
5869
|
+
id: item.id,
|
|
5870
|
+
content,
|
|
5871
|
+
timestamp
|
|
5872
|
+
};
|
|
5873
|
+
}
|
|
5874
|
+
if (item.type === "agentMessage") {
|
|
5875
|
+
return {
|
|
5876
|
+
type: "agentMessage",
|
|
5877
|
+
id: item.id,
|
|
5878
|
+
text: item.text,
|
|
5879
|
+
timestamp
|
|
5880
|
+
};
|
|
5881
|
+
}
|
|
5882
|
+
if (item.type === "reasoning") {
|
|
5883
|
+
return {
|
|
5884
|
+
type: "reasoning",
|
|
5885
|
+
id: item.id,
|
|
5886
|
+
text: [...item.summary, ...item.content].filter(Boolean).join("\n").trim(),
|
|
5887
|
+
timestamp,
|
|
5888
|
+
status
|
|
5889
|
+
};
|
|
5890
|
+
}
|
|
5891
|
+
if (item.type === "commandExecution") {
|
|
5892
|
+
const exitCode = item.exitCode ?? null;
|
|
5893
|
+
return {
|
|
5894
|
+
type: "commandExecution",
|
|
5895
|
+
id: item.id,
|
|
5896
|
+
command: item.command,
|
|
5897
|
+
...item.aggregatedOutput ? { output: item.aggregatedOutput } : {},
|
|
5898
|
+
exitCode,
|
|
5899
|
+
timestamp,
|
|
5900
|
+
status: normalizeCodexAspTranscriptStatus(item.status, typeof exitCode === "number" && exitCode !== 0)
|
|
5901
|
+
};
|
|
5902
|
+
}
|
|
5903
|
+
if (item.type === "fileChange") {
|
|
5904
|
+
return {
|
|
5905
|
+
type: "fileChange",
|
|
5906
|
+
id: item.id,
|
|
5907
|
+
operations: item.changes.map(transcriptPatchOperation),
|
|
5908
|
+
output: item.status,
|
|
5909
|
+
exitCode: item.status === "completed" ? 0 : 1,
|
|
5910
|
+
timestamp,
|
|
5911
|
+
status: normalizeCodexAspTranscriptStatus(item.status)
|
|
5912
|
+
};
|
|
5913
|
+
}
|
|
5914
|
+
if (item.type === "mcpToolCall") {
|
|
5915
|
+
return {
|
|
5916
|
+
type: "toolCall",
|
|
5917
|
+
id: item.id,
|
|
5918
|
+
server: item.server,
|
|
5919
|
+
tool: item.tool,
|
|
5920
|
+
input: item.arguments,
|
|
5921
|
+
output: item.error?.message ?? stringifyToolOutput(item.result),
|
|
5922
|
+
timestamp,
|
|
5923
|
+
status: normalizeCodexAspTranscriptStatus(item.status, item.status === "failed")
|
|
5924
|
+
};
|
|
5925
|
+
}
|
|
5926
|
+
if (item.type === "dynamicToolCall") {
|
|
5927
|
+
return {
|
|
5928
|
+
type: "toolCall",
|
|
5929
|
+
id: item.id,
|
|
5930
|
+
server: item.namespace ?? "dynamic",
|
|
5931
|
+
tool: item.tool,
|
|
5932
|
+
input: item.arguments,
|
|
5933
|
+
output: stringifyToolOutput(item.contentItems),
|
|
5934
|
+
timestamp,
|
|
5935
|
+
status: normalizeCodexAspTranscriptStatus(item.status, item.success === false)
|
|
5936
|
+
};
|
|
5937
|
+
}
|
|
5938
|
+
if (item.type === "collabAgentToolCall") {
|
|
5939
|
+
return {
|
|
5940
|
+
type: "toolCall",
|
|
5941
|
+
id: item.id,
|
|
5942
|
+
server: "collab",
|
|
5943
|
+
tool: item.tool,
|
|
5944
|
+
input: item.prompt ?? void 0,
|
|
5945
|
+
output: stringifyToolOutput(item.agentsStates),
|
|
5946
|
+
timestamp,
|
|
5947
|
+
status: normalizeCodexAspTranscriptStatus(item.status, item.status === "failed")
|
|
5948
|
+
};
|
|
5949
|
+
}
|
|
5950
|
+
if (item.type === "webSearch") {
|
|
5951
|
+
return {
|
|
5952
|
+
type: "webSearch",
|
|
5953
|
+
id: item.id,
|
|
5954
|
+
query: item.query,
|
|
5955
|
+
timestamp,
|
|
5956
|
+
status
|
|
5957
|
+
};
|
|
5958
|
+
}
|
|
5959
|
+
if (item.type === "plan") {
|
|
5960
|
+
return {
|
|
5961
|
+
type: "plan",
|
|
5962
|
+
id: item.id,
|
|
5963
|
+
text: item.text,
|
|
5964
|
+
timestamp,
|
|
5965
|
+
status
|
|
5966
|
+
};
|
|
5967
|
+
}
|
|
5968
|
+
if (item.type === "contextCompaction") {
|
|
5969
|
+
return {
|
|
5970
|
+
type: "contextCompaction",
|
|
5971
|
+
id: item.id,
|
|
5972
|
+
timestamp,
|
|
5973
|
+
status
|
|
5974
|
+
};
|
|
5975
|
+
}
|
|
5976
|
+
if (item.type === "imageView" || item.type === "imageGeneration" || item.type === "hookPrompt") {
|
|
5977
|
+
return null;
|
|
5978
|
+
}
|
|
5979
|
+
if (item.type === "enteredReviewMode" || item.type === "exitedReviewMode") {
|
|
5980
|
+
return {
|
|
5981
|
+
type: "reasoning",
|
|
5982
|
+
id: item.id,
|
|
5983
|
+
text: item.review,
|
|
5984
|
+
timestamp,
|
|
5985
|
+
status
|
|
5986
|
+
};
|
|
5987
|
+
}
|
|
5988
|
+
return null;
|
|
5989
|
+
}
|
|
5990
|
+
function aspGoalToChatGoal(goal) {
|
|
5991
|
+
return {
|
|
5992
|
+
threadId: goal.threadId,
|
|
5993
|
+
objective: goal.objective,
|
|
5994
|
+
status: goal.status,
|
|
5995
|
+
tokenBudget: goal.tokenBudget,
|
|
5996
|
+
tokensUsed: goal.tokensUsed,
|
|
5997
|
+
timeUsedSeconds: goal.timeUsedSeconds,
|
|
5998
|
+
createdAt: timestampFromSeconds(goal.createdAt),
|
|
5999
|
+
updatedAt: timestampFromSeconds(goal.updatedAt)
|
|
6000
|
+
};
|
|
6001
|
+
}
|
|
6002
|
+
function formatTurnFailure(turn) {
|
|
6003
|
+
const turnError = turn.error;
|
|
6004
|
+
if (!turnError) {
|
|
6005
|
+
return "Codex ASP turn failed without error details";
|
|
6006
|
+
}
|
|
6007
|
+
const parts = [`Codex ASP turn failed: ${turnError.message}`];
|
|
6008
|
+
if (turnError.codexErrorInfo !== null) {
|
|
6009
|
+
parts.push(`codexErrorInfo=${JSON.stringify(turnError.codexErrorInfo)}`);
|
|
6010
|
+
}
|
|
6011
|
+
if (turnError.additionalDetails) {
|
|
6012
|
+
parts.push(`details=${turnError.additionalDetails}`);
|
|
6013
|
+
}
|
|
6014
|
+
return parts.join(" ");
|
|
6015
|
+
}
|
|
6016
|
+
function threadToAspTranscript(thread) {
|
|
6017
|
+
let sequence = 0;
|
|
6018
|
+
const turns = thread.turns.map((turn, index) => ({ turn, index })).sort((a, b) => (a.turn.startedAt ?? a.turn.completedAt ?? Number.MAX_SAFE_INTEGER) - (b.turn.startedAt ?? b.turn.completedAt ?? Number.MAX_SAFE_INTEGER) || a.index - b.index).map(({ turn }) => {
|
|
6019
|
+
const startedAt = timestampFromSeconds(turn.startedAt);
|
|
6020
|
+
const completedAt = turn.completedAt === null ? null : timestampFromSeconds(turn.completedAt);
|
|
6021
|
+
const itemTimestamp = completedAt ?? startedAt;
|
|
6022
|
+
const status = normalizeCodexAspTranscriptStatus(turn.status);
|
|
6023
|
+
const items = [];
|
|
6024
|
+
for (const item of transcriptItemsForTurn(turn)) {
|
|
6025
|
+
const transcriptItem = itemToTranscriptItem(item, item.type === "userMessage" ? startedAt : itemTimestamp, status);
|
|
6026
|
+
if (transcriptItem) {
|
|
6027
|
+
items.push({ ...transcriptItem, sequence: sequence++ });
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
if (turn.error) {
|
|
6031
|
+
items.push({
|
|
6032
|
+
type: "error",
|
|
6033
|
+
id: `${turn.id}-error`,
|
|
6034
|
+
message: formatTurnFailure(turn),
|
|
6035
|
+
timestamp: itemTimestamp,
|
|
6036
|
+
sequence: sequence++
|
|
6037
|
+
});
|
|
6038
|
+
}
|
|
6039
|
+
return {
|
|
6040
|
+
id: turn.id,
|
|
6041
|
+
status: turn.status,
|
|
6042
|
+
startedAt,
|
|
6043
|
+
completedAt,
|
|
6044
|
+
items
|
|
6045
|
+
};
|
|
6046
|
+
});
|
|
6047
|
+
return {
|
|
6048
|
+
threadId: thread.id,
|
|
6049
|
+
updatedAt: timestampFromSeconds(thread.updatedAt),
|
|
6050
|
+
turns
|
|
6051
|
+
};
|
|
6052
|
+
}
|
|
6053
|
+
function latestIsoTimestamp(a, b) {
|
|
6054
|
+
return Date.parse(a) >= Date.parse(b) ? a : b;
|
|
6055
|
+
}
|
|
6056
|
+
function nextTranscriptSequence(transcript) {
|
|
6057
|
+
let max = -1;
|
|
6058
|
+
for (const turn of transcript.turns) {
|
|
6059
|
+
for (const item of turn.items) {
|
|
6060
|
+
if (typeof item.sequence === "number" && item.sequence > max) {
|
|
6061
|
+
max = item.sequence;
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
6065
|
+
return max + 1;
|
|
6066
|
+
}
|
|
6067
|
+
function transcriptItemRank(item) {
|
|
6068
|
+
return item.type === "userMessage" ? 0 : 1;
|
|
6069
|
+
}
|
|
6070
|
+
function transcriptItemSignature(item) {
|
|
6071
|
+
if (item.type === "userMessage") return `user:${item.content.trim()}`;
|
|
6072
|
+
if (item.type === "agentMessage" && item.text.trim()) return `agent:${item.text.trim()}`;
|
|
6073
|
+
if (item.type === "plan" && item.text.trim()) return `plan:${item.text.trim()}`;
|
|
6074
|
+
if (item.type === "error" && item.message.trim()) return `error:${item.message.trim()}`;
|
|
6075
|
+
return null;
|
|
6076
|
+
}
|
|
6077
|
+
function areEquivalentTranscriptItems(current, candidate) {
|
|
6078
|
+
if (current.id === candidate.id) return true;
|
|
6079
|
+
if (current.type !== candidate.type) return false;
|
|
6080
|
+
const signature = transcriptItemSignature(current);
|
|
6081
|
+
return signature !== null && signature === transcriptItemSignature(candidate);
|
|
6082
|
+
}
|
|
6083
|
+
function findEquivalentCodexAspTranscriptItemIndex(items, candidate) {
|
|
6084
|
+
return items.findIndex((current) => areEquivalentTranscriptItems(current, candidate));
|
|
6085
|
+
}
|
|
6086
|
+
function sortTranscriptItems(items) {
|
|
6087
|
+
return [...items].map((item, index) => ({ item, index })).sort((a, b) => {
|
|
6088
|
+
const aSequence = a.item.sequence ?? Number.MAX_SAFE_INTEGER;
|
|
6089
|
+
const bSequence = b.item.sequence ?? Number.MAX_SAFE_INTEGER;
|
|
6090
|
+
return transcriptItemRank(a.item) - transcriptItemRank(b.item) || aSequence - bSequence || Date.parse(a.item.timestamp) - Date.parse(b.item.timestamp) || a.index - b.index;
|
|
6091
|
+
}).map(({ item }) => item);
|
|
6092
|
+
}
|
|
6093
|
+
function dedupeTranscriptItems(items) {
|
|
6094
|
+
const deduped = [];
|
|
6095
|
+
for (const item of items) {
|
|
6096
|
+
const existingIndex = findEquivalentCodexAspTranscriptItemIndex(deduped, item);
|
|
6097
|
+
if (existingIndex === -1) {
|
|
6098
|
+
deduped.push(item);
|
|
6099
|
+
continue;
|
|
6100
|
+
}
|
|
6101
|
+
deduped[existingIndex] = mergeTranscriptItem(deduped[existingIndex], item);
|
|
6102
|
+
}
|
|
6103
|
+
return deduped;
|
|
6104
|
+
}
|
|
6105
|
+
function normalizeCodexAspTranscriptSequences(transcript) {
|
|
6106
|
+
let sequence = 0;
|
|
6107
|
+
const turns = [...transcript.turns].map((turn, index) => ({ turn, index })).sort((a, b) => Date.parse(a.turn.startedAt) - Date.parse(b.turn.startedAt) || a.index - b.index).map(({ turn }) => ({
|
|
6108
|
+
...turn,
|
|
6109
|
+
items: sortTranscriptItems(dedupeTranscriptItems(turn.items)).map((item) => ({
|
|
6110
|
+
...item,
|
|
6111
|
+
sequence: sequence++
|
|
6112
|
+
}))
|
|
6113
|
+
}));
|
|
6114
|
+
return {
|
|
6115
|
+
...transcript,
|
|
6116
|
+
turns
|
|
6117
|
+
};
|
|
6118
|
+
}
|
|
6119
|
+
function mergeCodexAspTranscriptItem(current, candidate) {
|
|
6120
|
+
if (current.type === "agentMessage" && candidate.type === "agentMessage" && candidate.text.length === 0) {
|
|
6121
|
+
return {
|
|
6122
|
+
...current,
|
|
6123
|
+
sequence: current.sequence ?? candidate.sequence
|
|
6124
|
+
};
|
|
6125
|
+
}
|
|
6126
|
+
return {
|
|
6127
|
+
...current,
|
|
6128
|
+
...candidate,
|
|
6129
|
+
id: current.id,
|
|
6130
|
+
timestamp: current.timestamp,
|
|
6131
|
+
sequence: current.sequence ?? candidate.sequence
|
|
6132
|
+
};
|
|
6133
|
+
}
|
|
6134
|
+
function mergeCodexAspTranscripts(primary, supplemental) {
|
|
6135
|
+
if (!primary) return supplemental ? normalizeCodexAspTranscriptSequences(supplemental) : null;
|
|
6136
|
+
if (!supplemental) return normalizeCodexAspTranscriptSequences(primary);
|
|
6137
|
+
if (primary.threadId !== supplemental.threadId) return normalizeCodexAspTranscriptSequences(supplemental);
|
|
6138
|
+
let sequence = nextTranscriptSequence(primary);
|
|
6139
|
+
const turns = primary.turns.map((turn) => ({
|
|
6140
|
+
...turn,
|
|
6141
|
+
items: [...turn.items]
|
|
6142
|
+
}));
|
|
6143
|
+
for (const supplementalTurn of supplemental.turns) {
|
|
6144
|
+
const existingTurn = turns.find((turn) => turn.id === supplementalTurn.id);
|
|
6145
|
+
if (!existingTurn) {
|
|
6146
|
+
turns.push({
|
|
6147
|
+
...supplementalTurn,
|
|
6148
|
+
items: supplementalTurn.items.map((item) => typeof item.sequence === "number" ? item : { ...item, sequence: sequence++ })
|
|
6149
|
+
});
|
|
6150
|
+
continue;
|
|
6151
|
+
}
|
|
6152
|
+
existingTurn.status = supplementalTurn.status;
|
|
6153
|
+
existingTurn.completedAt = supplementalTurn.completedAt ?? existingTurn.completedAt;
|
|
6154
|
+
existingTurn.startedAt = Date.parse(existingTurn.startedAt) <= Date.parse(supplementalTurn.startedAt) ? existingTurn.startedAt : supplementalTurn.startedAt;
|
|
6155
|
+
for (const item of supplementalTurn.items) {
|
|
6156
|
+
const existingIndex = findEquivalentCodexAspTranscriptItemIndex(existingTurn.items, item);
|
|
6157
|
+
if (existingIndex === -1) {
|
|
6158
|
+
existingTurn.items.push(
|
|
6159
|
+
typeof item.sequence === "number" ? item : { ...item, sequence: sequence++ }
|
|
6160
|
+
);
|
|
6161
|
+
continue;
|
|
6162
|
+
}
|
|
6163
|
+
existingTurn.items[existingIndex] = mergeCodexAspTranscriptItem(existingTurn.items[existingIndex], item);
|
|
6164
|
+
}
|
|
6165
|
+
existingTurn.items = sortTranscriptItems(existingTurn.items);
|
|
6166
|
+
}
|
|
6167
|
+
return normalizeCodexAspTranscriptSequences({
|
|
6168
|
+
threadId: primary.threadId,
|
|
6169
|
+
updatedAt: latestIsoTimestamp(primary.updatedAt, supplemental.updatedAt),
|
|
6170
|
+
turns: turns.sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt))
|
|
6171
|
+
});
|
|
6172
|
+
}
|
|
6173
|
+
async function buildThreadStartParams(workingDirectory, request, developerInstructions) {
|
|
6174
|
+
const additionalDirectories = await getAgentAdditionalDirectories();
|
|
6175
|
+
return {
|
|
6176
|
+
model: request.model ?? DEFAULT_MODEL,
|
|
6177
|
+
cwd: workingDirectory,
|
|
6178
|
+
runtimeWorkspaceRoots: additionalDirectories,
|
|
6179
|
+
sandbox: "danger-full-access",
|
|
6180
|
+
developerInstructions: developerInstructions ?? null,
|
|
6181
|
+
config: { web_search: "live" },
|
|
6182
|
+
experimentalRawEvents: false,
|
|
6183
|
+
persistExtendedHistory: false
|
|
6184
|
+
};
|
|
6185
|
+
}
|
|
6186
|
+
async function buildThreadResumeParams(workingDirectory, threadId, request, developerInstructions) {
|
|
6187
|
+
const additionalDirectories = await getAgentAdditionalDirectories();
|
|
6188
|
+
return {
|
|
6189
|
+
threadId,
|
|
6190
|
+
model: request.model ?? DEFAULT_MODEL,
|
|
6191
|
+
cwd: workingDirectory,
|
|
6192
|
+
runtimeWorkspaceRoots: additionalDirectories,
|
|
6193
|
+
sandbox: "danger-full-access",
|
|
6194
|
+
developerInstructions: developerInstructions ?? null,
|
|
6195
|
+
config: { web_search: "live" },
|
|
6196
|
+
excludeTurns: false,
|
|
6197
|
+
persistExtendedHistory: false
|
|
6198
|
+
};
|
|
6199
|
+
}
|
|
6200
|
+
async function buildTurnInput(request) {
|
|
6201
|
+
const input = [{
|
|
6202
|
+
type: "text",
|
|
6203
|
+
text: request.message,
|
|
6204
|
+
text_elements: []
|
|
6205
|
+
}];
|
|
6206
|
+
if (!request.images || request.images.length === 0) {
|
|
6207
|
+
return { input, tempImagePaths: [] };
|
|
6208
|
+
}
|
|
6209
|
+
const normalizedImages = await normalizeImages(request.images);
|
|
6210
|
+
const tempImagePaths = await saveNormalizedImagesToTempFiles(normalizedImages);
|
|
6211
|
+
input.push(...tempImagePaths.map((path4) => ({
|
|
6212
|
+
type: "localImage",
|
|
6213
|
+
path: path4
|
|
6214
|
+
})));
|
|
6215
|
+
return { input, tempImagePaths };
|
|
6216
|
+
}
|
|
6217
|
+
async function buildTurnStartParams(threadId, request, developerInstructions) {
|
|
6218
|
+
const effort = toReasoningEffort(request.thinkingLevel);
|
|
6219
|
+
const model = request.model ?? DEFAULT_MODEL;
|
|
6220
|
+
const { input, tempImagePaths } = await buildTurnInput(request);
|
|
6221
|
+
return {
|
|
6222
|
+
params: {
|
|
6223
|
+
threadId,
|
|
6224
|
+
input,
|
|
6225
|
+
model,
|
|
6226
|
+
...effort ? { effort } : {},
|
|
6227
|
+
...developerInstructions ? {
|
|
6228
|
+
collaborationMode: {
|
|
6229
|
+
mode: "default",
|
|
6230
|
+
settings: {
|
|
6231
|
+
model,
|
|
6232
|
+
reasoning_effort: effort ?? null,
|
|
6233
|
+
developer_instructions: developerInstructions
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
} : {}
|
|
6237
|
+
},
|
|
6238
|
+
tempImagePaths
|
|
6239
|
+
};
|
|
6240
|
+
}
|
|
6241
|
+
|
|
6242
|
+
// src/managers/codex-asp/notification-dispatch.ts
|
|
6243
|
+
var TURN_STARTED_METHOD = "turn/started";
|
|
6244
|
+
var TURN_COMPLETED_METHOD = "turn/completed";
|
|
6245
|
+
var TURN_PLAN_UPDATED_METHOD = "turn/plan/updated";
|
|
6246
|
+
var THREAD_GOAL_UPDATED_METHOD = "thread/goal/updated";
|
|
6247
|
+
var THREAD_GOAL_CLEARED_METHOD = "thread/goal/cleared";
|
|
6248
|
+
var ITEM_STARTED_METHOD = "item/started";
|
|
6249
|
+
var ITEM_COMPLETED_METHOD = "item/completed";
|
|
6250
|
+
var AGENT_MESSAGE_DELTA_METHOD = "item/agentMessage/delta";
|
|
6251
|
+
var COMMAND_EXECUTION_OUTPUT_DELTA_METHOD = "item/commandExecution/outputDelta";
|
|
6252
|
+
var FILE_CHANGE_OUTPUT_DELTA_METHOD = "item/fileChange/outputDelta";
|
|
6253
|
+
var ACCOUNT_RATE_LIMITS_UPDATED_METHOD = "account/rateLimits/updated";
|
|
6254
|
+
var THREAD_TOKEN_USAGE_UPDATED_METHOD = "thread/tokenUsage/updated";
|
|
6255
|
+
var THREAD_COMPACTED_METHOD = "thread/compacted";
|
|
6256
|
+
var COMMAND_APPROVAL_METHOD = "item/commandExecution/requestApproval";
|
|
6257
|
+
var FILE_CHANGE_APPROVAL_METHOD = "item/fileChange/requestApproval";
|
|
6258
|
+
function dispatchAspNotification(notification, handlers) {
|
|
6259
|
+
const handler = handlers[notification.method];
|
|
6260
|
+
if (!handler) return;
|
|
6261
|
+
handler(notification);
|
|
6262
|
+
}
|
|
6263
|
+
|
|
6264
|
+
// src/managers/codex-asp/codex-asp-manager.ts
|
|
6265
|
+
var CodexAspManager = class extends CodingAgentManager {
|
|
6266
|
+
currentThreadId = null;
|
|
6267
|
+
activeTurnId = null;
|
|
6268
|
+
threadAttached = false;
|
|
6269
|
+
historyEvents = [];
|
|
6270
|
+
codexAspTranscript = null;
|
|
6271
|
+
codexAspSequence = 0;
|
|
6272
|
+
quotaStatus = new CodexQuotaStatusTracker();
|
|
6273
|
+
currentGoal = null;
|
|
6274
|
+
constructor(options) {
|
|
6275
|
+
super(options);
|
|
6276
|
+
this.initializeManager(this.processMessageInternal.bind(this));
|
|
6277
|
+
}
|
|
6278
|
+
async initialize() {
|
|
6279
|
+
if (this.initialSessionId) {
|
|
6280
|
+
this.currentThreadId = this.initialSessionId;
|
|
6281
|
+
}
|
|
6282
|
+
}
|
|
6283
|
+
async interruptActiveTurn() {
|
|
6284
|
+
if (!this.currentThreadId || !this.activeTurnId) {
|
|
6285
|
+
return;
|
|
6286
|
+
}
|
|
6287
|
+
try {
|
|
6288
|
+
const host = await getCodexAspHost();
|
|
6289
|
+
const params = {
|
|
6290
|
+
threadId: this.currentThreadId,
|
|
6291
|
+
turnId: this.activeTurnId
|
|
6292
|
+
};
|
|
6293
|
+
await host.client.request(TURN_INTERRUPT_METHOD, params);
|
|
6294
|
+
} catch (error) {
|
|
6295
|
+
console.warn("[CodexAspManager] Failed to interrupt active turn:", error);
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
async getHistory() {
|
|
6299
|
+
if (!this.currentThreadId) {
|
|
6300
|
+
return { thread_id: null, events: [], goal: null };
|
|
6301
|
+
}
|
|
6302
|
+
if (this.activeTurnId && this.codexAspTranscript?.threadId === this.currentThreadId) {
|
|
6303
|
+
try {
|
|
6304
|
+
const host = await getCodexAspHost();
|
|
6305
|
+
await this.refreshThreadGoal(host, this.currentThreadId);
|
|
6306
|
+
} catch {
|
|
6307
|
+
}
|
|
6308
|
+
return {
|
|
6309
|
+
thread_id: this.currentThreadId,
|
|
6310
|
+
events: [...this.historyEvents],
|
|
6311
|
+
codexAspTranscript: this.codexAspTranscript,
|
|
6312
|
+
goal: this.currentGoal
|
|
6313
|
+
};
|
|
6314
|
+
}
|
|
6315
|
+
try {
|
|
6316
|
+
const host = await getCodexAspHost();
|
|
6317
|
+
const [response] = await Promise.all([
|
|
6318
|
+
host.client.request(
|
|
6319
|
+
THREAD_READ_METHOD,
|
|
6320
|
+
{ threadId: this.currentThreadId, includeTurns: true }
|
|
6321
|
+
),
|
|
6322
|
+
this.refreshThreadGoal(host, this.currentThreadId)
|
|
6323
|
+
]);
|
|
6324
|
+
const transcript = this.mergeTranscriptSnapshot(threadToAspTranscript(response.thread));
|
|
6325
|
+
if (transcript) {
|
|
6326
|
+
transcript.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6327
|
+
}
|
|
6328
|
+
return {
|
|
6329
|
+
thread_id: this.currentThreadId,
|
|
6330
|
+
events: [...this.historyEvents],
|
|
6331
|
+
codexAspTranscript: transcript,
|
|
6332
|
+
goal: this.currentGoal
|
|
6333
|
+
};
|
|
6334
|
+
} catch {
|
|
6335
|
+
}
|
|
6336
|
+
return {
|
|
6337
|
+
thread_id: this.currentThreadId,
|
|
6338
|
+
events: [...this.historyEvents],
|
|
6339
|
+
codexAspTranscript: this.codexAspTranscript,
|
|
6340
|
+
goal: this.currentGoal
|
|
6341
|
+
};
|
|
6342
|
+
}
|
|
6343
|
+
getGoal() {
|
|
6344
|
+
return this.currentGoal;
|
|
6345
|
+
}
|
|
6346
|
+
async clearGoal() {
|
|
6347
|
+
await this.initialized;
|
|
6348
|
+
if (!this.currentThreadId) {
|
|
6349
|
+
this.recordGoalChange(null, true);
|
|
6350
|
+
return null;
|
|
6351
|
+
}
|
|
6352
|
+
const host = await getCodexAspHost();
|
|
6353
|
+
await host.client.request(
|
|
6354
|
+
THREAD_GOAL_CLEAR_METHOD,
|
|
6355
|
+
{ threadId: this.currentThreadId }
|
|
6356
|
+
);
|
|
6357
|
+
this.recordGoalChange(null, true);
|
|
6358
|
+
return null;
|
|
6359
|
+
}
|
|
6360
|
+
async processMessageInternal(request) {
|
|
6361
|
+
let userMessageRecorded = false;
|
|
6362
|
+
const recordUserMessage = (extraPayload = {}) => {
|
|
6363
|
+
if (userMessageRecorded) return;
|
|
6364
|
+
userMessageRecorded = true;
|
|
6365
|
+
this.recordHistoryEvent("event_msg", {
|
|
6366
|
+
type: "user_message",
|
|
6367
|
+
message: request.message,
|
|
6368
|
+
...extraPayload
|
|
6369
|
+
});
|
|
6370
|
+
};
|
|
6371
|
+
const goalCommand = parseGoalCommand(request.message);
|
|
6372
|
+
const dispatch = async () => {
|
|
6373
|
+
if (goalCommand?.type === "clear") {
|
|
6374
|
+
await this.executeGoalClearCommand(request, recordUserMessage);
|
|
6375
|
+
return;
|
|
6376
|
+
}
|
|
6377
|
+
if (goalCommand?.type === "set") {
|
|
6378
|
+
await this.executeAspTurn(request, recordUserMessage, {
|
|
6379
|
+
runTurn: (host, threadId) => this.runGoalTurn(host, threadId, request, goalCommand.objective),
|
|
6380
|
+
userMessagePayload: { command: "goal" }
|
|
6381
|
+
});
|
|
6382
|
+
return;
|
|
6383
|
+
}
|
|
6384
|
+
await this.executeAspTurn(request, recordUserMessage);
|
|
6385
|
+
};
|
|
6386
|
+
try {
|
|
6387
|
+
await dispatch();
|
|
6388
|
+
} catch (error) {
|
|
6389
|
+
if (isCodexAuthError(error)) {
|
|
6390
|
+
const refreshed = await codexTokenManager.fetchFreshCredentials(error instanceof Error ? error.message : String(error));
|
|
6391
|
+
if (refreshed) {
|
|
6392
|
+
await restartCodexAspHost();
|
|
6393
|
+
this.threadAttached = false;
|
|
6394
|
+
await dispatch();
|
|
6395
|
+
return;
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
throw error;
|
|
6399
|
+
} finally {
|
|
6400
|
+
this.activeTurnId = null;
|
|
6401
|
+
await this.onTurnComplete();
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
async executeGoalClearCommand(request, recordUserMessage) {
|
|
6405
|
+
const host = await getCodexAspHost();
|
|
6406
|
+
const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
|
|
6407
|
+
recordUserMessage({ command: "goal" });
|
|
6408
|
+
const threadId = await this.ensureThread(host, request, developerInstructions);
|
|
6409
|
+
await host.client.request(
|
|
6410
|
+
THREAD_GOAL_CLEAR_METHOD,
|
|
6411
|
+
{ threadId }
|
|
6412
|
+
);
|
|
6413
|
+
this.recordGoalChange(null, true);
|
|
6414
|
+
}
|
|
6415
|
+
async executeAspTurn(request, recordUserMessage, options = {}) {
|
|
6416
|
+
const host = await getCodexAspHost();
|
|
6417
|
+
if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
|
|
6418
|
+
await this.refreshQuotaSnapshot(host);
|
|
6419
|
+
if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
|
|
6420
|
+
recordUserMessage(options.userMessagePayload);
|
|
6421
|
+
this.emitQuotaStatus(this.quotaStatus.latestSnapshot, true);
|
|
6422
|
+
return;
|
|
6423
|
+
}
|
|
6424
|
+
}
|
|
6425
|
+
const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
|
|
6426
|
+
recordUserMessage(options.userMessagePayload);
|
|
6427
|
+
const threadId = await this.ensureThread(host, request, developerInstructions);
|
|
6428
|
+
const runTurn = options.runTurn ?? ((aspHost, aspThreadId, aspInstructions) => this.runTurn(aspHost, aspThreadId, request, aspInstructions));
|
|
6429
|
+
let completedTurn;
|
|
6430
|
+
try {
|
|
6431
|
+
completedTurn = await runTurn(host, threadId, developerInstructions);
|
|
6432
|
+
} catch (error) {
|
|
6433
|
+
await this.refreshQuotaSnapshot(host);
|
|
6434
|
+
if (this.quotaStatus.blocked) {
|
|
6435
|
+
return;
|
|
6436
|
+
}
|
|
6437
|
+
throw error;
|
|
6438
|
+
}
|
|
6439
|
+
if (completedTurn.status === "failed") {
|
|
6440
|
+
await this.refreshQuotaSnapshot(host);
|
|
6441
|
+
if (this.quotaStatus.blocked) {
|
|
6442
|
+
return;
|
|
6443
|
+
}
|
|
6444
|
+
throw new Error(formatTurnFailure(completedTurn));
|
|
6445
|
+
}
|
|
6446
|
+
}
|
|
6447
|
+
async ensureThread(host, request, developerInstructions) {
|
|
6448
|
+
const existingThreadId = this.currentThreadId;
|
|
6449
|
+
if (existingThreadId) {
|
|
6450
|
+
if (!this.threadAttached) {
|
|
6451
|
+
const response = await host.client.request(
|
|
6452
|
+
THREAD_RESUME_METHOD,
|
|
6453
|
+
await buildThreadResumeParams(this.workingDirectory, existingThreadId, request, developerInstructions)
|
|
6454
|
+
);
|
|
6455
|
+
this.currentThreadId = response.thread.id;
|
|
6456
|
+
this.threadAttached = true;
|
|
6457
|
+
this.seedHistoryFromThread(response.thread);
|
|
6458
|
+
await this.onSaveSessionId(this.currentThreadId);
|
|
6459
|
+
}
|
|
6460
|
+
return this.currentThreadId ?? existingThreadId;
|
|
6461
|
+
}
|
|
6462
|
+
const threadStartResponse = await host.client.request(
|
|
6463
|
+
THREAD_START_METHOD,
|
|
6464
|
+
await buildThreadStartParams(this.workingDirectory, request, developerInstructions)
|
|
6465
|
+
);
|
|
6466
|
+
const threadId = threadStartResponse.thread.id;
|
|
6467
|
+
this.currentThreadId = threadId;
|
|
6468
|
+
this.threadAttached = true;
|
|
6469
|
+
await this.onSaveSessionId(this.currentThreadId);
|
|
6470
|
+
return threadId;
|
|
6471
|
+
}
|
|
6472
|
+
async runTurn(host, threadId, request, developerInstructions) {
|
|
6473
|
+
const { params, tempImagePaths } = await buildTurnStartParams(threadId, request, developerInstructions);
|
|
6474
|
+
return this.observeTurn(host, threadId, request, async () => {
|
|
6475
|
+
const turnStartResponse = await host.client.request(
|
|
6476
|
+
TURN_START_METHOD,
|
|
6477
|
+
params
|
|
6478
|
+
);
|
|
6479
|
+
return { turn: turnStartResponse.turn, tempImagePaths };
|
|
6480
|
+
});
|
|
6481
|
+
}
|
|
6482
|
+
async runGoalTurn(host, threadId, request, objective) {
|
|
6483
|
+
return this.observeTurn(host, threadId, request, async () => {
|
|
6484
|
+
const response = await host.client.request(
|
|
6485
|
+
THREAD_GOAL_SET_METHOD,
|
|
6486
|
+
{ threadId, objective, status: "active" }
|
|
6487
|
+
);
|
|
6488
|
+
this.recordGoalChange(response.goal, true);
|
|
6489
|
+
return { turn: null, tempImagePaths: [] };
|
|
6490
|
+
});
|
|
6491
|
+
}
|
|
6492
|
+
async observeTurn(host, threadId, request, startTurn) {
|
|
6493
|
+
let resolveCompleted;
|
|
6494
|
+
const completed = new Promise((resolve3) => {
|
|
6495
|
+
resolveCompleted = resolve3;
|
|
6496
|
+
});
|
|
6497
|
+
let rejectDisposed = () => {
|
|
6498
|
+
};
|
|
6499
|
+
const disposed = new Promise((_resolve, reject) => {
|
|
6500
|
+
rejectDisposed = reject;
|
|
6501
|
+
});
|
|
6502
|
+
void disposed.catch(() => {
|
|
6503
|
+
});
|
|
6504
|
+
let observedTurnId = null;
|
|
6505
|
+
const completedItems = [];
|
|
6506
|
+
const agentMessageDeltas = /* @__PURE__ */ new Map();
|
|
6507
|
+
const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
|
|
6508
|
+
const model = request.model ?? DEFAULT_MODEL;
|
|
6509
|
+
let tempImagePaths = [];
|
|
6510
|
+
const linearForwarder = new LinearEventForwarder(linearSessionId);
|
|
6511
|
+
const matchesTurn = (notificationThreadId, notificationTurnId) => notificationThreadId === threadId && (!observedTurnId || notificationTurnId === null || notificationTurnId === observedTurnId);
|
|
6512
|
+
const handlers = {
|
|
6513
|
+
[ACCOUNT_RATE_LIMITS_UPDATED_METHOD]: (notification) => {
|
|
6514
|
+
this.handleRateLimits(notification.params.rateLimits);
|
|
6515
|
+
},
|
|
6516
|
+
[THREAD_TOKEN_USAGE_UPDATED_METHOD]: (notification) => {
|
|
6517
|
+
if (notification.params.threadId !== threadId) return;
|
|
6518
|
+
this.emitCodexTokenUsage(notification.params.tokenUsage, model);
|
|
6519
|
+
},
|
|
6520
|
+
[THREAD_GOAL_UPDATED_METHOD]: (notification) => {
|
|
6521
|
+
if (notification.params.threadId !== threadId) return;
|
|
6522
|
+
this.recordGoalChange(notification.params.goal);
|
|
6523
|
+
},
|
|
6524
|
+
[THREAD_GOAL_CLEARED_METHOD]: (notification) => {
|
|
6525
|
+
if (notification.params.threadId !== threadId) return;
|
|
6526
|
+
this.recordGoalChange(null);
|
|
6527
|
+
},
|
|
6528
|
+
[THREAD_COMPACTED_METHOD]: (notification) => {
|
|
6529
|
+
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6530
|
+
this.setCompacting(false);
|
|
6531
|
+
},
|
|
6532
|
+
[TURN_STARTED_METHOD]: (notification) => {
|
|
6533
|
+
if (notification.params.threadId !== threadId) return;
|
|
6534
|
+
observedTurnId = notification.params.turn.id;
|
|
6535
|
+
this.activeTurnId = notification.params.turn.id;
|
|
6536
|
+
this.mergeTranscriptTurn(notification.params.threadId, notification.params.turn);
|
|
6537
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6538
|
+
linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
|
|
6539
|
+
},
|
|
6540
|
+
[TURN_PLAN_UPDATED_METHOD]: (notification) => {
|
|
6541
|
+
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6542
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6543
|
+
linearForwarder.sendPlan(extractPlanFromCodexAspNotification(notification));
|
|
6544
|
+
},
|
|
6545
|
+
[ITEM_STARTED_METHOD]: (notification) => {
|
|
6546
|
+
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6547
|
+
if (notification.params.item.type === "contextCompaction") {
|
|
6548
|
+
this.setCompacting(true);
|
|
6549
|
+
}
|
|
6550
|
+
this.upsertTranscriptItem(
|
|
6551
|
+
notification.params.threadId,
|
|
6552
|
+
notification.params.turnId,
|
|
6553
|
+
notification.params.item,
|
|
6554
|
+
timestampFromMilliseconds(notification.params.startedAtMs),
|
|
6555
|
+
"in_progress",
|
|
6556
|
+
"started"
|
|
6557
|
+
);
|
|
6558
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6559
|
+
linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
|
|
6560
|
+
},
|
|
6561
|
+
[ITEM_COMPLETED_METHOD]: (notification) => {
|
|
6562
|
+
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6563
|
+
completedItems.push(notification.params.item);
|
|
6564
|
+
if (notification.params.item.type === "contextCompaction") {
|
|
6565
|
+
this.setCompacting(false);
|
|
6566
|
+
}
|
|
6567
|
+
const itemFailed = notification.params.item.type === "commandExecution" && typeof notification.params.item.exitCode === "number" && notification.params.item.exitCode !== 0;
|
|
6568
|
+
this.upsertTranscriptItem(
|
|
6569
|
+
notification.params.threadId,
|
|
6570
|
+
notification.params.turnId,
|
|
6571
|
+
notification.params.item,
|
|
6572
|
+
timestampFromMilliseconds(notification.params.completedAtMs),
|
|
6573
|
+
itemFailed ? "failed" : "completed",
|
|
6574
|
+
"completed"
|
|
6575
|
+
);
|
|
6576
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6577
|
+
linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
|
|
6578
|
+
},
|
|
6579
|
+
[AGENT_MESSAGE_DELTA_METHOD]: (notification) => {
|
|
6580
|
+
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6581
|
+
const currentText = agentMessageDeltas.get(notification.params.itemId) ?? "";
|
|
6582
|
+
agentMessageDeltas.set(notification.params.itemId, currentText + notification.params.delta);
|
|
6583
|
+
this.appendAgentMessageDelta(
|
|
6584
|
+
notification.params.threadId,
|
|
6585
|
+
notification.params.turnId,
|
|
6586
|
+
notification.params.itemId,
|
|
6587
|
+
notification.params.delta
|
|
6588
|
+
);
|
|
6589
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6590
|
+
},
|
|
6591
|
+
[COMMAND_EXECUTION_OUTPUT_DELTA_METHOD]: (notification) => {
|
|
6592
|
+
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6593
|
+
this.appendTranscriptOutput(notification.params.turnId, notification.params.itemId, notification.params.delta);
|
|
6594
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6595
|
+
},
|
|
6596
|
+
[FILE_CHANGE_OUTPUT_DELTA_METHOD]: (notification) => {
|
|
6597
|
+
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6598
|
+
this.appendTranscriptOutput(notification.params.turnId, notification.params.itemId, notification.params.delta);
|
|
6599
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6600
|
+
},
|
|
6601
|
+
[TURN_COMPLETED_METHOD]: (notification) => {
|
|
6602
|
+
if (notification.params.threadId !== threadId) return;
|
|
6603
|
+
observedTurnId = notification.params.turn.id;
|
|
6604
|
+
const turn = notification.params.turn;
|
|
6605
|
+
const items = turn.items.length > 0 ? [...turn.items] : [];
|
|
6606
|
+
const itemIds = new Set(items.map((item) => item.id));
|
|
6607
|
+
for (const item of completedItems) {
|
|
6608
|
+
if (!itemIds.has(item.id)) {
|
|
6609
|
+
items.push(item);
|
|
6610
|
+
itemIds.add(item.id);
|
|
6611
|
+
}
|
|
6612
|
+
}
|
|
6613
|
+
for (const [itemId, text] of agentMessageDeltas) {
|
|
6614
|
+
if (!itemIds.has(itemId)) {
|
|
6615
|
+
items.push({
|
|
6616
|
+
type: "agentMessage",
|
|
6617
|
+
id: itemId,
|
|
6618
|
+
text,
|
|
6619
|
+
phase: null,
|
|
6620
|
+
memoryCitation: null
|
|
6621
|
+
});
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
const completedTurn = items.length > 0 ? { ...turn, items, itemsView: "full" } : turn;
|
|
6625
|
+
this.mergeTranscriptTurn(notification.params.threadId, completedTurn);
|
|
6626
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6627
|
+
resolveCompleted(completedTurn);
|
|
6628
|
+
}
|
|
6629
|
+
};
|
|
6630
|
+
const onNotification = (notification) => {
|
|
6631
|
+
dispatchAspNotification(notification, handlers);
|
|
6632
|
+
};
|
|
6633
|
+
const onServerRequest = (serverRequest) => {
|
|
6634
|
+
if (serverRequest.method !== COMMAND_APPROVAL_METHOD && serverRequest.method !== FILE_CHANGE_APPROVAL_METHOD) {
|
|
6635
|
+
return;
|
|
6636
|
+
}
|
|
6637
|
+
console.warn("[CodexAspManager] approval requested while sandbox is danger-full-access");
|
|
6638
|
+
host.client.respond(serverRequest.id, { decision: "accept" });
|
|
6639
|
+
};
|
|
6640
|
+
const onDispose = (reason) => {
|
|
6641
|
+
this.threadAttached = false;
|
|
6642
|
+
this.activeTurnId = null;
|
|
6643
|
+
const turnLabel = observedTurnId ? ` ${observedTurnId}` : "";
|
|
6644
|
+
rejectDisposed(new Error(`Codex ASP client disposed before turn${turnLabel} completed: ${reason.message}`));
|
|
6645
|
+
};
|
|
6646
|
+
host.client.on("notification", onNotification);
|
|
6647
|
+
host.client.on("serverRequest", onServerRequest);
|
|
6648
|
+
host.client.on("dispose", onDispose);
|
|
6649
|
+
try {
|
|
6650
|
+
const started = await startTurn();
|
|
6651
|
+
tempImagePaths = started.tempImagePaths;
|
|
6652
|
+
if (started.turn) {
|
|
6653
|
+
observedTurnId = started.turn.id;
|
|
6654
|
+
this.activeTurnId = started.turn.id;
|
|
6655
|
+
}
|
|
6656
|
+
const turn = await Promise.race([completed, disposed]);
|
|
6657
|
+
linearForwarder.flushThoughtAsResponse();
|
|
6658
|
+
return turn;
|
|
6659
|
+
} finally {
|
|
6660
|
+
host.client.off("notification", onNotification);
|
|
6661
|
+
host.client.off("serverRequest", onServerRequest);
|
|
6662
|
+
host.client.off("dispose", onDispose);
|
|
6663
|
+
await removeTempImageFiles(tempImagePaths);
|
|
6664
|
+
if (host.client.isDisposed) {
|
|
6665
|
+
this.threadAttached = false;
|
|
6666
|
+
}
|
|
6667
|
+
}
|
|
6668
|
+
}
|
|
6669
|
+
nextTranscriptSequence() {
|
|
6670
|
+
return this.codexAspSequence++;
|
|
6671
|
+
}
|
|
6672
|
+
syncTranscriptSequence(transcript) {
|
|
6673
|
+
if (!transcript) return;
|
|
6674
|
+
let next = 0;
|
|
6675
|
+
for (const turn of transcript.turns) {
|
|
6676
|
+
for (const item of turn.items) {
|
|
6677
|
+
if (typeof item.sequence === "number" && item.sequence >= next) {
|
|
6678
|
+
next = item.sequence + 1;
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
this.codexAspSequence = next;
|
|
6201
6683
|
}
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
var THREAD_START_METHOD = "thread/start";
|
|
6207
|
-
var THREAD_RESUME_METHOD = "thread/resume";
|
|
6208
|
-
var THREAD_READ_METHOD = "thread/read";
|
|
6209
|
-
var THREAD_GOAL_SET_METHOD = "thread/goal/set";
|
|
6210
|
-
var THREAD_GOAL_GET_METHOD = "thread/goal/get";
|
|
6211
|
-
var THREAD_GOAL_CLEAR_METHOD = "thread/goal/clear";
|
|
6212
|
-
var TURN_START_METHOD = "turn/start";
|
|
6213
|
-
var TURN_INTERRUPT_METHOD = "turn/interrupt";
|
|
6214
|
-
var ACCOUNT_RATE_LIMITS_READ_METHOD = "account/rateLimits/read";
|
|
6215
|
-
function toReasoningEffort(thinkingLevel) {
|
|
6216
|
-
return codexReasoningEffortForThinkingLevel(thinkingLevel);
|
|
6217
|
-
}
|
|
6218
|
-
function extractRateLimitsSnapshot(rateLimits) {
|
|
6219
|
-
const credits = rateLimits.credits;
|
|
6220
|
-
return buildCodexRateLimitsSnapshot({
|
|
6221
|
-
unlimited: credits?.unlimited ?? null,
|
|
6222
|
-
hasCredits: credits?.hasCredits ?? null,
|
|
6223
|
-
balance: credits?.balance ?? null,
|
|
6224
|
-
rateLimitResetType: rateLimits.rateLimitReachedType || null,
|
|
6225
|
-
planType: rateLimits.planType
|
|
6226
|
-
});
|
|
6227
|
-
}
|
|
6228
|
-
function timestampFromSeconds(value) {
|
|
6229
|
-
return new Date((value ?? Date.now() / 1e3) * 1e3).toISOString();
|
|
6230
|
-
}
|
|
6231
|
-
function aspGoalToChatGoal(goal) {
|
|
6232
|
-
return {
|
|
6233
|
-
threadId: goal.threadId,
|
|
6234
|
-
objective: goal.objective,
|
|
6235
|
-
status: goal.status,
|
|
6236
|
-
tokenBudget: goal.tokenBudget,
|
|
6237
|
-
tokensUsed: goal.tokensUsed,
|
|
6238
|
-
timeUsedSeconds: goal.timeUsedSeconds,
|
|
6239
|
-
createdAt: timestampFromSeconds(goal.createdAt),
|
|
6240
|
-
updatedAt: timestampFromSeconds(goal.updatedAt)
|
|
6241
|
-
};
|
|
6242
|
-
}
|
|
6243
|
-
function formatTurnFailure(turn) {
|
|
6244
|
-
const turnError = turn.error;
|
|
6245
|
-
if (!turnError) {
|
|
6246
|
-
return "Codex ASP turn failed without error details";
|
|
6684
|
+
mergeTranscriptSnapshot(snapshot) {
|
|
6685
|
+
this.codexAspTranscript = mergeCodexAspTranscripts(this.codexAspTranscript, snapshot);
|
|
6686
|
+
this.syncTranscriptSequence(this.codexAspTranscript);
|
|
6687
|
+
return this.codexAspTranscript;
|
|
6247
6688
|
}
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6689
|
+
ensureTranscript(threadId) {
|
|
6690
|
+
if (this.codexAspTranscript?.threadId === threadId) {
|
|
6691
|
+
return this.codexAspTranscript;
|
|
6692
|
+
}
|
|
6693
|
+
this.codexAspTranscript = {
|
|
6694
|
+
threadId,
|
|
6695
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6696
|
+
turns: []
|
|
6697
|
+
};
|
|
6698
|
+
this.codexAspSequence = 0;
|
|
6699
|
+
return this.codexAspTranscript;
|
|
6700
|
+
}
|
|
6701
|
+
ensureTranscriptTurn(threadId, turnId, startedAt) {
|
|
6702
|
+
const transcript = this.ensureTranscript(threadId);
|
|
6703
|
+
let turn = transcript.turns.find((candidate) => candidate.id === turnId);
|
|
6704
|
+
if (!turn) {
|
|
6705
|
+
turn = {
|
|
6706
|
+
id: turnId,
|
|
6707
|
+
status: "inProgress",
|
|
6708
|
+
startedAt,
|
|
6709
|
+
completedAt: null,
|
|
6710
|
+
items: []
|
|
6711
|
+
};
|
|
6712
|
+
transcript.turns.push(turn);
|
|
6713
|
+
transcript.turns.sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt));
|
|
6714
|
+
return turn;
|
|
6715
|
+
}
|
|
6716
|
+
if (Date.parse(startedAt) < Date.parse(turn.startedAt)) {
|
|
6717
|
+
turn.startedAt = startedAt;
|
|
6718
|
+
}
|
|
6719
|
+
return turn;
|
|
6251
6720
|
}
|
|
6252
|
-
|
|
6253
|
-
|
|
6721
|
+
mergeTranscriptTurn(threadId, turn) {
|
|
6722
|
+
const startedAt = timestampFromSeconds(turn.startedAt);
|
|
6723
|
+
const completedAt = turn.completedAt === null ? null : timestampFromSeconds(turn.completedAt);
|
|
6724
|
+
const itemTimestamp = completedAt ?? startedAt;
|
|
6725
|
+
const transcriptTurn = this.ensureTranscriptTurn(threadId, turn.id, startedAt);
|
|
6726
|
+
transcriptTurn.status = turn.status;
|
|
6727
|
+
transcriptTurn.completedAt = completedAt;
|
|
6728
|
+
const itemStatus = turn.status === "failed" ? "failed" : turn.status === "completed" ? "completed" : "in_progress";
|
|
6729
|
+
for (const item of turn.items) {
|
|
6730
|
+
this.upsertTranscriptItem(
|
|
6731
|
+
threadId,
|
|
6732
|
+
turn.id,
|
|
6733
|
+
item,
|
|
6734
|
+
item.type === "userMessage" ? startedAt : itemTimestamp,
|
|
6735
|
+
itemStatus,
|
|
6736
|
+
"completed"
|
|
6737
|
+
);
|
|
6738
|
+
}
|
|
6739
|
+
if (turn.error) {
|
|
6740
|
+
const existingIndex = transcriptTurn.items.findIndex((item) => item.id === `${turn.id}-error`);
|
|
6741
|
+
const errorItem = {
|
|
6742
|
+
type: "error",
|
|
6743
|
+
id: `${turn.id}-error`,
|
|
6744
|
+
message: formatTurnFailure(turn),
|
|
6745
|
+
timestamp: itemTimestamp,
|
|
6746
|
+
sequence: existingIndex === -1 ? this.nextTranscriptSequence() : transcriptTurn.items[existingIndex].sequence
|
|
6747
|
+
};
|
|
6748
|
+
if (existingIndex === -1) {
|
|
6749
|
+
transcriptTurn.items.push(errorItem);
|
|
6750
|
+
} else {
|
|
6751
|
+
transcriptTurn.items[existingIndex] = errorItem;
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6754
|
+
if (this.codexAspTranscript) {
|
|
6755
|
+
this.codexAspTranscript.updatedAt = itemTimestamp;
|
|
6756
|
+
}
|
|
6254
6757
|
}
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
if (
|
|
6267
|
-
|
|
6268
|
-
} else if (change.kind.type === "delete") {
|
|
6269
|
-
lines.push(`*** Delete File: ${change.path}`);
|
|
6758
|
+
upsertTranscriptItem(threadId, turnId, item, timestamp, status, lifecycle) {
|
|
6759
|
+
const turn = this.ensureTranscriptTurn(threadId, turnId, timestamp);
|
|
6760
|
+
const candidate = itemToTranscriptItem(item, timestamp, status);
|
|
6761
|
+
if (!candidate) return;
|
|
6762
|
+
const existingIndex = findEquivalentCodexAspTranscriptItemIndex(turn.items, candidate);
|
|
6763
|
+
const existing = existingIndex === -1 ? null : turn.items[existingIndex];
|
|
6764
|
+
const sequence = existing?.sequence ?? this.nextTranscriptSequence();
|
|
6765
|
+
const itemTimestamp = existing && lifecycle === "completed" ? existing.timestamp : timestamp;
|
|
6766
|
+
const transcriptItem = itemToTranscriptItem(item, itemTimestamp, status);
|
|
6767
|
+
if (!transcriptItem) return;
|
|
6768
|
+
const sequencedItem = { ...transcriptItem, sequence };
|
|
6769
|
+
if (existingIndex === -1) {
|
|
6770
|
+
turn.items.push(sequencedItem);
|
|
6270
6771
|
} else {
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6772
|
+
const existingItem = turn.items[existingIndex];
|
|
6773
|
+
const mergedItem = mergeCodexAspTranscriptItem(existingItem, {
|
|
6774
|
+
...sequencedItem,
|
|
6775
|
+
timestamp: itemTimestamp,
|
|
6776
|
+
sequence
|
|
6777
|
+
});
|
|
6778
|
+
turn.items[existingIndex] = existingItem.type === "agentMessage" && mergedItem.type === "agentMessage" && lifecycle === "started" && mergedItem.text.length === 0 ? { ...mergedItem, text: existingItem.text } : mergedItem;
|
|
6275
6779
|
}
|
|
6276
|
-
if (
|
|
6277
|
-
|
|
6780
|
+
if (this.codexAspTranscript) {
|
|
6781
|
+
this.codexAspTranscript.updatedAt = timestamp;
|
|
6278
6782
|
}
|
|
6279
6783
|
}
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6784
|
+
appendAgentMessageDelta(threadId, turnId, itemId, delta) {
|
|
6785
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
6786
|
+
const turn = this.ensureTranscriptTurn(threadId, turnId, timestamp);
|
|
6787
|
+
const existingIndex = turn.items.findIndex((item) => item.id === itemId);
|
|
6788
|
+
if (existingIndex === -1) {
|
|
6789
|
+
turn.items.push({
|
|
6790
|
+
type: "agentMessage",
|
|
6791
|
+
id: itemId,
|
|
6792
|
+
text: delta,
|
|
6793
|
+
timestamp,
|
|
6794
|
+
sequence: this.nextTranscriptSequence()
|
|
6795
|
+
});
|
|
6796
|
+
} else {
|
|
6797
|
+
const existing = turn.items[existingIndex];
|
|
6798
|
+
if (existing.type === "agentMessage") {
|
|
6799
|
+
turn.items[existingIndex] = {
|
|
6800
|
+
...existing,
|
|
6801
|
+
text: `${existing.text}${delta}`
|
|
6802
|
+
};
|
|
6295
6803
|
}
|
|
6296
|
-
}
|
|
6804
|
+
}
|
|
6805
|
+
if (this.codexAspTranscript) {
|
|
6806
|
+
this.codexAspTranscript.updatedAt = timestamp;
|
|
6807
|
+
}
|
|
6297
6808
|
}
|
|
6298
|
-
|
|
6299
|
-
if (
|
|
6300
|
-
const
|
|
6301
|
-
if (!
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6809
|
+
appendTranscriptOutput(turnId, itemId, delta) {
|
|
6810
|
+
if (!this.codexAspTranscript) return;
|
|
6811
|
+
const turn = this.codexAspTranscript.turns.find((candidate) => candidate.id === turnId);
|
|
6812
|
+
if (!turn) return;
|
|
6813
|
+
const itemIndex = turn.items.findIndex((item2) => item2.id === itemId);
|
|
6814
|
+
if (itemIndex === -1) return;
|
|
6815
|
+
const item = turn.items[itemIndex];
|
|
6816
|
+
if (item.type !== "commandExecution" && item.type !== "fileChange") return;
|
|
6817
|
+
turn.items[itemIndex] = {
|
|
6818
|
+
...item,
|
|
6819
|
+
output: `${item.output ?? ""}${delta}`,
|
|
6820
|
+
status: "in_progress"
|
|
6821
|
+
};
|
|
6822
|
+
this.codexAspTranscript.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6309
6823
|
}
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
return [{
|
|
6313
|
-
type: "response_item",
|
|
6314
|
-
payload: {
|
|
6315
|
-
type: "function_call",
|
|
6316
|
-
name: "exec_command",
|
|
6317
|
-
call_id: item.id,
|
|
6318
|
-
arguments: JSON.stringify({ cmd: item.command })
|
|
6319
|
-
}
|
|
6320
|
-
}];
|
|
6321
|
-
}
|
|
6322
|
-
return [{
|
|
6323
|
-
type: "response_item",
|
|
6324
|
-
payload: {
|
|
6325
|
-
type: "function_call_output",
|
|
6326
|
-
call_id: item.id,
|
|
6327
|
-
output: formatCommandOutput(item)
|
|
6328
|
-
}
|
|
6329
|
-
}];
|
|
6824
|
+
seedHistoryFromThread(thread) {
|
|
6825
|
+
this.mergeTranscriptSnapshot(threadToAspTranscript(thread));
|
|
6330
6826
|
}
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
status: "in_progress"
|
|
6341
|
-
}
|
|
6342
|
-
}];
|
|
6827
|
+
async refreshThreadGoal(host, threadId) {
|
|
6828
|
+
try {
|
|
6829
|
+
const response = await host.client.request(
|
|
6830
|
+
THREAD_GOAL_GET_METHOD,
|
|
6831
|
+
{ threadId }
|
|
6832
|
+
);
|
|
6833
|
+
this.currentGoal = response.goal ? aspGoalToChatGoal(response.goal) : null;
|
|
6834
|
+
} catch (error) {
|
|
6835
|
+
console.warn("[CodexAspManager] Failed to read ASP thread goal:", error);
|
|
6343
6836
|
}
|
|
6344
|
-
return [{
|
|
6345
|
-
type: "response_item",
|
|
6346
|
-
payload: {
|
|
6347
|
-
type: "custom_tool_call_output",
|
|
6348
|
-
call_id: item.id,
|
|
6349
|
-
output: JSON.stringify({
|
|
6350
|
-
output: item.status,
|
|
6351
|
-
metadata: { exit_code: item.status === "completed" ? 0 : 1 }
|
|
6352
|
-
})
|
|
6353
|
-
}
|
|
6354
|
-
}];
|
|
6355
6837
|
}
|
|
6356
|
-
|
|
6357
|
-
const
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
return [{
|
|
6361
|
-
type: "response_item",
|
|
6362
|
-
payload: {
|
|
6363
|
-
type: "custom_tool_call",
|
|
6364
|
-
name: tool2,
|
|
6365
|
-
call_id: item.id,
|
|
6366
|
-
input,
|
|
6367
|
-
status: "in_progress"
|
|
6368
|
-
}
|
|
6369
|
-
}];
|
|
6838
|
+
recordGoalChange(goal, force = false) {
|
|
6839
|
+
const nextGoal = goal ? aspGoalToChatGoal(goal) : null;
|
|
6840
|
+
if (!force && JSON.stringify(this.currentGoal) === JSON.stringify(nextGoal)) {
|
|
6841
|
+
return;
|
|
6370
6842
|
}
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
output: JSON.stringify({
|
|
6378
|
-
output: failed ? "Failed" : "Done",
|
|
6379
|
-
metadata: { exit_code: failed ? 1 : 0 }
|
|
6380
|
-
})
|
|
6381
|
-
}
|
|
6382
|
-
}];
|
|
6843
|
+
this.currentGoal = nextGoal;
|
|
6844
|
+
const event = this.recordHistoryEvent(
|
|
6845
|
+
CHAT_GOAL_EVENT_TYPE,
|
|
6846
|
+
{ goal: nextGoal }
|
|
6847
|
+
);
|
|
6848
|
+
this.onEvent(event);
|
|
6383
6849
|
}
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
const userMessages = turn.items.filter((item) => item.type === "userMessage");
|
|
6393
|
-
const agentItems = turn.items.filter((item) => item.type !== "userMessage");
|
|
6394
|
-
for (const item of userMessages) {
|
|
6395
|
-
const message = item.content.filter((input) => input.type === "text").map((input) => input.text).join("\n");
|
|
6396
|
-
if (message) {
|
|
6397
|
-
events.push({
|
|
6398
|
-
timestamp: startedAt,
|
|
6399
|
-
type: "event_msg",
|
|
6400
|
-
payload: {
|
|
6401
|
-
type: "user_message",
|
|
6402
|
-
message,
|
|
6403
|
-
[CODEX_ASP_ITEM_ID_PAYLOAD_KEY]: item.id
|
|
6404
|
-
}
|
|
6405
|
-
});
|
|
6406
|
-
}
|
|
6407
|
-
}
|
|
6408
|
-
for (const item of agentItems) {
|
|
6409
|
-
for (const draft of itemToAgentEventDrafts(item, "started")) {
|
|
6410
|
-
events.push({ timestamp: startedAt, ...draft });
|
|
6411
|
-
}
|
|
6412
|
-
for (const draft of itemToAgentEventDrafts(item, "completed")) {
|
|
6413
|
-
events.push({ timestamp: completedAt, ...draft });
|
|
6414
|
-
}
|
|
6415
|
-
}
|
|
6850
|
+
recordHistoryEvent(type, payload) {
|
|
6851
|
+
const event = {
|
|
6852
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6853
|
+
type,
|
|
6854
|
+
payload
|
|
6855
|
+
};
|
|
6856
|
+
this.historyEvents.push(event);
|
|
6857
|
+
return event;
|
|
6416
6858
|
}
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
};
|
|
6431
|
-
}
|
|
6432
|
-
async function buildThreadResumeParams(workingDirectory, threadId, request, developerInstructions) {
|
|
6433
|
-
const additionalDirectories = await getAgentAdditionalDirectories();
|
|
6434
|
-
return {
|
|
6435
|
-
threadId,
|
|
6436
|
-
model: request.model ?? DEFAULT_MODEL2,
|
|
6437
|
-
cwd: workingDirectory,
|
|
6438
|
-
runtimeWorkspaceRoots: additionalDirectories,
|
|
6439
|
-
sandbox: "danger-full-access",
|
|
6440
|
-
developerInstructions: developerInstructions ?? null,
|
|
6441
|
-
config: { web_search: "live" },
|
|
6442
|
-
excludeTurns: false,
|
|
6443
|
-
persistExtendedHistory: false
|
|
6444
|
-
};
|
|
6445
|
-
}
|
|
6446
|
-
async function buildTurnInput(request) {
|
|
6447
|
-
const input = [{
|
|
6448
|
-
type: "text",
|
|
6449
|
-
text: request.message,
|
|
6450
|
-
text_elements: []
|
|
6451
|
-
}];
|
|
6452
|
-
if (!request.images || request.images.length === 0) {
|
|
6453
|
-
return { input, tempImagePaths: [] };
|
|
6859
|
+
emitTranscriptUpdated(threadId) {
|
|
6860
|
+
const updatedAt = this.codexAspTranscript?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6861
|
+
const transcript = this.codexAspTranscript ? structuredClone(this.codexAspTranscript) : null;
|
|
6862
|
+
const updatePayload = {
|
|
6863
|
+
updatedAt,
|
|
6864
|
+
transcript,
|
|
6865
|
+
threadId
|
|
6866
|
+
};
|
|
6867
|
+
this.onEvent({
|
|
6868
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6869
|
+
type: CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE,
|
|
6870
|
+
payload: updatePayload
|
|
6871
|
+
});
|
|
6454
6872
|
}
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
}
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
var ACCOUNT_RATE_LIMITS_UPDATED_METHOD = "account/rateLimits/updated";
|
|
6498
|
-
var THREAD_TOKEN_USAGE_UPDATED_METHOD = "thread/tokenUsage/updated";
|
|
6499
|
-
var THREAD_COMPACTED_METHOD = "thread/compacted";
|
|
6500
|
-
var COMMAND_APPROVAL_METHOD = "item/commandExecution/requestApproval";
|
|
6501
|
-
var FILE_CHANGE_APPROVAL_METHOD = "item/fileChange/requestApproval";
|
|
6502
|
-
function dispatchAspNotification(notification, handlers) {
|
|
6503
|
-
const handler = handlers[notification.method];
|
|
6504
|
-
if (!handler) return;
|
|
6505
|
-
handler(notification);
|
|
6506
|
-
}
|
|
6873
|
+
handleRateLimits(rateLimits) {
|
|
6874
|
+
const snapshot = extractRateLimitsSnapshot(rateLimits);
|
|
6875
|
+
if (snapshot) {
|
|
6876
|
+
this.emitQuotaStatus(snapshot);
|
|
6877
|
+
}
|
|
6878
|
+
}
|
|
6879
|
+
async refreshQuotaSnapshot(host) {
|
|
6880
|
+
try {
|
|
6881
|
+
const response = await host.client.request(
|
|
6882
|
+
ACCOUNT_RATE_LIMITS_READ_METHOD,
|
|
6883
|
+
void 0
|
|
6884
|
+
);
|
|
6885
|
+
this.handleRateLimits(response.rateLimits);
|
|
6886
|
+
} catch {
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
emitQuotaStatus(snapshot, force = false) {
|
|
6890
|
+
const event = this.quotaStatus.apply(snapshot, force);
|
|
6891
|
+
if (!event) return;
|
|
6892
|
+
this.historyEvents.push(event);
|
|
6893
|
+
this.onEvent(event);
|
|
6894
|
+
}
|
|
6895
|
+
emitCodexTokenUsage(tokenUsage, model) {
|
|
6896
|
+
const payload = buildCodexTokenUsageContextUsagePayload({
|
|
6897
|
+
model,
|
|
6898
|
+
modelContextWindow: tokenUsage.modelContextWindow,
|
|
6899
|
+
last: {
|
|
6900
|
+
inputTokens: tokenUsage.last.inputTokens,
|
|
6901
|
+
outputTokens: tokenUsage.last.outputTokens,
|
|
6902
|
+
totalTokens: tokenUsage.last.totalTokens,
|
|
6903
|
+
cachedInputTokens: tokenUsage.last.cachedInputTokens,
|
|
6904
|
+
reasoningOutputTokens: tokenUsage.last.reasoningOutputTokens
|
|
6905
|
+
},
|
|
6906
|
+
total: {
|
|
6907
|
+
totalTokens: tokenUsage.total.totalTokens
|
|
6908
|
+
},
|
|
6909
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6910
|
+
});
|
|
6911
|
+
const event = this.emitContextUsage(payload);
|
|
6912
|
+
this.historyEvents.push(event);
|
|
6913
|
+
}
|
|
6914
|
+
};
|
|
6507
6915
|
|
|
6508
|
-
// src/managers/codex-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
const command = typeof event.payload.command === "string" ? event.payload.command : "";
|
|
6521
|
-
const message = typeof event.payload.message === "string" ? event.payload.message : "";
|
|
6522
|
-
return `${event.type}:user_message:${event.timestamp}:${command}:${message}`;
|
|
6523
|
-
}
|
|
6524
|
-
if (itemId && payloadType) {
|
|
6525
|
-
return `${event.type}:${payloadType}:item:${itemId}`;
|
|
6526
|
-
}
|
|
6527
|
-
return `${event.type}:${event.timestamp}:${JSON.stringify(event.payload)}`;
|
|
6528
|
-
}
|
|
6529
|
-
function areDuplicateHistoryEvents(a, b) {
|
|
6530
|
-
if (historyEventKey(a) === historyEventKey(b)) return true;
|
|
6531
|
-
return areSameUserMessageEvents(a, b);
|
|
6532
|
-
}
|
|
6533
|
-
function mergeHistoryEvent(current, candidate) {
|
|
6534
|
-
if (getUserMessage(current) && getUserMessage(current) === getUserMessage(candidate)) {
|
|
6535
|
-
return {
|
|
6536
|
-
...current,
|
|
6537
|
-
timestamp: getEventTimestampMs(current) <= getEventTimestampMs(candidate) ? current.timestamp : candidate.timestamp,
|
|
6538
|
-
payload: {
|
|
6539
|
-
...current.payload,
|
|
6540
|
-
...candidate.payload
|
|
6541
|
-
}
|
|
6542
|
-
};
|
|
6916
|
+
// src/managers/codex-manager.ts
|
|
6917
|
+
import { Codex } from "@openai/codex-sdk";
|
|
6918
|
+
import { readdir as readdir3, stat as stat2, writeFile as writeFile8, mkdir as mkdir10, readFile as readFile7 } from "fs/promises";
|
|
6919
|
+
import { existsSync as existsSync6 } from "fs";
|
|
6920
|
+
import { join as join14 } from "path";
|
|
6921
|
+
import { homedir as homedir12 } from "os";
|
|
6922
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
6923
|
+
var DEFAULT_MODEL2 = "gpt-5.5";
|
|
6924
|
+
var CODEX_CONFIG_PATH = join14(homedir12(), ".codex", "config.toml");
|
|
6925
|
+
function isJsonlEvent2(value) {
|
|
6926
|
+
if (!isRecord4(value)) {
|
|
6927
|
+
return false;
|
|
6543
6928
|
}
|
|
6544
|
-
return
|
|
6929
|
+
return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord4(value.payload);
|
|
6545
6930
|
}
|
|
6546
|
-
function
|
|
6547
|
-
return
|
|
6548
|
-
areDuplicates: areDuplicateHistoryEvents,
|
|
6549
|
-
mergeEvent: mergeHistoryEvent
|
|
6550
|
-
});
|
|
6931
|
+
function sleep(ms) {
|
|
6932
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
6551
6933
|
}
|
|
6552
|
-
var
|
|
6934
|
+
var CodexManager = class extends CodingAgentManager {
|
|
6935
|
+
codex;
|
|
6553
6936
|
currentThreadId = null;
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
historyEvents = [];
|
|
6557
|
-
emittedItemStages = /* @__PURE__ */ new Set();
|
|
6937
|
+
currentThread = null;
|
|
6938
|
+
activeAbortController = null;
|
|
6558
6939
|
quotaStatus = new CodexQuotaStatusTracker();
|
|
6559
|
-
currentGoal = null;
|
|
6560
6940
|
constructor(options) {
|
|
6561
6941
|
super(options);
|
|
6942
|
+
this.codex = this.createCodexClient();
|
|
6562
6943
|
this.initializeManager(this.processMessageInternal.bind(this));
|
|
6563
6944
|
}
|
|
6945
|
+
createCodexClient() {
|
|
6946
|
+
const codexApiKey = resolveCodexApiKey();
|
|
6947
|
+
return new Codex({
|
|
6948
|
+
env: buildCodexAgentEnv(),
|
|
6949
|
+
...codexApiKey ? { apiKey: codexApiKey } : {}
|
|
6950
|
+
});
|
|
6951
|
+
}
|
|
6952
|
+
resetCodexClient() {
|
|
6953
|
+
this.codex = this.createCodexClient();
|
|
6954
|
+
this.currentThread = null;
|
|
6955
|
+
}
|
|
6564
6956
|
async initialize() {
|
|
6565
6957
|
if (this.initialSessionId) {
|
|
6566
6958
|
this.currentThreadId = this.initialSessionId;
|
|
6959
|
+
console.log(`[CodexManager] Restored thread ID from persisted state: ${this.currentThreadId}`);
|
|
6567
6960
|
}
|
|
6568
6961
|
}
|
|
6569
|
-
async
|
|
6570
|
-
if (!this.currentThreadId
|
|
6571
|
-
return;
|
|
6572
|
-
}
|
|
6962
|
+
async flushQuotaSnapshotFromCurrentSession() {
|
|
6963
|
+
if (!this.currentThreadId) return;
|
|
6573
6964
|
try {
|
|
6574
|
-
const
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6965
|
+
const sessionFile = await this.findSessionFile(this.currentThreadId);
|
|
6966
|
+
if (!sessionFile) return;
|
|
6967
|
+
const content = await readFile7(sessionFile, "utf-8");
|
|
6968
|
+
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
6969
|
+
let latest = null;
|
|
6970
|
+
for (const line of lines) {
|
|
6971
|
+
try {
|
|
6972
|
+
const parsed = JSON.parse(line);
|
|
6973
|
+
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
6974
|
+
if (snapshot) {
|
|
6975
|
+
latest = snapshot;
|
|
6976
|
+
}
|
|
6977
|
+
} catch {
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
6980
|
+
if (latest) {
|
|
6981
|
+
this.emitQuotaStatus(latest);
|
|
6982
|
+
}
|
|
6580
6983
|
} catch (error) {
|
|
6581
|
-
console.warn("[
|
|
6984
|
+
console.warn("[CodexManager] Failed to flush quota snapshot from session:", error);
|
|
6582
6985
|
}
|
|
6583
6986
|
}
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6987
|
+
emitQuotaStatus(snapshot, force = false) {
|
|
6988
|
+
const event = this.quotaStatus.apply(snapshot, force);
|
|
6989
|
+
if (event) this.onEvent(event);
|
|
6990
|
+
}
|
|
6991
|
+
async interruptActiveTurn() {
|
|
6992
|
+
if (this.activeAbortController) {
|
|
6993
|
+
this.activeAbortController.abort();
|
|
6587
6994
|
}
|
|
6995
|
+
}
|
|
6996
|
+
/**
|
|
6997
|
+
* Update the developer_instructions in ~/.codex/config.toml
|
|
6998
|
+
* This sets the system prompt that Codex will use for this turn
|
|
6999
|
+
*/
|
|
7000
|
+
async updateCodexConfig(developerInstructions) {
|
|
6588
7001
|
try {
|
|
6589
|
-
const
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
7002
|
+
const codexDir = join14(homedir12(), ".codex");
|
|
7003
|
+
await mkdir10(codexDir, { recursive: true });
|
|
7004
|
+
let config = {};
|
|
7005
|
+
if (existsSync6(CODEX_CONFIG_PATH)) {
|
|
7006
|
+
try {
|
|
7007
|
+
const existingContent = await readFile7(CODEX_CONFIG_PATH, "utf-8");
|
|
7008
|
+
const parsed = parseToml(existingContent);
|
|
7009
|
+
if (isRecord4(parsed)) {
|
|
7010
|
+
config = parsed;
|
|
7011
|
+
}
|
|
7012
|
+
} catch (parseError) {
|
|
7013
|
+
console.warn("[CodexManager] Failed to parse existing config.toml, starting fresh:", parseError);
|
|
7014
|
+
}
|
|
7015
|
+
}
|
|
7016
|
+
if (developerInstructions) {
|
|
7017
|
+
config.developer_instructions = developerInstructions;
|
|
7018
|
+
} else {
|
|
7019
|
+
delete config.developer_instructions;
|
|
7020
|
+
}
|
|
7021
|
+
const tomlContent = stringifyToml(config);
|
|
7022
|
+
await writeFile8(CODEX_CONFIG_PATH, tomlContent, "utf-8");
|
|
7023
|
+
console.log("[CodexManager] Updated config.toml with developer_instructions");
|
|
7024
|
+
} catch (error) {
|
|
7025
|
+
console.error("[CodexManager] Failed to update config.toml:", error);
|
|
6605
7026
|
}
|
|
6606
|
-
return {
|
|
6607
|
-
thread_id: this.currentThreadId,
|
|
6608
|
-
events: [...this.historyEvents],
|
|
6609
|
-
goal: this.currentGoal
|
|
6610
|
-
};
|
|
6611
|
-
}
|
|
6612
|
-
getGoal() {
|
|
6613
|
-
return this.currentGoal;
|
|
6614
7027
|
}
|
|
6615
7028
|
async processMessageInternal(request) {
|
|
6616
|
-
let userMessageRecorded = false;
|
|
6617
|
-
const recordUserMessage = (extraPayload = {}) => {
|
|
6618
|
-
if (userMessageRecorded) return;
|
|
6619
|
-
userMessageRecorded = true;
|
|
6620
|
-
this.recordHistoryEvent("event_msg", {
|
|
6621
|
-
type: "user_message",
|
|
6622
|
-
message: request.message,
|
|
6623
|
-
...extraPayload
|
|
6624
|
-
});
|
|
6625
|
-
};
|
|
6626
|
-
const goalCommand = parseGoalCommand(request.message);
|
|
6627
|
-
const dispatch = async () => {
|
|
6628
|
-
if (goalCommand?.type === "clear") {
|
|
6629
|
-
await this.executeGoalClearCommand(request, recordUserMessage);
|
|
6630
|
-
return;
|
|
6631
|
-
}
|
|
6632
|
-
if (goalCommand?.type === "set") {
|
|
6633
|
-
await this.executeAspTurn(request, recordUserMessage, {
|
|
6634
|
-
runTurn: (host, threadId) => this.runGoalTurn(host, threadId, request, goalCommand.objective),
|
|
6635
|
-
userMessagePayload: { command: "goal" }
|
|
6636
|
-
});
|
|
6637
|
-
return;
|
|
6638
|
-
}
|
|
6639
|
-
await this.executeAspTurn(request, recordUserMessage);
|
|
6640
|
-
};
|
|
6641
7029
|
try {
|
|
6642
|
-
await
|
|
7030
|
+
await this.executeCodexTurn(request);
|
|
6643
7031
|
} catch (error) {
|
|
6644
7032
|
if (isCodexAuthError(error)) {
|
|
6645
7033
|
const refreshed = await codexTokenManager.fetchFreshCredentials(error instanceof Error ? error.message : String(error));
|
|
6646
7034
|
if (refreshed) {
|
|
6647
|
-
|
|
6648
|
-
this.
|
|
6649
|
-
await dispatch();
|
|
7035
|
+
this.resetCodexClient();
|
|
7036
|
+
await this.executeCodexTurn(request);
|
|
6650
7037
|
return;
|
|
6651
|
-
}
|
|
6652
|
-
}
|
|
6653
|
-
throw error;
|
|
6654
|
-
} finally {
|
|
6655
|
-
this.activeTurnId = null;
|
|
6656
|
-
await this.onTurnComplete();
|
|
6657
|
-
}
|
|
6658
|
-
}
|
|
6659
|
-
async executeGoalClearCommand(request, recordUserMessage) {
|
|
6660
|
-
const host = await getCodexAspHost();
|
|
6661
|
-
const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
|
|
6662
|
-
recordUserMessage({ command: "goal" });
|
|
6663
|
-
const threadId = await this.ensureThread(host, request, developerInstructions);
|
|
6664
|
-
await host.client.request(
|
|
6665
|
-
THREAD_GOAL_CLEAR_METHOD,
|
|
6666
|
-
{ threadId }
|
|
6667
|
-
);
|
|
6668
|
-
this.recordGoalChange(null, true);
|
|
6669
|
-
}
|
|
6670
|
-
async executeAspTurn(request, recordUserMessage, options = {}) {
|
|
6671
|
-
const host = await getCodexAspHost();
|
|
6672
|
-
if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
|
|
6673
|
-
await this.refreshQuotaSnapshot(host);
|
|
6674
|
-
if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
|
|
6675
|
-
recordUserMessage(options.userMessagePayload);
|
|
6676
|
-
this.emitQuotaStatus(this.quotaStatus.latestSnapshot, true);
|
|
6677
|
-
return;
|
|
6678
|
-
}
|
|
6679
|
-
}
|
|
6680
|
-
const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
|
|
6681
|
-
recordUserMessage(options.userMessagePayload);
|
|
6682
|
-
const threadId = await this.ensureThread(host, request, developerInstructions);
|
|
6683
|
-
const runTurn = options.runTurn ?? ((aspHost, aspThreadId, aspInstructions) => this.runTurn(aspHost, aspThreadId, request, aspInstructions));
|
|
6684
|
-
let completedTurn;
|
|
6685
|
-
try {
|
|
6686
|
-
completedTurn = await runTurn(host, threadId, developerInstructions);
|
|
6687
|
-
} catch (error) {
|
|
6688
|
-
await this.refreshQuotaSnapshot(host);
|
|
6689
|
-
if (this.quotaStatus.blocked) {
|
|
6690
|
-
return;
|
|
6691
|
-
}
|
|
6692
|
-
throw error;
|
|
6693
|
-
}
|
|
6694
|
-
if (completedTurn.status === "failed") {
|
|
6695
|
-
await this.refreshQuotaSnapshot(host);
|
|
6696
|
-
if (this.quotaStatus.blocked) {
|
|
6697
|
-
return;
|
|
6698
|
-
}
|
|
6699
|
-
throw new Error(formatTurnFailure(completedTurn));
|
|
6700
|
-
}
|
|
6701
|
-
if (completedTurn.status === "completed") {
|
|
6702
|
-
this.emitTurnCompletedItems(completedTurn);
|
|
6703
|
-
}
|
|
6704
|
-
}
|
|
6705
|
-
async ensureThread(host, request, developerInstructions) {
|
|
6706
|
-
if (this.currentThreadId) {
|
|
6707
|
-
if (!this.threadAttached) {
|
|
6708
|
-
const response = await host.client.request(
|
|
6709
|
-
THREAD_RESUME_METHOD,
|
|
6710
|
-
await buildThreadResumeParams(this.workingDirectory, this.currentThreadId, request, developerInstructions)
|
|
6711
|
-
);
|
|
6712
|
-
this.currentThreadId = response.thread.id;
|
|
6713
|
-
this.threadAttached = true;
|
|
6714
|
-
this.seedHistoryFromThread(response.thread);
|
|
6715
|
-
await this.onSaveSessionId(this.currentThreadId);
|
|
7038
|
+
}
|
|
6716
7039
|
}
|
|
6717
|
-
|
|
7040
|
+
throw error;
|
|
6718
7041
|
}
|
|
6719
|
-
const threadStartResponse = await host.client.request(
|
|
6720
|
-
THREAD_START_METHOD,
|
|
6721
|
-
await buildThreadStartParams(this.workingDirectory, request, developerInstructions)
|
|
6722
|
-
);
|
|
6723
|
-
this.currentThreadId = threadStartResponse.thread.id;
|
|
6724
|
-
this.threadAttached = true;
|
|
6725
|
-
await this.onSaveSessionId(this.currentThreadId);
|
|
6726
|
-
return this.currentThreadId;
|
|
6727
|
-
}
|
|
6728
|
-
async runTurn(host, threadId, request, developerInstructions) {
|
|
6729
|
-
const { params, tempImagePaths } = await buildTurnStartParams(threadId, request, developerInstructions);
|
|
6730
|
-
return this.observeTurn(host, threadId, request, async () => {
|
|
6731
|
-
const turnStartResponse = await host.client.request(
|
|
6732
|
-
TURN_START_METHOD,
|
|
6733
|
-
params
|
|
6734
|
-
);
|
|
6735
|
-
return { turn: turnStartResponse.turn, tempImagePaths };
|
|
6736
|
-
});
|
|
6737
|
-
}
|
|
6738
|
-
async runGoalTurn(host, threadId, request, objective) {
|
|
6739
|
-
return this.observeTurn(host, threadId, request, async () => {
|
|
6740
|
-
const response = await host.client.request(
|
|
6741
|
-
THREAD_GOAL_SET_METHOD,
|
|
6742
|
-
{ threadId, objective, status: "active" }
|
|
6743
|
-
);
|
|
6744
|
-
this.recordGoalChange(response.goal, true);
|
|
6745
|
-
return { turn: null, tempImagePaths: [] };
|
|
6746
|
-
});
|
|
6747
7042
|
}
|
|
6748
|
-
async
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
const
|
|
6762
|
-
|
|
7043
|
+
async executeCodexTurn(request) {
|
|
7044
|
+
if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
|
|
7045
|
+
await this.flushQuotaSnapshotFromCurrentSession();
|
|
7046
|
+
if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
|
|
7047
|
+
this.emitQuotaStatus(this.quotaStatus.latestSnapshot, true);
|
|
7048
|
+
try {
|
|
7049
|
+
await this.onTurnComplete();
|
|
7050
|
+
} catch (error) {
|
|
7051
|
+
console.error("[CodexManager] onTurnComplete failed during quota-blocked turn:", error);
|
|
7052
|
+
}
|
|
7053
|
+
return;
|
|
7054
|
+
}
|
|
7055
|
+
}
|
|
7056
|
+
const {
|
|
7057
|
+
message,
|
|
7058
|
+
model,
|
|
7059
|
+
customInstructions,
|
|
7060
|
+
images,
|
|
7061
|
+
permissionMode,
|
|
7062
|
+
thinkingLevel
|
|
7063
|
+
} = request;
|
|
6763
7064
|
const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
|
|
6764
|
-
const model = request.model ?? DEFAULT_MODEL2;
|
|
6765
7065
|
let tempImagePaths = [];
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
}
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
this.
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6801
|
-
if (notification.params.item.type === "contextCompaction") {
|
|
6802
|
-
this.setCompacting(true);
|
|
6803
|
-
}
|
|
6804
|
-
this.emitThreadItemLifecycle(notification.params.item, "started");
|
|
6805
|
-
linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
|
|
6806
|
-
},
|
|
6807
|
-
[ITEM_COMPLETED_METHOD]: (notification) => {
|
|
6808
|
-
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6809
|
-
completedItems.push(notification.params.item);
|
|
6810
|
-
if (notification.params.item.type === "contextCompaction") {
|
|
6811
|
-
this.setCompacting(false);
|
|
6812
|
-
}
|
|
6813
|
-
this.emitThreadItemLifecycle(notification.params.item, "completed");
|
|
6814
|
-
linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
|
|
6815
|
-
},
|
|
6816
|
-
[AGENT_MESSAGE_DELTA_METHOD]: (notification) => {
|
|
6817
|
-
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6818
|
-
const currentText = agentMessageDeltas.get(notification.params.itemId) ?? "";
|
|
6819
|
-
agentMessageDeltas.set(notification.params.itemId, currentText + notification.params.delta);
|
|
6820
|
-
},
|
|
6821
|
-
[TURN_COMPLETED_METHOD]: (notification) => {
|
|
6822
|
-
if (notification.params.threadId !== threadId) return;
|
|
6823
|
-
observedTurnId = notification.params.turn.id;
|
|
6824
|
-
const turn = notification.params.turn;
|
|
6825
|
-
const items = turn.items.length > 0 ? [...turn.items] : [];
|
|
6826
|
-
const itemIds = new Set(items.map((item) => item.id));
|
|
6827
|
-
for (const item of completedItems) {
|
|
6828
|
-
if (!itemIds.has(item.id)) {
|
|
6829
|
-
items.push(item);
|
|
6830
|
-
itemIds.add(item.id);
|
|
7066
|
+
let stopTail = null;
|
|
7067
|
+
let abortController = null;
|
|
7068
|
+
try {
|
|
7069
|
+
if (images && images.length > 0) {
|
|
7070
|
+
const normalizedImages = await normalizeImages(images);
|
|
7071
|
+
tempImagePaths = await saveNormalizedImagesToTempFiles(normalizedImages);
|
|
7072
|
+
}
|
|
7073
|
+
const developerInstructions = this.buildCombinedInstructions(customInstructions);
|
|
7074
|
+
await this.updateCodexConfig(developerInstructions);
|
|
7075
|
+
const sandboxMode = "danger-full-access";
|
|
7076
|
+
const webSearchMode = "live";
|
|
7077
|
+
const codexReasoningEffort = codexReasoningEffortForThinkingLevel(thinkingLevel);
|
|
7078
|
+
const additionalDirectories = await getAgentAdditionalDirectories();
|
|
7079
|
+
const threadOptions = {
|
|
7080
|
+
workingDirectory: this.workingDirectory,
|
|
7081
|
+
skipGitRepoCheck: true,
|
|
7082
|
+
sandboxMode,
|
|
7083
|
+
model: model || DEFAULT_MODEL2,
|
|
7084
|
+
webSearchMode,
|
|
7085
|
+
additionalDirectories,
|
|
7086
|
+
...codexReasoningEffort ? { modelReasoningEffort: codexReasoningEffort } : {}
|
|
7087
|
+
};
|
|
7088
|
+
abortController = new AbortController();
|
|
7089
|
+
this.activeAbortController = abortController;
|
|
7090
|
+
if (this.currentThreadId) {
|
|
7091
|
+
this.currentThread = this.codex.resumeThread(this.currentThreadId, threadOptions);
|
|
7092
|
+
} else {
|
|
7093
|
+
this.currentThread = this.codex.startThread(threadOptions);
|
|
7094
|
+
const { events } = await this.currentThread.runStreamed("Hello", { signal: abortController.signal });
|
|
7095
|
+
for await (const event of events) {
|
|
7096
|
+
if (event.type === "thread.started") {
|
|
7097
|
+
this.currentThreadId = event.thread_id;
|
|
7098
|
+
await this.onSaveSessionId(this.currentThreadId);
|
|
7099
|
+
console.log(`[CodexManager] Captured and persisted thread ID: ${this.currentThreadId}`);
|
|
6831
7100
|
}
|
|
6832
7101
|
}
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
id: itemId,
|
|
6838
|
-
text,
|
|
6839
|
-
phase: null,
|
|
6840
|
-
memoryCitation: null
|
|
6841
|
-
});
|
|
6842
|
-
}
|
|
7102
|
+
if (!this.currentThreadId && this.currentThread.id) {
|
|
7103
|
+
this.currentThreadId = this.currentThread.id;
|
|
7104
|
+
await this.onSaveSessionId(this.currentThreadId);
|
|
7105
|
+
console.log(`[CodexManager] Captured and persisted thread ID from thread.id: ${this.currentThreadId}`);
|
|
6843
7106
|
}
|
|
6844
|
-
resolveCompleted(items.length > 0 ? { ...turn, items, itemsView: "full" } : turn);
|
|
6845
7107
|
}
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
7108
|
+
stopTail = this.currentThreadId ? await this.startSessionTail(this.currentThreadId) : null;
|
|
7109
|
+
let input;
|
|
7110
|
+
if (tempImagePaths.length > 0) {
|
|
7111
|
+
const inputItems = [
|
|
7112
|
+
{ type: "text", text: message },
|
|
7113
|
+
...tempImagePaths.map((path4) => ({ type: "local_image", path: path4 }))
|
|
7114
|
+
];
|
|
7115
|
+
input = inputItems;
|
|
7116
|
+
} else {
|
|
7117
|
+
input = message;
|
|
6853
7118
|
}
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
observedTurnId = started.turn.id;
|
|
6871
|
-
this.activeTurnId = started.turn.id;
|
|
7119
|
+
try {
|
|
7120
|
+
const { events } = await this.currentThread.runStreamed(input, { signal: abortController.signal });
|
|
7121
|
+
const linearForwarder = new LinearEventForwarder(linearSessionId);
|
|
7122
|
+
for await (const event of events) {
|
|
7123
|
+
if (linearSessionId) {
|
|
7124
|
+
linearForwarder.sendPlan(extractPlanFromCodexEvent(event));
|
|
7125
|
+
linearForwarder.sendEvent(convertCodexEvent(event, linearSessionId));
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7128
|
+
linearForwarder.flushThoughtAsResponse();
|
|
7129
|
+
} catch (error) {
|
|
7130
|
+
await this.flushQuotaSnapshotFromCurrentSession();
|
|
7131
|
+
if (this.quotaStatus.blocked) {
|
|
7132
|
+
return;
|
|
7133
|
+
}
|
|
7134
|
+
throw error;
|
|
6872
7135
|
}
|
|
6873
|
-
const turn = await Promise.race([completed, disposed]);
|
|
6874
|
-
linearForwarder.flushThoughtAsResponse();
|
|
6875
|
-
return turn;
|
|
6876
7136
|
} finally {
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
7137
|
+
if (stopTail) {
|
|
7138
|
+
await stopTail();
|
|
7139
|
+
}
|
|
6880
7140
|
await removeTempImageFiles(tempImagePaths);
|
|
6881
|
-
|
|
6882
|
-
this.
|
|
7141
|
+
try {
|
|
7142
|
+
await this.onTurnComplete();
|
|
7143
|
+
} catch (error) {
|
|
7144
|
+
console.error("[CodexManager] onTurnComplete failed:", error);
|
|
6883
7145
|
}
|
|
7146
|
+
this.activeAbortController = null;
|
|
6884
7147
|
}
|
|
6885
7148
|
}
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
this.emittedItemStages.add(`started:${item.id}`);
|
|
6893
|
-
this.recordAndEmitDrafts(itemToAgentEventDrafts(item, "started"));
|
|
7149
|
+
async getHistory() {
|
|
7150
|
+
if (!this.currentThreadId) {
|
|
7151
|
+
return {
|
|
7152
|
+
thread_id: null,
|
|
7153
|
+
events: []
|
|
7154
|
+
};
|
|
6894
7155
|
}
|
|
6895
|
-
this.
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
7156
|
+
const sessionFile = await this.findSessionFile(this.currentThreadId);
|
|
7157
|
+
if (!sessionFile) {
|
|
7158
|
+
return {
|
|
7159
|
+
thread_id: this.currentThreadId,
|
|
7160
|
+
events: []
|
|
7161
|
+
};
|
|
6901
7162
|
}
|
|
7163
|
+
const events = await readJSONL(sessionFile);
|
|
7164
|
+
return {
|
|
7165
|
+
thread_id: this.currentThreadId,
|
|
7166
|
+
events
|
|
7167
|
+
};
|
|
6902
7168
|
}
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
7169
|
+
// Helper methods for finding session files
|
|
7170
|
+
async findSessionFile(threadId) {
|
|
7171
|
+
const sessionsDir = join14(homedir12(), ".codex", "sessions");
|
|
7172
|
+
try {
|
|
7173
|
+
const now = /* @__PURE__ */ new Date();
|
|
7174
|
+
const year = now.getFullYear();
|
|
7175
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
7176
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
7177
|
+
const todayDir = join14(sessionsDir, String(year), month, day);
|
|
7178
|
+
const file = await this.findFileInDirectory(todayDir, threadId);
|
|
7179
|
+
if (file) return file;
|
|
7180
|
+
for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
|
|
7181
|
+
const date = new Date(now);
|
|
7182
|
+
date.setDate(date.getDate() - daysAgo);
|
|
7183
|
+
const searchYear = date.getFullYear();
|
|
7184
|
+
const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
|
|
7185
|
+
const searchDay = String(date.getDate()).padStart(2, "0");
|
|
7186
|
+
const searchDir = join14(sessionsDir, String(searchYear), searchMonth, searchDay);
|
|
7187
|
+
const file2 = await this.findFileInDirectory(searchDir, threadId);
|
|
7188
|
+
if (file2) return file2;
|
|
6912
7189
|
}
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
this.historyEvents.splice(0, this.historyEvents.length, ...mergeHistoryEvents(this.historyEvents, events));
|
|
7190
|
+
return null;
|
|
7191
|
+
} catch (error) {
|
|
7192
|
+
return null;
|
|
7193
|
+
}
|
|
6918
7194
|
}
|
|
6919
|
-
async
|
|
7195
|
+
async findFileInDirectory(directory, threadId) {
|
|
6920
7196
|
try {
|
|
6921
|
-
const
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
7197
|
+
const files = await readdir3(directory);
|
|
7198
|
+
for (const file of files) {
|
|
7199
|
+
if (file.endsWith(".jsonl") && file.includes(threadId)) {
|
|
7200
|
+
const fullPath = join14(directory, file);
|
|
7201
|
+
const stats = await stat2(fullPath);
|
|
7202
|
+
if (stats.isFile()) {
|
|
7203
|
+
return fullPath;
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
7207
|
+
return null;
|
|
6926
7208
|
} catch (error) {
|
|
6927
|
-
|
|
7209
|
+
return null;
|
|
6928
7210
|
}
|
|
6929
7211
|
}
|
|
6930
|
-
|
|
6931
|
-
const
|
|
6932
|
-
|
|
6933
|
-
|
|
7212
|
+
async waitForSessionFile(threadId, timeoutMs = 5e3) {
|
|
7213
|
+
const start = Date.now();
|
|
7214
|
+
while (Date.now() - start < timeoutMs) {
|
|
7215
|
+
const sessionFile = await this.findSessionFile(threadId);
|
|
7216
|
+
if (sessionFile) {
|
|
7217
|
+
return sessionFile;
|
|
7218
|
+
}
|
|
7219
|
+
await sleep(100);
|
|
6934
7220
|
}
|
|
6935
|
-
|
|
6936
|
-
const event = this.recordHistoryEvent(
|
|
6937
|
-
CHAT_GOAL_EVENT_TYPE,
|
|
6938
|
-
{ goal: nextGoal }
|
|
6939
|
-
);
|
|
6940
|
-
this.onEvent(event);
|
|
7221
|
+
return null;
|
|
6941
7222
|
}
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
this.
|
|
7223
|
+
// @openai/codex-sdk doesn't expose manual /compact (TUI-only); we only mirror the auto-compaction rollout entries to the UI.
|
|
7224
|
+
trackNativeCompaction(event) {
|
|
7225
|
+
if (event.type === "compacted") {
|
|
7226
|
+
this.setCompacting(false);
|
|
7227
|
+
return;
|
|
6946
7228
|
}
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
}
|
|
6957
|
-
handleRateLimits(rateLimits) {
|
|
6958
|
-
const snapshot = extractRateLimitsSnapshot(rateLimits);
|
|
6959
|
-
if (snapshot) {
|
|
6960
|
-
this.emitQuotaStatus(snapshot);
|
|
7229
|
+
if (event.type !== "event_msg") return;
|
|
7230
|
+
const msg = event.payload.msg;
|
|
7231
|
+
if (!msg) return;
|
|
7232
|
+
const itemType = msg.payload?.item?.type;
|
|
7233
|
+
if (itemType !== "context_compaction") return;
|
|
7234
|
+
if (msg.type === "item_started") {
|
|
7235
|
+
this.setCompacting(true);
|
|
7236
|
+
} else if (msg.type === "item_completed") {
|
|
7237
|
+
this.setCompacting(false);
|
|
6961
7238
|
}
|
|
6962
7239
|
}
|
|
6963
|
-
async
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
);
|
|
6969
|
-
this.handleRateLimits(response.rateLimits);
|
|
6970
|
-
} catch {
|
|
7240
|
+
async startSessionTail(threadId) {
|
|
7241
|
+
const sessionFile = await this.waitForSessionFile(threadId);
|
|
7242
|
+
if (!sessionFile) {
|
|
7243
|
+
return async () => {
|
|
7244
|
+
};
|
|
6971
7245
|
}
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
const
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
}
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
const
|
|
6996
|
-
|
|
7246
|
+
let active = true;
|
|
7247
|
+
const seenLines = /* @__PURE__ */ new Set();
|
|
7248
|
+
const seedSeenLines = async () => {
|
|
7249
|
+
try {
|
|
7250
|
+
const content = await readFile7(sessionFile, "utf-8");
|
|
7251
|
+
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
7252
|
+
let latest = null;
|
|
7253
|
+
for (const line of lines) {
|
|
7254
|
+
seenLines.add(line);
|
|
7255
|
+
try {
|
|
7256
|
+
const parsed = JSON.parse(line);
|
|
7257
|
+
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
7258
|
+
if (snapshot) latest = snapshot;
|
|
7259
|
+
} catch {
|
|
7260
|
+
}
|
|
7261
|
+
}
|
|
7262
|
+
if (latest) {
|
|
7263
|
+
this.quotaStatus.prime(latest);
|
|
7264
|
+
}
|
|
7265
|
+
} catch {
|
|
7266
|
+
}
|
|
7267
|
+
};
|
|
7268
|
+
await seedSeenLines();
|
|
7269
|
+
const pump = async () => {
|
|
7270
|
+
let emitted = 0;
|
|
7271
|
+
try {
|
|
7272
|
+
const content = await readFile7(sessionFile, "utf-8");
|
|
7273
|
+
const lines = content.split("\n");
|
|
7274
|
+
const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
|
|
7275
|
+
for (const line of completeLines) {
|
|
7276
|
+
const trimmed = line.trim();
|
|
7277
|
+
if (!trimmed || seenLines.has(trimmed)) {
|
|
7278
|
+
continue;
|
|
7279
|
+
}
|
|
7280
|
+
seenLines.add(trimmed);
|
|
7281
|
+
try {
|
|
7282
|
+
const parsed = JSON.parse(trimmed);
|
|
7283
|
+
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
7284
|
+
if (snapshot) {
|
|
7285
|
+
this.emitQuotaStatus(snapshot);
|
|
7286
|
+
}
|
|
7287
|
+
if (isJsonlEvent2(parsed)) {
|
|
7288
|
+
this.trackNativeCompaction(parsed);
|
|
7289
|
+
this.onEvent(parsed);
|
|
7290
|
+
emitted += 1;
|
|
7291
|
+
}
|
|
7292
|
+
} catch {
|
|
7293
|
+
}
|
|
7294
|
+
}
|
|
7295
|
+
} catch {
|
|
7296
|
+
}
|
|
7297
|
+
return emitted;
|
|
7298
|
+
};
|
|
7299
|
+
const loop = (async () => {
|
|
7300
|
+
while (active) {
|
|
7301
|
+
await pump();
|
|
7302
|
+
await sleep(100);
|
|
7303
|
+
}
|
|
7304
|
+
await pump();
|
|
7305
|
+
})();
|
|
7306
|
+
return async () => {
|
|
7307
|
+
active = false;
|
|
7308
|
+
await loop;
|
|
7309
|
+
const deadline = Date.now() + 1500;
|
|
7310
|
+
while (Date.now() < deadline) {
|
|
7311
|
+
const emitted = await pump();
|
|
7312
|
+
if (emitted > 0) {
|
|
7313
|
+
continue;
|
|
7314
|
+
}
|
|
7315
|
+
await sleep(100);
|
|
7316
|
+
}
|
|
7317
|
+
};
|
|
6997
7318
|
}
|
|
6998
7319
|
};
|
|
6999
7320
|
|
|
@@ -7610,18 +7931,58 @@ var RELAY_HISTORY_DIR = join15(ENGINE_DIR2, "relay-histories");
|
|
|
7610
7931
|
var CHAT_SENDERS_DIR = join15(ENGINE_DIR2, "chat-senders");
|
|
7611
7932
|
var CODEX_AUTH_PATH2 = join15(homedir13(), ".codex", "auth.json");
|
|
7612
7933
|
function isChatMessageSender(value) {
|
|
7613
|
-
if (!
|
|
7934
|
+
if (!isRecord4(value)) return false;
|
|
7614
7935
|
return typeof value.senderUserId === "string" && typeof value.senderEmail === "string" && typeof value.recordedAt === "string";
|
|
7615
7936
|
}
|
|
7616
7937
|
function isCodexAvailable() {
|
|
7617
7938
|
return existsSync7(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
|
|
7618
7939
|
}
|
|
7940
|
+
function isSameAcceptedUserEvent(event, acceptedEvent) {
|
|
7941
|
+
if (areSameUserMessageEvents(event, acceptedEvent)) return true;
|
|
7942
|
+
const eventMessage = getUserMessage(event);
|
|
7943
|
+
const acceptedMessage = getUserMessage(acceptedEvent);
|
|
7944
|
+
return Boolean(eventMessage) && eventMessage === acceptedMessage && Math.abs(getEventTimestampMs(event) - getEventTimestampMs(acceptedEvent)) <= 3e4;
|
|
7945
|
+
}
|
|
7946
|
+
function getCodexTranscriptUserMessages(transcript) {
|
|
7947
|
+
if (!transcript) return [];
|
|
7948
|
+
const messages = [];
|
|
7949
|
+
for (const turn of transcript.turns) {
|
|
7950
|
+
for (const item of turn.items) {
|
|
7951
|
+
if (item.type === "userMessage" && item.content.trim()) {
|
|
7952
|
+
messages.push(item.content);
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
}
|
|
7956
|
+
return messages;
|
|
7957
|
+
}
|
|
7958
|
+
function getCodexTranscriptFromEvent(event) {
|
|
7959
|
+
if (event.type !== CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE) return null;
|
|
7960
|
+
const transcript = event.payload.transcript;
|
|
7961
|
+
return isCodexAspTranscript(transcript) ? transcript : null;
|
|
7962
|
+
}
|
|
7963
|
+
function acceptedEventInCodexTranscript(acceptedEvent, transcript) {
|
|
7964
|
+
const message = getUserMessage(acceptedEvent);
|
|
7965
|
+
if (!message) return false;
|
|
7966
|
+
return getCodexTranscriptUserMessages(transcript).includes(message);
|
|
7967
|
+
}
|
|
7619
7968
|
function isPersistedChat(value) {
|
|
7620
|
-
if (!
|
|
7969
|
+
if (!isRecord4(value)) {
|
|
7621
7970
|
return false;
|
|
7622
7971
|
}
|
|
7623
7972
|
const candidate = value;
|
|
7624
|
-
return typeof candidate.id === "string" && (candidate.provider === "claude" || candidate.provider === "codex" || candidate.provider === "relay") && typeof candidate.title === "string" && typeof candidate.createdAt === "string" && typeof candidate.updatedAt === "string" && (candidate.providerSessionId === null || typeof candidate.providerSessionId === "string") && (candidate.parentChatId === void 0 || candidate.parentChatId === null || typeof candidate.parentChatId === "string");
|
|
7973
|
+
return typeof candidate.id === "string" && (candidate.provider === "claude" || candidate.provider === "codex" || candidate.provider === "relay") && typeof candidate.title === "string" && typeof candidate.createdAt === "string" && typeof candidate.updatedAt === "string" && (candidate.providerSessionId === null || typeof candidate.providerSessionId === "string") && (candidate.parentChatId === void 0 || candidate.parentChatId === null || typeof candidate.parentChatId === "string") && (candidate.codexBackend === void 0 || candidate.codexBackend === "sdk" || candidate.codexBackend === "asp");
|
|
7974
|
+
}
|
|
7975
|
+
function codexBackendForChat(chat) {
|
|
7976
|
+
if (chat.provider !== "codex") return "asp";
|
|
7977
|
+
if (chat.codexBackend) return chat.codexBackend;
|
|
7978
|
+
return chat.providerSessionId ? "sdk" : "asp";
|
|
7979
|
+
}
|
|
7980
|
+
function normalizePersistedChat(chat) {
|
|
7981
|
+
return {
|
|
7982
|
+
...chat,
|
|
7983
|
+
parentChatId: chat.parentChatId ?? null,
|
|
7984
|
+
...chat.provider === "codex" ? { codexBackend: codexBackendForChat(chat) } : {}
|
|
7985
|
+
};
|
|
7625
7986
|
}
|
|
7626
7987
|
function createUserMessageEvent(message, messageId) {
|
|
7627
7988
|
return {
|
|
@@ -7635,7 +7996,6 @@ function createUserMessageEvent(message, messageId) {
|
|
|
7635
7996
|
}
|
|
7636
7997
|
};
|
|
7637
7998
|
}
|
|
7638
|
-
var isSameUserMessageEvent = areSameUserMessageEvents;
|
|
7639
7999
|
var ChatService = class {
|
|
7640
8000
|
constructor(workingDirectory) {
|
|
7641
8001
|
this.workingDirectory = workingDirectory;
|
|
@@ -7699,7 +8059,8 @@ var ChatService = class {
|
|
|
7699
8059
|
createdAt: now,
|
|
7700
8060
|
updatedAt: now,
|
|
7701
8061
|
providerSessionId: null,
|
|
7702
|
-
parentChatId
|
|
8062
|
+
parentChatId,
|
|
8063
|
+
...request.provider === "codex" ? { codexBackend: "asp" } : {}
|
|
7703
8064
|
};
|
|
7704
8065
|
const runtime = this.createRuntimeChat(persisted);
|
|
7705
8066
|
this.chats.set(persisted.id, runtime);
|
|
@@ -7796,6 +8157,37 @@ var ChatService = class {
|
|
|
7796
8157
|
});
|
|
7797
8158
|
return result;
|
|
7798
8159
|
}
|
|
8160
|
+
async clearGoal(chatId) {
|
|
8161
|
+
const chat = this.requireChat(chatId);
|
|
8162
|
+
if (!chat.provider.clearGoal) {
|
|
8163
|
+
return { interrupted: false, queue: [], goal: null };
|
|
8164
|
+
}
|
|
8165
|
+
const interruptResult = await chat.provider.interrupt();
|
|
8166
|
+
chat.hasActiveTurn = false;
|
|
8167
|
+
chat.activeMessageId = null;
|
|
8168
|
+
keepAliveService.stop();
|
|
8169
|
+
chat.pendingMessageIds = [];
|
|
8170
|
+
chat.acceptedUserEvents.clear();
|
|
8171
|
+
const goal = await chat.provider.clearGoal();
|
|
8172
|
+
this.touch(chat);
|
|
8173
|
+
await this.publish({
|
|
8174
|
+
type: "chat.interrupted",
|
|
8175
|
+
payload: {
|
|
8176
|
+
chatId,
|
|
8177
|
+
interrupted: interruptResult.interrupted,
|
|
8178
|
+
queue: interruptResult.queue
|
|
8179
|
+
}
|
|
8180
|
+
});
|
|
8181
|
+
await this.publish({
|
|
8182
|
+
type: "chat.updated",
|
|
8183
|
+
payload: { chat: this.toSummary(chat) }
|
|
8184
|
+
});
|
|
8185
|
+
return {
|
|
8186
|
+
interrupted: interruptResult.interrupted,
|
|
8187
|
+
queue: interruptResult.queue,
|
|
8188
|
+
goal
|
|
8189
|
+
};
|
|
8190
|
+
}
|
|
7799
8191
|
getChatQueue(chatId) {
|
|
7800
8192
|
const chat = this.requireChat(chatId);
|
|
7801
8193
|
return {
|
|
@@ -7874,10 +8266,17 @@ var ChatService = class {
|
|
|
7874
8266
|
chat.provider.getHistory(),
|
|
7875
8267
|
this.readSenders(chatId)
|
|
7876
8268
|
]);
|
|
7877
|
-
const
|
|
8269
|
+
for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
|
|
8270
|
+
if (acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript)) {
|
|
8271
|
+
chat.acceptedUserEvents.delete(messageId);
|
|
8272
|
+
}
|
|
8273
|
+
}
|
|
8274
|
+
const acceptedEvents = [...chat.acceptedUserEvents.values()].filter((acceptedEvent) => !acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) && !history.events.some((event) => isSameAcceptedUserEvent(event, acceptedEvent)));
|
|
8275
|
+
const events = [...history.events, ...acceptedEvents].sort((a, b) => getEventTimestampMs(a) - getEventTimestampMs(b));
|
|
7878
8276
|
return {
|
|
7879
8277
|
thread_id: history.thread_id,
|
|
7880
|
-
events
|
|
8278
|
+
events,
|
|
8279
|
+
codexAspTranscript: history.codexAspTranscript ?? null,
|
|
7881
8280
|
goal: history.goal ?? chat.provider.getGoal?.() ?? null,
|
|
7882
8281
|
senders
|
|
7883
8282
|
};
|
|
@@ -7932,7 +8331,7 @@ var ChatService = class {
|
|
|
7932
8331
|
codexAvailable: isCodexAvailable()
|
|
7933
8332
|
});
|
|
7934
8333
|
} else {
|
|
7935
|
-
const CodexProviderCtor =
|
|
8334
|
+
const CodexProviderCtor = codexBackendForChat(persisted) === "sdk" ? CodexManager : CodexAspManager;
|
|
7936
8335
|
provider = new CodexProviderCtor({
|
|
7937
8336
|
workingDirectory: this.workingDirectory,
|
|
7938
8337
|
initialSessionId: persisted.providerSessionId,
|
|
@@ -8013,12 +8412,20 @@ var ChatService = class {
|
|
|
8013
8412
|
};
|
|
8014
8413
|
}
|
|
8015
8414
|
for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
|
|
8016
|
-
if (
|
|
8415
|
+
if (isSameAcceptedUserEvent(event, acceptedEvent)) {
|
|
8017
8416
|
chat.acceptedUserEvents.delete(messageId);
|
|
8018
8417
|
break;
|
|
8019
8418
|
}
|
|
8020
8419
|
}
|
|
8021
8420
|
}
|
|
8421
|
+
const codexTranscript = getCodexTranscriptFromEvent(event);
|
|
8422
|
+
if (codexTranscript) {
|
|
8423
|
+
for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
|
|
8424
|
+
if (acceptedEventInCodexTranscript(acceptedEvent, codexTranscript)) {
|
|
8425
|
+
chat.acceptedUserEvents.delete(messageId);
|
|
8426
|
+
}
|
|
8427
|
+
}
|
|
8428
|
+
}
|
|
8022
8429
|
this.touch(chat);
|
|
8023
8430
|
this.observeCurrentBranches(chat).catch(() => {
|
|
8024
8431
|
});
|
|
@@ -8072,7 +8479,7 @@ var ChatService = class {
|
|
|
8072
8479
|
if (!Array.isArray(parsed)) {
|
|
8073
8480
|
return [];
|
|
8074
8481
|
}
|
|
8075
|
-
return parsed.filter((entry) => isPersistedChat(entry));
|
|
8482
|
+
return parsed.filter((entry) => isPersistedChat(entry)).map((entry) => normalizePersistedChat(entry));
|
|
8076
8483
|
} catch (error) {
|
|
8077
8484
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
8078
8485
|
return [];
|
|
@@ -9067,6 +9474,17 @@ function createV1Routes(deps) {
|
|
|
9067
9474
|
return c.json(jsonError("Failed to interrupt chat", error instanceof Error ? error.message : "Unknown error"), 404);
|
|
9068
9475
|
}
|
|
9069
9476
|
});
|
|
9477
|
+
app2.post("/chats/:chatId/goal/clear", async (c) => {
|
|
9478
|
+
try {
|
|
9479
|
+
const result = await deps.chatService.clearGoal(c.req.param("chatId"));
|
|
9480
|
+
return c.json(result);
|
|
9481
|
+
} catch (error) {
|
|
9482
|
+
if (error instanceof ChatNotFoundError) {
|
|
9483
|
+
return c.json(jsonError("Failed to clear goal", error.message), 404);
|
|
9484
|
+
}
|
|
9485
|
+
return c.json(jsonError("Failed to clear goal", error instanceof Error ? error.message : "Unknown error"), 404);
|
|
9486
|
+
}
|
|
9487
|
+
});
|
|
9070
9488
|
app2.get("/chats/:chatId/queue", (c) => {
|
|
9071
9489
|
try {
|
|
9072
9490
|
const result = deps.chatService.getChatQueue(c.req.param("chatId"));
|