hankweave 0.5.7 → 0.6.2
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/README.md +12 -11
- package/dist/base-process-manager.d.ts +30 -0
- package/dist/budget.d.ts +315 -0
- package/dist/checkpoint-git.d.ts +98 -0
- package/dist/claude-agent-sdk-manager.d.ts +144 -0
- package/dist/claude-log-parser.d.ts +63 -0
- package/dist/claude-runtime-extractor.d.ts +73 -0
- package/dist/codex-runtime-extractor.d.ts +107 -0
- package/dist/codon-runner.d.ts +278 -0
- package/dist/config-validation/model-validator.d.ts +16 -0
- package/dist/config-validation/sentinel.schema.d.ts +6967 -0
- package/dist/config.d.ts +40815 -0
- package/dist/cost-tracker.d.ts +72 -0
- package/dist/execution-planner.d.ts +62 -0
- package/dist/execution-thread.d.ts +71 -0
- package/dist/exports/schemas.d.ts +9 -0
- package/dist/exports/schemas.js +1019 -0
- package/dist/exports/types.d.ts +15 -0
- package/dist/exports/types.js +60 -0
- package/dist/file-resolver.d.ts +33 -0
- package/dist/index.js +380 -293
- package/dist/index.js.map +33 -29
- package/dist/llm/llm-provider-registry.d.ts +207 -0
- package/dist/llm/models-dev-schema.d.ts +679 -0
- package/dist/llm/provider-config.d.ts +30 -0
- package/dist/prompt-builder.d.ts +75 -0
- package/dist/prompt-frontmatter.d.ts +61 -0
- package/dist/replay-process-manager.d.ts +82 -0
- package/dist/runtime-extractor-base.d.ts +120 -0
- package/dist/schemas/event-schemas.d.ts +8389 -0
- package/dist/schemas/websocket-log-schemas.d.ts +4502 -0
- package/dist/shim-process-manager.d.ts +98 -0
- package/dist/shim-runtime-extractor.d.ts +51 -0
- package/dist/shims/codex/README.md +129 -0
- package/dist/shims/codex/THIRDPARTY.md +18 -0
- package/dist/shims/codex/VERSION +1 -0
- package/dist/shims/codex/common/package.json +24 -0
- package/dist/shims/codex/index.js +1154 -970
- package/dist/shims/codex/package.json +46 -0
- package/dist/shims/codex/tsup.config.ts +16 -0
- package/dist/shims/gemini/README.md +59 -0
- package/dist/shims/gemini/THIRDPARTY.md +32 -0
- package/dist/shims/gemini/VERSION +1 -0
- package/dist/shims/gemini/common/package.json +24 -0
- package/dist/shims/gemini/index.js +1359 -30
- package/dist/shims/gemini/package.json +37 -0
- package/dist/shims/opencode/README.md +82 -0
- package/dist/shims/opencode/THIRDPARTY.md +32 -0
- package/dist/shims/opencode/VERSION +1 -0
- package/dist/shims/opencode/common/package.json +24 -0
- package/dist/shims/opencode/index.js +1476 -0
- package/dist/shims/opencode/package.json +38 -0
- package/dist/shims/pi/README.md +87 -0
- package/dist/shims/pi/THIRDPARTY.md +24 -0
- package/dist/shims/pi/VERSION +1 -0
- package/dist/shims/pi/common/package.json +24 -0
- package/dist/shims/pi/index.js +249832 -0
- package/dist/shims/pi/package.json +53 -0
- package/dist/state-manager.d.ts +161 -0
- package/dist/state-transition-guards.d.ts +37 -0
- package/dist/telemetry/telemetry-types.d.ts +206 -0
- package/dist/typed-event-emitter.d.ts +57 -0
- package/dist/types/branded-types.d.ts +15 -0
- package/dist/types/budget-types.d.ts +82 -0
- package/dist/types/claude-session-schema.d.ts +2430 -0
- package/dist/types/error-types.d.ts +44 -0
- package/dist/types/input-ai-types.d.ts +1070 -0
- package/dist/types/llm-call-types.d.ts +3829 -0
- package/dist/types/sentinel-types.d.ts +66 -0
- package/dist/types/state-types.d.ts +1099 -0
- package/dist/types/tool-types.d.ts +86 -0
- package/dist/types/types.d.ts +367 -0
- package/dist/types/websocket-log-types.d.ts +7 -0
- package/dist/utils.d.ts +452 -0
- package/package.json +15 -2
- package/schemas/hank.schema.json +158 -3
- package/schemas/hankweave.schema.json +17 -1
- package/shims/codex/index.js +0 -1583
- package/shims/gemini/index.js +0 -31
package/shims/codex/index.js
DELETED
|
@@ -1,1583 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// ../common/src/args.ts
|
|
4
|
-
var VALID_SANDBOX_LEVELS = ["none", "standard", "strict"];
|
|
5
|
-
function parseArgs(argv, aliases) {
|
|
6
|
-
const args = {
|
|
7
|
-
model: "",
|
|
8
|
-
verbose: false,
|
|
9
|
-
idleTimeout: 120,
|
|
10
|
-
sandbox: "none",
|
|
11
|
-
selfTest: false,
|
|
12
|
-
version: false,
|
|
13
|
-
help: false
|
|
14
|
-
};
|
|
15
|
-
for (let i = 0; i < argv.length; i++) {
|
|
16
|
-
let arg = argv[i];
|
|
17
|
-
if (arg.includes("=")) {
|
|
18
|
-
const [key, value] = arg.split("=", 2);
|
|
19
|
-
argv.splice(i, 1, key, value);
|
|
20
|
-
arg = key;
|
|
21
|
-
}
|
|
22
|
-
if (aliases && arg in aliases) {
|
|
23
|
-
arg = aliases[arg];
|
|
24
|
-
}
|
|
25
|
-
switch (arg) {
|
|
26
|
-
case "--model":
|
|
27
|
-
args.model = argv[++i];
|
|
28
|
-
break;
|
|
29
|
-
case "--resume":
|
|
30
|
-
args.resume = argv[++i];
|
|
31
|
-
break;
|
|
32
|
-
case "--verbose":
|
|
33
|
-
args.verbose = true;
|
|
34
|
-
break;
|
|
35
|
-
case "--append-system-prompt":
|
|
36
|
-
args.appendSystemPrompt = argv[++i];
|
|
37
|
-
break;
|
|
38
|
-
case "--debug-dir":
|
|
39
|
-
args.debugDir = argv[++i];
|
|
40
|
-
break;
|
|
41
|
-
case "--idle-timeout": {
|
|
42
|
-
const val = Number(argv[++i]);
|
|
43
|
-
if (!Number.isFinite(val) || val <= 0) {
|
|
44
|
-
console.error("Invalid --idle-timeout value: must be a positive number");
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
args.idleTimeout = val;
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
case "--sandbox": {
|
|
51
|
-
const level = argv[++i];
|
|
52
|
-
if (!VALID_SANDBOX_LEVELS.includes(level)) {
|
|
53
|
-
console.error(
|
|
54
|
-
`Invalid --sandbox value: must be one of ${VALID_SANDBOX_LEVELS.join(", ")}`
|
|
55
|
-
);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
args.sandbox = level;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
case "--self-test":
|
|
62
|
-
args.selfTest = true;
|
|
63
|
-
break;
|
|
64
|
-
case "--version":
|
|
65
|
-
args.version = true;
|
|
66
|
-
break;
|
|
67
|
-
case "--help":
|
|
68
|
-
args.help = true;
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return args;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// src/selftest.ts
|
|
76
|
-
import { spawn } from "child_process";
|
|
77
|
-
import { existsSync as existsSync2 } from "fs";
|
|
78
|
-
import { isAbsolute } from "path";
|
|
79
|
-
|
|
80
|
-
// src/utils/auth.ts
|
|
81
|
-
import * as fs from "fs";
|
|
82
|
-
import * as os from "os";
|
|
83
|
-
import * as path from "path";
|
|
84
|
-
function getAuthFilePath() {
|
|
85
|
-
const homeDir = os.homedir();
|
|
86
|
-
return path.join(homeDir, ".codex", "auth.json");
|
|
87
|
-
}
|
|
88
|
-
function readAuthFile() {
|
|
89
|
-
try {
|
|
90
|
-
const authPath = getAuthFilePath();
|
|
91
|
-
if (!fs.existsSync(authPath)) {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
return fs.readFileSync(authPath, "utf8");
|
|
95
|
-
} catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function getApiKey() {
|
|
100
|
-
const authFileKey = readAuthFile();
|
|
101
|
-
if (authFileKey) {
|
|
102
|
-
return { apiKey: authFileKey, source: "env" };
|
|
103
|
-
}
|
|
104
|
-
if (process.env.CODEX_API_KEY) {
|
|
105
|
-
return { apiKey: process.env.CODEX_API_KEY, source: "CODEX_API_KEY" };
|
|
106
|
-
}
|
|
107
|
-
if (process.env.OPENAI_API_KEY) {
|
|
108
|
-
return { apiKey: process.env.OPENAI_API_KEY, source: "OPENAI_API_KEY" };
|
|
109
|
-
}
|
|
110
|
-
return { apiKey: null, source: "none" };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// src/selftest.ts
|
|
114
|
-
async function isCodexInstalled() {
|
|
115
|
-
return new Promise((resolve) => {
|
|
116
|
-
const isWindows = process.platform === "win32";
|
|
117
|
-
const codexCommand = process.env.CODEX_PATH_OVERRIDE || "codex";
|
|
118
|
-
if (isAbsolute(codexCommand)) {
|
|
119
|
-
if (existsSync2(codexCommand)) {
|
|
120
|
-
const versionProc = spawn(codexCommand, ["--version"], { shell: isWindows });
|
|
121
|
-
let version = "unknown";
|
|
122
|
-
versionProc.stdout.on("data", (data) => {
|
|
123
|
-
version = data.toString().trim();
|
|
124
|
-
});
|
|
125
|
-
versionProc.on("close", () => {
|
|
126
|
-
resolve({ found: true, version });
|
|
127
|
-
});
|
|
128
|
-
} else {
|
|
129
|
-
resolve({ found: false, version: "N/A" });
|
|
130
|
-
}
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const whichCommand = isWindows ? "where" : "which";
|
|
134
|
-
const proc = spawn(whichCommand, [codexCommand], { shell: isWindows });
|
|
135
|
-
let found = false;
|
|
136
|
-
proc.on("close", (code) => {
|
|
137
|
-
if (code === 0) {
|
|
138
|
-
found = true;
|
|
139
|
-
}
|
|
140
|
-
if (found) {
|
|
141
|
-
const versionProc = spawn(codexCommand, ["--version"], { shell: isWindows });
|
|
142
|
-
let version = "unknown";
|
|
143
|
-
versionProc.stdout.on("data", (data) => {
|
|
144
|
-
version = data.toString().trim();
|
|
145
|
-
});
|
|
146
|
-
versionProc.on("close", () => {
|
|
147
|
-
resolve({ found: true, version });
|
|
148
|
-
});
|
|
149
|
-
} else {
|
|
150
|
-
resolve({ found: false, version: "N/A" });
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
function checkApiKey() {
|
|
156
|
-
const authConfig = getApiKey();
|
|
157
|
-
if (authConfig.apiKey) {
|
|
158
|
-
const sourceMessages = {
|
|
159
|
-
env: "API key found in ~/.codex/auth.json",
|
|
160
|
-
CODEX_API_KEY: "CODEX_API_KEY found in environment",
|
|
161
|
-
OPENAI_API_KEY: "OPENAI_API_KEY found in environment",
|
|
162
|
-
none: "No API key found"
|
|
163
|
-
};
|
|
164
|
-
return {
|
|
165
|
-
passed: true,
|
|
166
|
-
message: sourceMessages[authConfig.source] || "API key found"
|
|
167
|
-
};
|
|
168
|
-
} else {
|
|
169
|
-
return {
|
|
170
|
-
passed: false,
|
|
171
|
-
message: "No API key found. Set CODEX_API_KEY or OPENAI_API_KEY environment variable, or create ~/.codex/auth.json with apiKey field."
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
async function runSelfTest() {
|
|
176
|
-
const checks = [];
|
|
177
|
-
const codexStatus = await isCodexInstalled();
|
|
178
|
-
checks.push({
|
|
179
|
-
name: "codex_cli_found",
|
|
180
|
-
passed: codexStatus.found,
|
|
181
|
-
message: codexStatus.found ? `Codex CLI found (version: ${codexStatus.version})` : "Codex CLI not found in PATH. Install from https://developers.openai.com/codex/"
|
|
182
|
-
});
|
|
183
|
-
const apiKeyStatus = checkApiKey();
|
|
184
|
-
checks.push({
|
|
185
|
-
name: "api_key",
|
|
186
|
-
passed: apiKeyStatus.passed,
|
|
187
|
-
message: apiKeyStatus.message
|
|
188
|
-
});
|
|
189
|
-
const nodeVersion = process.version;
|
|
190
|
-
const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0]);
|
|
191
|
-
const nodeVersionOk = majorVersion >= 18;
|
|
192
|
-
checks.push({
|
|
193
|
-
name: "node_version",
|
|
194
|
-
passed: nodeVersionOk,
|
|
195
|
-
message: nodeVersionOk ? `Node.js version ${nodeVersion} is compatible` : `Node.js version ${nodeVersion} is too old. Requires Node.js 18+`
|
|
196
|
-
});
|
|
197
|
-
const allPassed = checks.every((c) => c.passed);
|
|
198
|
-
return {
|
|
199
|
-
shim: {
|
|
200
|
-
name: "codex-shim",
|
|
201
|
-
version: "1.0.0"
|
|
202
|
-
},
|
|
203
|
-
agent: {
|
|
204
|
-
name: "Codex",
|
|
205
|
-
version: codexStatus.version,
|
|
206
|
-
found: codexStatus.found
|
|
207
|
-
},
|
|
208
|
-
checks,
|
|
209
|
-
overall: {
|
|
210
|
-
passed: allPassed,
|
|
211
|
-
message: allPassed ? "All checks passed" : "Some checks failed"
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// src/shim.ts
|
|
217
|
-
import * as fs4 from "fs";
|
|
218
|
-
import * as path4 from "path";
|
|
219
|
-
|
|
220
|
-
// ../../node_modules/.bun/@openai+codex-sdk@0.98.0/node_modules/@openai/codex-sdk/dist/index.js
|
|
221
|
-
import { promises as fs2 } from "fs";
|
|
222
|
-
import os2 from "os";
|
|
223
|
-
import path2 from "path";
|
|
224
|
-
import { spawn as spawn2 } from "child_process";
|
|
225
|
-
import path22 from "path";
|
|
226
|
-
import readline from "readline";
|
|
227
|
-
import { fileURLToPath } from "url";
|
|
228
|
-
async function createOutputSchemaFile(schema) {
|
|
229
|
-
if (schema === void 0) {
|
|
230
|
-
return { cleanup: async () => {
|
|
231
|
-
} };
|
|
232
|
-
}
|
|
233
|
-
if (!isJsonObject(schema)) {
|
|
234
|
-
throw new Error("outputSchema must be a plain JSON object");
|
|
235
|
-
}
|
|
236
|
-
const schemaDir = await fs2.mkdtemp(path2.join(os2.tmpdir(), "codex-output-schema-"));
|
|
237
|
-
const schemaPath = path2.join(schemaDir, "schema.json");
|
|
238
|
-
const cleanup = async () => {
|
|
239
|
-
try {
|
|
240
|
-
await fs2.rm(schemaDir, { recursive: true, force: true });
|
|
241
|
-
} catch {
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
try {
|
|
245
|
-
await fs2.writeFile(schemaPath, JSON.stringify(schema), "utf8");
|
|
246
|
-
return { schemaPath, cleanup };
|
|
247
|
-
} catch (error) {
|
|
248
|
-
await cleanup();
|
|
249
|
-
throw error;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
function isJsonObject(value) {
|
|
253
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
254
|
-
}
|
|
255
|
-
var Thread = class {
|
|
256
|
-
_exec;
|
|
257
|
-
_options;
|
|
258
|
-
_id;
|
|
259
|
-
_threadOptions;
|
|
260
|
-
/** Returns the ID of the thread. Populated after the first turn starts. */
|
|
261
|
-
get id() {
|
|
262
|
-
return this._id;
|
|
263
|
-
}
|
|
264
|
-
/* @internal */
|
|
265
|
-
constructor(exec, options, threadOptions, id = null) {
|
|
266
|
-
this._exec = exec;
|
|
267
|
-
this._options = options;
|
|
268
|
-
this._id = id;
|
|
269
|
-
this._threadOptions = threadOptions;
|
|
270
|
-
}
|
|
271
|
-
/** Provides the input to the agent and streams events as they are produced during the turn. */
|
|
272
|
-
async runStreamed(input, turnOptions = {}) {
|
|
273
|
-
return { events: this.runStreamedInternal(input, turnOptions) };
|
|
274
|
-
}
|
|
275
|
-
async *runStreamedInternal(input, turnOptions = {}) {
|
|
276
|
-
const { schemaPath, cleanup } = await createOutputSchemaFile(turnOptions.outputSchema);
|
|
277
|
-
const options = this._threadOptions;
|
|
278
|
-
const { prompt, images } = normalizeInput(input);
|
|
279
|
-
const generator = this._exec.run({
|
|
280
|
-
input: prompt,
|
|
281
|
-
baseUrl: this._options.baseUrl,
|
|
282
|
-
apiKey: this._options.apiKey,
|
|
283
|
-
threadId: this._id,
|
|
284
|
-
images,
|
|
285
|
-
model: options?.model,
|
|
286
|
-
sandboxMode: options?.sandboxMode,
|
|
287
|
-
workingDirectory: options?.workingDirectory,
|
|
288
|
-
skipGitRepoCheck: options?.skipGitRepoCheck,
|
|
289
|
-
outputSchemaFile: schemaPath,
|
|
290
|
-
modelReasoningEffort: options?.modelReasoningEffort,
|
|
291
|
-
signal: turnOptions.signal,
|
|
292
|
-
networkAccessEnabled: options?.networkAccessEnabled,
|
|
293
|
-
webSearchMode: options?.webSearchMode,
|
|
294
|
-
webSearchEnabled: options?.webSearchEnabled,
|
|
295
|
-
approvalPolicy: options?.approvalPolicy,
|
|
296
|
-
additionalDirectories: options?.additionalDirectories
|
|
297
|
-
});
|
|
298
|
-
try {
|
|
299
|
-
for await (const item of generator) {
|
|
300
|
-
let parsed;
|
|
301
|
-
try {
|
|
302
|
-
parsed = JSON.parse(item);
|
|
303
|
-
} catch (error) {
|
|
304
|
-
throw new Error(`Failed to parse item: ${item}`, { cause: error });
|
|
305
|
-
}
|
|
306
|
-
if (parsed.type === "thread.started") {
|
|
307
|
-
this._id = parsed.thread_id;
|
|
308
|
-
}
|
|
309
|
-
yield parsed;
|
|
310
|
-
}
|
|
311
|
-
} finally {
|
|
312
|
-
await cleanup();
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
/** Provides the input to the agent and returns the completed turn. */
|
|
316
|
-
async run(input, turnOptions = {}) {
|
|
317
|
-
const generator = this.runStreamedInternal(input, turnOptions);
|
|
318
|
-
const items = [];
|
|
319
|
-
let finalResponse = "";
|
|
320
|
-
let usage = null;
|
|
321
|
-
let turnFailure = null;
|
|
322
|
-
for await (const event of generator) {
|
|
323
|
-
if (event.type === "item.completed") {
|
|
324
|
-
if (event.item.type === "agent_message") {
|
|
325
|
-
finalResponse = event.item.text;
|
|
326
|
-
}
|
|
327
|
-
items.push(event.item);
|
|
328
|
-
} else if (event.type === "turn.completed") {
|
|
329
|
-
usage = event.usage;
|
|
330
|
-
} else if (event.type === "turn.failed") {
|
|
331
|
-
turnFailure = event.error;
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
if (turnFailure) {
|
|
336
|
-
throw new Error(turnFailure.message);
|
|
337
|
-
}
|
|
338
|
-
return { items, finalResponse, usage };
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
function normalizeInput(input) {
|
|
342
|
-
if (typeof input === "string") {
|
|
343
|
-
return { prompt: input, images: [] };
|
|
344
|
-
}
|
|
345
|
-
const promptParts = [];
|
|
346
|
-
const images = [];
|
|
347
|
-
for (const item of input) {
|
|
348
|
-
if (item.type === "text") {
|
|
349
|
-
promptParts.push(item.text);
|
|
350
|
-
} else if (item.type === "local_image") {
|
|
351
|
-
images.push(item.path);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return { prompt: promptParts.join("\n\n"), images };
|
|
355
|
-
}
|
|
356
|
-
var INTERNAL_ORIGINATOR_ENV = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE";
|
|
357
|
-
var TYPESCRIPT_SDK_ORIGINATOR = "codex_sdk_ts";
|
|
358
|
-
var CodexExec = class {
|
|
359
|
-
executablePath;
|
|
360
|
-
envOverride;
|
|
361
|
-
configOverrides;
|
|
362
|
-
constructor(executablePath = null, env, configOverrides) {
|
|
363
|
-
this.executablePath = executablePath || findCodexPath();
|
|
364
|
-
this.envOverride = env;
|
|
365
|
-
this.configOverrides = configOverrides;
|
|
366
|
-
}
|
|
367
|
-
async *run(args) {
|
|
368
|
-
const commandArgs = ["exec", "--experimental-json"];
|
|
369
|
-
if (this.configOverrides) {
|
|
370
|
-
for (const override of serializeConfigOverrides(this.configOverrides)) {
|
|
371
|
-
commandArgs.push("--config", override);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
if (args.model) {
|
|
375
|
-
commandArgs.push("--model", args.model);
|
|
376
|
-
}
|
|
377
|
-
if (args.sandboxMode) {
|
|
378
|
-
commandArgs.push("--sandbox", args.sandboxMode);
|
|
379
|
-
}
|
|
380
|
-
if (args.workingDirectory) {
|
|
381
|
-
commandArgs.push("--cd", args.workingDirectory);
|
|
382
|
-
}
|
|
383
|
-
if (args.additionalDirectories?.length) {
|
|
384
|
-
for (const dir of args.additionalDirectories) {
|
|
385
|
-
commandArgs.push("--add-dir", dir);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
if (args.skipGitRepoCheck) {
|
|
389
|
-
commandArgs.push("--skip-git-repo-check");
|
|
390
|
-
}
|
|
391
|
-
if (args.outputSchemaFile) {
|
|
392
|
-
commandArgs.push("--output-schema", args.outputSchemaFile);
|
|
393
|
-
}
|
|
394
|
-
if (args.modelReasoningEffort) {
|
|
395
|
-
commandArgs.push("--config", `model_reasoning_effort="${args.modelReasoningEffort}"`);
|
|
396
|
-
}
|
|
397
|
-
if (args.networkAccessEnabled !== void 0) {
|
|
398
|
-
commandArgs.push(
|
|
399
|
-
"--config",
|
|
400
|
-
`sandbox_workspace_write.network_access=${args.networkAccessEnabled}`
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
if (args.webSearchMode) {
|
|
404
|
-
commandArgs.push("--config", `web_search="${args.webSearchMode}"`);
|
|
405
|
-
} else if (args.webSearchEnabled === true) {
|
|
406
|
-
commandArgs.push("--config", `web_search="live"`);
|
|
407
|
-
} else if (args.webSearchEnabled === false) {
|
|
408
|
-
commandArgs.push("--config", `web_search="disabled"`);
|
|
409
|
-
}
|
|
410
|
-
if (args.approvalPolicy) {
|
|
411
|
-
commandArgs.push("--config", `approval_policy="${args.approvalPolicy}"`);
|
|
412
|
-
}
|
|
413
|
-
if (args.threadId) {
|
|
414
|
-
commandArgs.push("resume", args.threadId);
|
|
415
|
-
}
|
|
416
|
-
if (args.images?.length) {
|
|
417
|
-
for (const image of args.images) {
|
|
418
|
-
commandArgs.push("--image", image);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const env = {};
|
|
422
|
-
if (this.envOverride) {
|
|
423
|
-
Object.assign(env, this.envOverride);
|
|
424
|
-
} else {
|
|
425
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
426
|
-
if (value !== void 0) {
|
|
427
|
-
env[key] = value;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
if (!env[INTERNAL_ORIGINATOR_ENV]) {
|
|
432
|
-
env[INTERNAL_ORIGINATOR_ENV] = TYPESCRIPT_SDK_ORIGINATOR;
|
|
433
|
-
}
|
|
434
|
-
if (args.baseUrl) {
|
|
435
|
-
env.OPENAI_BASE_URL = args.baseUrl;
|
|
436
|
-
}
|
|
437
|
-
if (args.apiKey) {
|
|
438
|
-
env.CODEX_API_KEY = args.apiKey;
|
|
439
|
-
}
|
|
440
|
-
const child = spawn2(this.executablePath, commandArgs, {
|
|
441
|
-
env,
|
|
442
|
-
signal: args.signal
|
|
443
|
-
});
|
|
444
|
-
let spawnError = null;
|
|
445
|
-
child.once("error", (err) => spawnError = err);
|
|
446
|
-
if (!child.stdin) {
|
|
447
|
-
child.kill();
|
|
448
|
-
throw new Error("Child process has no stdin");
|
|
449
|
-
}
|
|
450
|
-
child.stdin.write(args.input);
|
|
451
|
-
child.stdin.end();
|
|
452
|
-
if (!child.stdout) {
|
|
453
|
-
child.kill();
|
|
454
|
-
throw new Error("Child process has no stdout");
|
|
455
|
-
}
|
|
456
|
-
const stderrChunks = [];
|
|
457
|
-
if (child.stderr) {
|
|
458
|
-
child.stderr.on("data", (data) => {
|
|
459
|
-
stderrChunks.push(data);
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
const exitPromise = new Promise(
|
|
463
|
-
(resolve) => {
|
|
464
|
-
child.once("exit", (code, signal) => {
|
|
465
|
-
resolve({ code, signal });
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
);
|
|
469
|
-
const rl = readline.createInterface({
|
|
470
|
-
input: child.stdout,
|
|
471
|
-
crlfDelay: Infinity
|
|
472
|
-
});
|
|
473
|
-
try {
|
|
474
|
-
for await (const line of rl) {
|
|
475
|
-
yield line;
|
|
476
|
-
}
|
|
477
|
-
if (spawnError) throw spawnError;
|
|
478
|
-
const { code, signal } = await exitPromise;
|
|
479
|
-
if (code !== 0 || signal) {
|
|
480
|
-
const stderrBuffer = Buffer.concat(stderrChunks);
|
|
481
|
-
const detail = signal ? `signal ${signal}` : `code ${code ?? 1}`;
|
|
482
|
-
throw new Error(`Codex Exec exited with ${detail}: ${stderrBuffer.toString("utf8")}`);
|
|
483
|
-
}
|
|
484
|
-
} finally {
|
|
485
|
-
rl.close();
|
|
486
|
-
child.removeAllListeners();
|
|
487
|
-
try {
|
|
488
|
-
if (!child.killed) child.kill();
|
|
489
|
-
} catch {
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
function serializeConfigOverrides(configOverrides) {
|
|
495
|
-
const overrides = [];
|
|
496
|
-
flattenConfigOverrides(configOverrides, "", overrides);
|
|
497
|
-
return overrides;
|
|
498
|
-
}
|
|
499
|
-
function flattenConfigOverrides(value, prefix, overrides) {
|
|
500
|
-
if (!isPlainObject(value)) {
|
|
501
|
-
if (prefix) {
|
|
502
|
-
overrides.push(`${prefix}=${toTomlValue(value, prefix)}`);
|
|
503
|
-
return;
|
|
504
|
-
} else {
|
|
505
|
-
throw new Error("Codex config overrides must be a plain object");
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
const entries = Object.entries(value);
|
|
509
|
-
if (!prefix && entries.length === 0) {
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
if (prefix && entries.length === 0) {
|
|
513
|
-
overrides.push(`${prefix}={}`);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
for (const [key, child] of entries) {
|
|
517
|
-
if (!key) {
|
|
518
|
-
throw new Error("Codex config override keys must be non-empty strings");
|
|
519
|
-
}
|
|
520
|
-
if (child === void 0) {
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
const path32 = prefix ? `${prefix}.${key}` : key;
|
|
524
|
-
if (isPlainObject(child)) {
|
|
525
|
-
flattenConfigOverrides(child, path32, overrides);
|
|
526
|
-
} else {
|
|
527
|
-
overrides.push(`${path32}=${toTomlValue(child, path32)}`);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
function toTomlValue(value, path32) {
|
|
532
|
-
if (typeof value === "string") {
|
|
533
|
-
return JSON.stringify(value);
|
|
534
|
-
} else if (typeof value === "number") {
|
|
535
|
-
if (!Number.isFinite(value)) {
|
|
536
|
-
throw new Error(`Codex config override at ${path32} must be a finite number`);
|
|
537
|
-
}
|
|
538
|
-
return `${value}`;
|
|
539
|
-
} else if (typeof value === "boolean") {
|
|
540
|
-
return value ? "true" : "false";
|
|
541
|
-
} else if (Array.isArray(value)) {
|
|
542
|
-
const rendered = value.map((item, index) => toTomlValue(item, `${path32}[${index}]`));
|
|
543
|
-
return `[${rendered.join(", ")}]`;
|
|
544
|
-
} else if (isPlainObject(value)) {
|
|
545
|
-
const parts = [];
|
|
546
|
-
for (const [key, child] of Object.entries(value)) {
|
|
547
|
-
if (!key) {
|
|
548
|
-
throw new Error("Codex config override keys must be non-empty strings");
|
|
549
|
-
}
|
|
550
|
-
if (child === void 0) {
|
|
551
|
-
continue;
|
|
552
|
-
}
|
|
553
|
-
parts.push(`${formatTomlKey(key)} = ${toTomlValue(child, `${path32}.${key}`)}`);
|
|
554
|
-
}
|
|
555
|
-
return `{${parts.join(", ")}}`;
|
|
556
|
-
} else if (value === null) {
|
|
557
|
-
throw new Error(`Codex config override at ${path32} cannot be null`);
|
|
558
|
-
} else {
|
|
559
|
-
const typeName = typeof value;
|
|
560
|
-
throw new Error(`Unsupported Codex config override value at ${path32}: ${typeName}`);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
var TOML_BARE_KEY = /^[A-Za-z0-9_-]+$/;
|
|
564
|
-
function formatTomlKey(key) {
|
|
565
|
-
return TOML_BARE_KEY.test(key) ? key : JSON.stringify(key);
|
|
566
|
-
}
|
|
567
|
-
function isPlainObject(value) {
|
|
568
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
569
|
-
}
|
|
570
|
-
var scriptFileName = fileURLToPath(import.meta.url);
|
|
571
|
-
var scriptDirName = path22.dirname(scriptFileName);
|
|
572
|
-
function findCodexPath() {
|
|
573
|
-
const { platform, arch } = process;
|
|
574
|
-
let targetTriple = null;
|
|
575
|
-
switch (platform) {
|
|
576
|
-
case "linux":
|
|
577
|
-
case "android":
|
|
578
|
-
switch (arch) {
|
|
579
|
-
case "x64":
|
|
580
|
-
targetTriple = "x86_64-unknown-linux-musl";
|
|
581
|
-
break;
|
|
582
|
-
case "arm64":
|
|
583
|
-
targetTriple = "aarch64-unknown-linux-musl";
|
|
584
|
-
break;
|
|
585
|
-
default:
|
|
586
|
-
break;
|
|
587
|
-
}
|
|
588
|
-
break;
|
|
589
|
-
case "darwin":
|
|
590
|
-
switch (arch) {
|
|
591
|
-
case "x64":
|
|
592
|
-
targetTriple = "x86_64-apple-darwin";
|
|
593
|
-
break;
|
|
594
|
-
case "arm64":
|
|
595
|
-
targetTriple = "aarch64-apple-darwin";
|
|
596
|
-
break;
|
|
597
|
-
default:
|
|
598
|
-
break;
|
|
599
|
-
}
|
|
600
|
-
break;
|
|
601
|
-
case "win32":
|
|
602
|
-
switch (arch) {
|
|
603
|
-
case "x64":
|
|
604
|
-
targetTriple = "x86_64-pc-windows-msvc";
|
|
605
|
-
break;
|
|
606
|
-
case "arm64":
|
|
607
|
-
targetTriple = "aarch64-pc-windows-msvc";
|
|
608
|
-
break;
|
|
609
|
-
default:
|
|
610
|
-
break;
|
|
611
|
-
}
|
|
612
|
-
break;
|
|
613
|
-
default:
|
|
614
|
-
break;
|
|
615
|
-
}
|
|
616
|
-
if (!targetTriple) {
|
|
617
|
-
throw new Error(`Unsupported platform: ${platform} (${arch})`);
|
|
618
|
-
}
|
|
619
|
-
const vendorRoot = path22.join(scriptDirName, "..", "vendor");
|
|
620
|
-
const archRoot = path22.join(vendorRoot, targetTriple);
|
|
621
|
-
const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
|
|
622
|
-
const binaryPath = path22.join(archRoot, "codex", codexBinaryName);
|
|
623
|
-
return binaryPath;
|
|
624
|
-
}
|
|
625
|
-
var Codex = class {
|
|
626
|
-
exec;
|
|
627
|
-
options;
|
|
628
|
-
constructor(options = {}) {
|
|
629
|
-
const { codexPathOverride, env, config } = options;
|
|
630
|
-
this.exec = new CodexExec(codexPathOverride, env, config);
|
|
631
|
-
this.options = options;
|
|
632
|
-
}
|
|
633
|
-
/**
|
|
634
|
-
* Starts a new conversation with an agent.
|
|
635
|
-
* @returns A new thread instance.
|
|
636
|
-
*/
|
|
637
|
-
startThread(options = {}) {
|
|
638
|
-
return new Thread(this.exec, this.options, options);
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Resumes a conversation with an agent based on the thread id.
|
|
642
|
-
* Threads are persisted in ~/.codex/sessions.
|
|
643
|
-
*
|
|
644
|
-
* @param id The id of the thread to resume.
|
|
645
|
-
* @returns A new thread instance.
|
|
646
|
-
*/
|
|
647
|
-
resumeThread(id, options = {}) {
|
|
648
|
-
return new Thread(this.exec, this.options, options, id);
|
|
649
|
-
}
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
// ../common/src/sessions.ts
|
|
653
|
-
import { randomUUID } from "crypto";
|
|
654
|
-
import fs3 from "fs";
|
|
655
|
-
import path3 from "path";
|
|
656
|
-
var SessionManager = class {
|
|
657
|
-
sessionsDir;
|
|
658
|
-
constructor(options = {}) {
|
|
659
|
-
if (options.debugDir) {
|
|
660
|
-
this.sessionsDir = path3.join(options.debugDir, "sessions");
|
|
661
|
-
} else {
|
|
662
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
663
|
-
if (!home) {
|
|
664
|
-
throw new Error("Cannot determine home directory for session storage");
|
|
665
|
-
}
|
|
666
|
-
this.sessionsDir = path3.join(home, ".shim", "sessions");
|
|
667
|
-
}
|
|
668
|
-
fs3.mkdirSync(this.sessionsDir, { recursive: true });
|
|
669
|
-
}
|
|
670
|
-
/**
|
|
671
|
-
* Generate a new UUID v4 session ID
|
|
672
|
-
*/
|
|
673
|
-
generateSessionId() {
|
|
674
|
-
return randomUUID();
|
|
675
|
-
}
|
|
676
|
-
/**
|
|
677
|
-
* Save session data
|
|
678
|
-
*/
|
|
679
|
-
saveSession(data) {
|
|
680
|
-
const sessionPath = path3.join(this.sessionsDir, `${data.sessionId}.json`);
|
|
681
|
-
fs3.writeFileSync(sessionPath, JSON.stringify(data, null, 2), "utf8");
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* Load session data by session ID
|
|
685
|
-
* @throws Error if session not found
|
|
686
|
-
*/
|
|
687
|
-
loadSession(sessionId) {
|
|
688
|
-
const sessionPath = path3.join(this.sessionsDir, `${sessionId}.json`);
|
|
689
|
-
try {
|
|
690
|
-
const content = fs3.readFileSync(sessionPath, "utf8");
|
|
691
|
-
return JSON.parse(content);
|
|
692
|
-
} catch (error) {
|
|
693
|
-
if (error.code === "ENOENT") {
|
|
694
|
-
throw new Error(`Session not found: ${sessionId}`);
|
|
695
|
-
}
|
|
696
|
-
throw error;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Check if session exists
|
|
701
|
-
*/
|
|
702
|
-
sessionExists(sessionId) {
|
|
703
|
-
const sessionPath = path3.join(this.sessionsDir, `${sessionId}.json`);
|
|
704
|
-
return fs3.existsSync(sessionPath);
|
|
705
|
-
}
|
|
706
|
-
/**
|
|
707
|
-
* Delete session data
|
|
708
|
-
*/
|
|
709
|
-
deleteSession(sessionId) {
|
|
710
|
-
const sessionPath = path3.join(this.sessionsDir, `${sessionId}.json`);
|
|
711
|
-
try {
|
|
712
|
-
fs3.unlinkSync(sessionPath);
|
|
713
|
-
} catch (error) {
|
|
714
|
-
if (error.code !== "ENOENT") {
|
|
715
|
-
throw error;
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* List all session IDs
|
|
721
|
-
*/
|
|
722
|
-
listSessions() {
|
|
723
|
-
try {
|
|
724
|
-
const files = fs3.readdirSync(this.sessionsDir);
|
|
725
|
-
return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
|
|
726
|
-
} catch (error) {
|
|
727
|
-
if (error.code === "ENOENT") {
|
|
728
|
-
return [];
|
|
729
|
-
}
|
|
730
|
-
throw error;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Get the sessions directory path
|
|
735
|
-
*/
|
|
736
|
-
getSessionsDir() {
|
|
737
|
-
return this.sessionsDir;
|
|
738
|
-
}
|
|
739
|
-
};
|
|
740
|
-
|
|
741
|
-
// ../common/src/timeout.ts
|
|
742
|
-
var IdleTimeoutError = class extends Error {
|
|
743
|
-
timeoutMs;
|
|
744
|
-
constructor(timeoutMs) {
|
|
745
|
-
super(`Idle timeout: no events received for ${timeoutMs}ms`);
|
|
746
|
-
this.name = "IdleTimeoutError";
|
|
747
|
-
this.timeoutMs = timeoutMs;
|
|
748
|
-
}
|
|
749
|
-
};
|
|
750
|
-
async function* withIdleTimeout(events, timeoutMs) {
|
|
751
|
-
const iterator = events[Symbol.asyncIterator]();
|
|
752
|
-
try {
|
|
753
|
-
while (true) {
|
|
754
|
-
let timeoutId;
|
|
755
|
-
try {
|
|
756
|
-
const result = await Promise.race([
|
|
757
|
-
iterator.next(),
|
|
758
|
-
new Promise((_, reject) => {
|
|
759
|
-
timeoutId = setTimeout(() => {
|
|
760
|
-
reject(new IdleTimeoutError(timeoutMs));
|
|
761
|
-
}, timeoutMs);
|
|
762
|
-
})
|
|
763
|
-
]);
|
|
764
|
-
if (result.done) break;
|
|
765
|
-
yield result.value;
|
|
766
|
-
} finally {
|
|
767
|
-
clearTimeout(timeoutId);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
} finally {
|
|
771
|
-
void iterator.return?.();
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// src/utils/ids.ts
|
|
776
|
-
function generateMessageId() {
|
|
777
|
-
return `msg_${Date.now().toString(36)}${Math.random().toString(36).substring(2, 10)}`;
|
|
778
|
-
}
|
|
779
|
-
function generateToolUseId() {
|
|
780
|
-
return `toolu_${Date.now().toString(36)}${Math.random().toString(36).substring(2, 12)}`;
|
|
781
|
-
}
|
|
782
|
-
var NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
783
|
-
|
|
784
|
-
// src/utils/models.ts
|
|
785
|
-
var MODEL_SHORTNAMES = {
|
|
786
|
-
codex: "openai/gpt-5.1-codex-max",
|
|
787
|
-
"codex-max": "openai/gpt-5.1-codex-max",
|
|
788
|
-
"o4-mini": "openai/o4-mini"
|
|
789
|
-
};
|
|
790
|
-
var VALID_REASONING_EFFORTS = [
|
|
791
|
-
"minimal",
|
|
792
|
-
"low",
|
|
793
|
-
"medium",
|
|
794
|
-
"high",
|
|
795
|
-
"xhigh"
|
|
796
|
-
];
|
|
797
|
-
function isValidReasoningEffort(value) {
|
|
798
|
-
return VALID_REASONING_EFFORTS.includes(value);
|
|
799
|
-
}
|
|
800
|
-
function resolveModel(input) {
|
|
801
|
-
let modelInput = input;
|
|
802
|
-
let reasoningEffort;
|
|
803
|
-
const lastHyphenIndex = input.lastIndexOf("-");
|
|
804
|
-
if (lastHyphenIndex !== -1) {
|
|
805
|
-
const potentialEffort = input.substring(lastHyphenIndex + 1);
|
|
806
|
-
const baseModel = input.substring(0, lastHyphenIndex);
|
|
807
|
-
if (isValidReasoningEffort(potentialEffort)) {
|
|
808
|
-
modelInput = baseModel;
|
|
809
|
-
reasoningEffort = potentialEffort;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
const fullModel = MODEL_SHORTNAMES[modelInput.toLowerCase()] || modelInput;
|
|
813
|
-
if (fullModel.includes("/")) {
|
|
814
|
-
const [providerID, modelID] = fullModel.split("/", 2);
|
|
815
|
-
return { providerID, modelID, reasoningEffort };
|
|
816
|
-
}
|
|
817
|
-
return {
|
|
818
|
-
providerID: "openai",
|
|
819
|
-
modelID: fullModel,
|
|
820
|
-
reasoningEffort
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
function formatModelOutput(spec) {
|
|
824
|
-
return `${spec.providerID}/${spec.modelID}`;
|
|
825
|
-
}
|
|
826
|
-
function getCodexModelId(spec) {
|
|
827
|
-
return spec.modelID;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
// src/utils/output.ts
|
|
831
|
-
function emit(message) {
|
|
832
|
-
console.log(JSON.stringify(message));
|
|
833
|
-
}
|
|
834
|
-
function verboseLog(verbose, ...args) {
|
|
835
|
-
if (verbose) {
|
|
836
|
-
console.error("[codex-shim]", ...args);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
async function flushStdout() {
|
|
840
|
-
return new Promise((resolve) => {
|
|
841
|
-
const written = process.stdout.write("");
|
|
842
|
-
if (written) {
|
|
843
|
-
resolve();
|
|
844
|
-
} else {
|
|
845
|
-
process.stdout.once("drain", resolve);
|
|
846
|
-
}
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// src/utils/tools.ts
|
|
851
|
-
var STANDARD_TOOLS = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "LS"];
|
|
852
|
-
function normalizeToolName(name) {
|
|
853
|
-
const lowerName = name.toLowerCase();
|
|
854
|
-
const toolMap = {
|
|
855
|
-
read: "Read",
|
|
856
|
-
file_read: "Read",
|
|
857
|
-
readfile: "Read",
|
|
858
|
-
read_text_file: "Read",
|
|
859
|
-
write: "Write",
|
|
860
|
-
file_write: "Write",
|
|
861
|
-
writefile: "Write",
|
|
862
|
-
write_text_file: "Write",
|
|
863
|
-
edit: "Edit",
|
|
864
|
-
str_replace_editor: "Edit",
|
|
865
|
-
edit_text_file: "Edit",
|
|
866
|
-
bash: "Bash",
|
|
867
|
-
shell: "Bash",
|
|
868
|
-
execute_bash: "Bash",
|
|
869
|
-
exec: "Bash",
|
|
870
|
-
glob: "Glob",
|
|
871
|
-
find_files: "Glob",
|
|
872
|
-
grep: "Grep",
|
|
873
|
-
search_files: "Grep",
|
|
874
|
-
ls: "LS",
|
|
875
|
-
list: "LS",
|
|
876
|
-
list_directory: "LS"
|
|
877
|
-
};
|
|
878
|
-
return toolMap[lowerName] || name;
|
|
879
|
-
}
|
|
880
|
-
function camelToSnake(str) {
|
|
881
|
-
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
882
|
-
}
|
|
883
|
-
function normalizeToolInput(input) {
|
|
884
|
-
const normalized = {};
|
|
885
|
-
for (const [key, value] of Object.entries(input)) {
|
|
886
|
-
normalized[camelToSnake(key)] = value;
|
|
887
|
-
}
|
|
888
|
-
return normalized;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
// src/shim.ts
|
|
892
|
-
var CodexShim = class {
|
|
893
|
-
args;
|
|
894
|
-
codex;
|
|
895
|
-
cwd;
|
|
896
|
-
startTime;
|
|
897
|
-
apiStartTime;
|
|
898
|
-
interrupted = false;
|
|
899
|
-
sessionManager;
|
|
900
|
-
authConfig;
|
|
901
|
-
constructor(args) {
|
|
902
|
-
this.args = args;
|
|
903
|
-
this.cwd = process.cwd();
|
|
904
|
-
this.startTime = Date.now();
|
|
905
|
-
this.apiStartTime = 0;
|
|
906
|
-
this.sessionManager = new SessionManager({ debugDir: args.debugDir });
|
|
907
|
-
this.authConfig = getApiKey();
|
|
908
|
-
this.codex = new Codex({
|
|
909
|
-
apiKey: this.authConfig.source !== "env" && this.authConfig.apiKey ? this.authConfig.apiKey : void 0,
|
|
910
|
-
env: process.env,
|
|
911
|
-
// Use system-installed codex instead of vendored binary
|
|
912
|
-
codexPathOverride: process.env.CODEX_PATH_OVERRIDE || "codex"
|
|
913
|
-
});
|
|
914
|
-
this.setupSignalHandlers();
|
|
915
|
-
}
|
|
916
|
-
setupSignalHandlers() {
|
|
917
|
-
const handleSignal = () => {
|
|
918
|
-
if (!this.interrupted) {
|
|
919
|
-
this.interrupted = true;
|
|
920
|
-
verboseLog(this.args.verbose, "Received interrupt signal, shutting down gracefully");
|
|
921
|
-
process.exit(0);
|
|
922
|
-
}
|
|
923
|
-
};
|
|
924
|
-
process.on("SIGINT", handleSignal);
|
|
925
|
-
process.on("SIGTERM", handleSignal);
|
|
926
|
-
}
|
|
927
|
-
async run(prompt) {
|
|
928
|
-
const modelSpec = resolveModel(this.args.model);
|
|
929
|
-
const modelOutput = formatModelOutput(modelSpec);
|
|
930
|
-
const codexModelId = getCodexModelId(modelSpec);
|
|
931
|
-
verboseLog(this.args.verbose, `Resolved model: ${modelOutput}`);
|
|
932
|
-
verboseLog(this.args.verbose, `Codex model ID: ${codexModelId}`);
|
|
933
|
-
let thread;
|
|
934
|
-
let sessionId = null;
|
|
935
|
-
try {
|
|
936
|
-
if (this.args.resume) {
|
|
937
|
-
const resumeSessionId = this.args.resume;
|
|
938
|
-
verboseLog(this.args.verbose, `Resuming session: ${resumeSessionId}`);
|
|
939
|
-
const sessionData = this.sessionManager.loadSession(resumeSessionId);
|
|
940
|
-
const threadId = sessionData.agentSessionId;
|
|
941
|
-
verboseLog(
|
|
942
|
-
this.args.verbose,
|
|
943
|
-
`Found thread ID: ${threadId} for session: ${resumeSessionId}`
|
|
944
|
-
);
|
|
945
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
946
|
-
const codexSessionDir = path4.join(home, ".codex", "sessions");
|
|
947
|
-
let threadExists = false;
|
|
948
|
-
try {
|
|
949
|
-
const { spawnSync } = await import("child_process");
|
|
950
|
-
const result = spawnSync("find", [codexSessionDir, "-name", `*${threadId}*`], {
|
|
951
|
-
encoding: "utf8"
|
|
952
|
-
});
|
|
953
|
-
threadExists = result.stdout.trim().length > 0 && result.status === 0;
|
|
954
|
-
} catch (error) {
|
|
955
|
-
verboseLog(this.args.verbose, `Thread validation error: ${error}`);
|
|
956
|
-
}
|
|
957
|
-
if (!threadExists) {
|
|
958
|
-
throw new Error(
|
|
959
|
-
`Thread not found for session ${resumeSessionId}. Cannot resume non-existent conversation.`
|
|
960
|
-
);
|
|
961
|
-
}
|
|
962
|
-
thread = this.codex.resumeThread(threadId, this.getThreadOptions());
|
|
963
|
-
sessionId = resumeSessionId;
|
|
964
|
-
} else {
|
|
965
|
-
verboseLog(this.args.verbose, `Starting new session`);
|
|
966
|
-
sessionId = this.sessionManager.generateSessionId();
|
|
967
|
-
thread = this.codex.startThread(this.getThreadOptions());
|
|
968
|
-
}
|
|
969
|
-
} catch (error) {
|
|
970
|
-
verboseLog(this.args.verbose, "Pre-init error:", error);
|
|
971
|
-
if (this.args.debugDir) {
|
|
972
|
-
this.savePreInitError(error);
|
|
973
|
-
}
|
|
974
|
-
throw error;
|
|
975
|
-
}
|
|
976
|
-
if (!sessionId) {
|
|
977
|
-
throw new Error("Failed to determine session ID");
|
|
978
|
-
}
|
|
979
|
-
const state = {
|
|
980
|
-
sessionId,
|
|
981
|
-
thread,
|
|
982
|
-
startTime: this.startTime,
|
|
983
|
-
apiStartTime: 0,
|
|
984
|
-
numTurns: 0,
|
|
985
|
-
totalUsage: {
|
|
986
|
-
input_tokens: 0,
|
|
987
|
-
output_tokens: 0
|
|
988
|
-
},
|
|
989
|
-
workStarted: false,
|
|
990
|
-
hasReceivedDeltas: false,
|
|
991
|
-
lastEmittedAssistantText: "",
|
|
992
|
-
lastEmittedReasoningByItemId: /* @__PURE__ */ new Map(),
|
|
993
|
-
emittedToolIds: /* @__PURE__ */ new Set(),
|
|
994
|
-
toolIdMapping: /* @__PURE__ */ new Map()
|
|
995
|
-
};
|
|
996
|
-
try {
|
|
997
|
-
await this.processPrompt(state, prompt, modelOutput);
|
|
998
|
-
this.emitResult(state, false, "Completed successfully");
|
|
999
|
-
} catch (error) {
|
|
1000
|
-
verboseLog(this.args.verbose, "Error during execution:", error);
|
|
1001
|
-
if (this.args.debugDir) {
|
|
1002
|
-
this.saveRuntimeError(state.sessionId, error);
|
|
1003
|
-
}
|
|
1004
|
-
this.emitSyntheticError(error, modelOutput);
|
|
1005
|
-
this.emitResult(state, true, error instanceof Error ? error.message : String(error));
|
|
1006
|
-
await flushStdout();
|
|
1007
|
-
process.exit(1);
|
|
1008
|
-
}
|
|
1009
|
-
await flushStdout();
|
|
1010
|
-
process.exit(0);
|
|
1011
|
-
}
|
|
1012
|
-
getThreadOptions() {
|
|
1013
|
-
const modelSpec = resolveModel(this.args.model);
|
|
1014
|
-
const sandboxModeMap = {
|
|
1015
|
-
none: "danger-full-access",
|
|
1016
|
-
standard: "workspace-write",
|
|
1017
|
-
strict: "read-only"
|
|
1018
|
-
};
|
|
1019
|
-
const options = {
|
|
1020
|
-
workingDirectory: this.cwd,
|
|
1021
|
-
skipGitRepoCheck: true,
|
|
1022
|
-
model: getCodexModelId(modelSpec),
|
|
1023
|
-
// Auto-approve all operations (no user present)
|
|
1024
|
-
approvalPolicy: "never",
|
|
1025
|
-
sandboxMode: sandboxModeMap[this.args.sandbox],
|
|
1026
|
-
networkAccessEnabled: true,
|
|
1027
|
-
webSearchEnabled: true,
|
|
1028
|
-
modelReasoningEffort: modelSpec.reasoningEffort || "high"
|
|
1029
|
-
};
|
|
1030
|
-
return options;
|
|
1031
|
-
}
|
|
1032
|
-
emitSystemInit(sessionId, model) {
|
|
1033
|
-
let apiKeySource;
|
|
1034
|
-
switch (this.authConfig.source) {
|
|
1035
|
-
case "env":
|
|
1036
|
-
case "CODEX_API_KEY":
|
|
1037
|
-
case "OPENAI_API_KEY":
|
|
1038
|
-
apiKeySource = "env";
|
|
1039
|
-
break;
|
|
1040
|
-
default:
|
|
1041
|
-
apiKeySource = "none";
|
|
1042
|
-
}
|
|
1043
|
-
const message = {
|
|
1044
|
-
type: "system",
|
|
1045
|
-
subtype: "init",
|
|
1046
|
-
cwd: this.cwd,
|
|
1047
|
-
session_id: sessionId,
|
|
1048
|
-
tools: [...STANDARD_TOOLS],
|
|
1049
|
-
model,
|
|
1050
|
-
permissionMode: "bypassPermissions",
|
|
1051
|
-
apiKeySource,
|
|
1052
|
-
mcp_servers: []
|
|
1053
|
-
};
|
|
1054
|
-
emit(message);
|
|
1055
|
-
verboseLog(this.args.verbose, "Emitted system init");
|
|
1056
|
-
}
|
|
1057
|
-
async processPrompt(state, prompt, model) {
|
|
1058
|
-
let finalPrompt = prompt;
|
|
1059
|
-
if (this.args.appendSystemPrompt) {
|
|
1060
|
-
finalPrompt = `${prompt}
|
|
1061
|
-
|
|
1062
|
-
${this.args.appendSystemPrompt}`;
|
|
1063
|
-
}
|
|
1064
|
-
verboseLog(this.args.verbose, `Sending prompt: ${finalPrompt.substring(0, 100)}...`);
|
|
1065
|
-
state.apiStartTime = Date.now();
|
|
1066
|
-
this.apiStartTime = state.apiStartTime;
|
|
1067
|
-
const { events } = await state.thread.runStreamed(finalPrompt);
|
|
1068
|
-
const timedEvents = withIdleTimeout(events, this.args.idleTimeout * 1e3);
|
|
1069
|
-
const currentMessageContent = [];
|
|
1070
|
-
let currentMessageId = generateMessageId();
|
|
1071
|
-
const pendingToolResults = /* @__PURE__ */ new Map();
|
|
1072
|
-
let systemInitEmitted = false;
|
|
1073
|
-
for await (const event of timedEvents) {
|
|
1074
|
-
if (this.interrupted) {
|
|
1075
|
-
verboseLog(this.args.verbose, "Interrupted, stopping event processing");
|
|
1076
|
-
break;
|
|
1077
|
-
}
|
|
1078
|
-
verboseLog(this.args.verbose, `Event: ${event.type}`);
|
|
1079
|
-
if (this.args.debugDir && state.sessionId) {
|
|
1080
|
-
this.saveRawEvent(state.sessionId, event);
|
|
1081
|
-
if (event.type === "thread.started") {
|
|
1082
|
-
this.createRawLogFile(state.sessionId);
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
if (!state.workStarted) {
|
|
1086
|
-
if (event.type === "item.started" || event.type === "item.updated" || event.type === "turn.started") {
|
|
1087
|
-
state.workStarted = true;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
switch (event.type) {
|
|
1091
|
-
case "thread.started":
|
|
1092
|
-
if (event.thread_id) {
|
|
1093
|
-
verboseLog(this.args.verbose, `Thread started with Codex ID: ${event.thread_id}`);
|
|
1094
|
-
verboseLog(this.args.verbose, `Using session ID: ${state.sessionId}`);
|
|
1095
|
-
if (!systemInitEmitted) {
|
|
1096
|
-
this.emitSystemInit(state.sessionId, model);
|
|
1097
|
-
systemInitEmitted = true;
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
break;
|
|
1101
|
-
case "turn.started":
|
|
1102
|
-
state.numTurns++;
|
|
1103
|
-
verboseLog(this.args.verbose, `Turn ${state.numTurns} started`);
|
|
1104
|
-
break;
|
|
1105
|
-
case "item.started":
|
|
1106
|
-
case "item.updated":
|
|
1107
|
-
case "item.completed":
|
|
1108
|
-
this.handleItemEvent(
|
|
1109
|
-
event.item,
|
|
1110
|
-
state,
|
|
1111
|
-
currentMessageContent,
|
|
1112
|
-
pendingToolResults,
|
|
1113
|
-
model,
|
|
1114
|
-
currentMessageId,
|
|
1115
|
-
event.type === "item.completed"
|
|
1116
|
-
);
|
|
1117
|
-
break;
|
|
1118
|
-
case "turn.completed": {
|
|
1119
|
-
if (event.usage) {
|
|
1120
|
-
state.totalUsage.input_tokens += event.usage.input_tokens;
|
|
1121
|
-
state.totalUsage.output_tokens += event.usage.output_tokens;
|
|
1122
|
-
if (event.usage.cached_input_tokens) {
|
|
1123
|
-
state.totalUsage.cache_read_input_tokens = (state.totalUsage.cache_read_input_tokens || 0) + event.usage.cached_input_tokens;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
verboseLog(this.args.verbose, "Turn completed", event.usage);
|
|
1127
|
-
const completionContent = state.hasReceivedDeltas ? [] : currentMessageContent;
|
|
1128
|
-
if (completionContent.length > 0 || event.usage) {
|
|
1129
|
-
this.emitAssistantMessage(
|
|
1130
|
-
currentMessageId,
|
|
1131
|
-
model,
|
|
1132
|
-
completionContent,
|
|
1133
|
-
event.usage,
|
|
1134
|
-
"end_turn"
|
|
1135
|
-
);
|
|
1136
|
-
}
|
|
1137
|
-
for (const result of pendingToolResults.values()) {
|
|
1138
|
-
this.emitToolResult(result);
|
|
1139
|
-
}
|
|
1140
|
-
pendingToolResults.clear();
|
|
1141
|
-
currentMessageContent.length = 0;
|
|
1142
|
-
state.hasReceivedDeltas = false;
|
|
1143
|
-
state.lastEmittedAssistantText = "";
|
|
1144
|
-
state.lastEmittedReasoningByItemId.clear();
|
|
1145
|
-
currentMessageId = generateMessageId();
|
|
1146
|
-
break;
|
|
1147
|
-
}
|
|
1148
|
-
case "turn.failed":
|
|
1149
|
-
verboseLog(this.args.verbose, "Turn failed:", event.error);
|
|
1150
|
-
if (this.args.debugDir) {
|
|
1151
|
-
this.saveRuntimeError(
|
|
1152
|
-
state.sessionId,
|
|
1153
|
-
new Error(`Turn failed: ${event.error.message}`)
|
|
1154
|
-
);
|
|
1155
|
-
}
|
|
1156
|
-
throw new Error(`Turn failed: ${event.error.message}`);
|
|
1157
|
-
case "error":
|
|
1158
|
-
verboseLog(this.args.verbose, `Thread error: ${event.message}`);
|
|
1159
|
-
if (this.args.debugDir) {
|
|
1160
|
-
this.saveRuntimeError(state.sessionId, new Error(`Thread error: ${event.message}`));
|
|
1161
|
-
}
|
|
1162
|
-
break;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
if (state.thread.id) {
|
|
1166
|
-
this.saveSession(state.thread.id, state);
|
|
1167
|
-
}
|
|
1168
|
-
if (this.args.debugDir) {
|
|
1169
|
-
this.createRawLogFile(state.sessionId);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
handleItemEvent(item, state, currentMessageContent, pendingToolResults, model, currentMessageId, _isCompleted = false) {
|
|
1173
|
-
switch (item.type) {
|
|
1174
|
-
case "agent_message":
|
|
1175
|
-
if (item.text) {
|
|
1176
|
-
const previousText = state.lastEmittedAssistantText || "";
|
|
1177
|
-
const deltaText = item.text.startsWith(previousText) ? item.text.slice(previousText.length) : item.text;
|
|
1178
|
-
if (deltaText) {
|
|
1179
|
-
this.emitAssistantMessage(
|
|
1180
|
-
currentMessageId,
|
|
1181
|
-
model,
|
|
1182
|
-
[{ type: "text", text: deltaText }],
|
|
1183
|
-
void 0,
|
|
1184
|
-
null
|
|
1185
|
-
);
|
|
1186
|
-
state.hasReceivedDeltas = true;
|
|
1187
|
-
}
|
|
1188
|
-
state.lastEmittedAssistantText = item.text;
|
|
1189
|
-
const existing = currentMessageContent.find((c) => c.type === "text");
|
|
1190
|
-
if (existing && existing.type === "text") {
|
|
1191
|
-
existing.text = item.text;
|
|
1192
|
-
} else {
|
|
1193
|
-
currentMessageContent.push({ type: "text", text: item.text });
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
break;
|
|
1197
|
-
case "reasoning":
|
|
1198
|
-
if (item.text) {
|
|
1199
|
-
const reasoningItemId = item.id || "reasoning";
|
|
1200
|
-
const previousReasoning = state.lastEmittedReasoningByItemId.get(reasoningItemId) || "";
|
|
1201
|
-
const deltaThinking = item.text.startsWith(previousReasoning) ? item.text.slice(previousReasoning.length) : item.text;
|
|
1202
|
-
if (deltaThinking) {
|
|
1203
|
-
this.emitAssistantMessage(
|
|
1204
|
-
currentMessageId,
|
|
1205
|
-
model,
|
|
1206
|
-
[{ type: "thinking", thinking: deltaThinking }],
|
|
1207
|
-
void 0,
|
|
1208
|
-
null
|
|
1209
|
-
);
|
|
1210
|
-
state.hasReceivedDeltas = true;
|
|
1211
|
-
}
|
|
1212
|
-
state.lastEmittedReasoningByItemId.set(reasoningItemId, item.text);
|
|
1213
|
-
currentMessageContent.push({ type: "thinking", thinking: item.text });
|
|
1214
|
-
}
|
|
1215
|
-
break;
|
|
1216
|
-
case "command_execution":
|
|
1217
|
-
this.handleCommandExecution(
|
|
1218
|
-
item,
|
|
1219
|
-
state,
|
|
1220
|
-
currentMessageContent,
|
|
1221
|
-
pendingToolResults,
|
|
1222
|
-
model,
|
|
1223
|
-
currentMessageId
|
|
1224
|
-
);
|
|
1225
|
-
break;
|
|
1226
|
-
case "mcp_tool_call":
|
|
1227
|
-
this.handleMcpToolCall(
|
|
1228
|
-
item,
|
|
1229
|
-
state,
|
|
1230
|
-
currentMessageContent,
|
|
1231
|
-
pendingToolResults,
|
|
1232
|
-
model,
|
|
1233
|
-
currentMessageId
|
|
1234
|
-
);
|
|
1235
|
-
break;
|
|
1236
|
-
case "file_change":
|
|
1237
|
-
this.handleFileChange(
|
|
1238
|
-
item,
|
|
1239
|
-
state,
|
|
1240
|
-
currentMessageContent,
|
|
1241
|
-
pendingToolResults,
|
|
1242
|
-
model,
|
|
1243
|
-
currentMessageId
|
|
1244
|
-
);
|
|
1245
|
-
break;
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
handleCommandExecution(item, state, currentMessageContent, pendingToolResults, model, currentMessageId) {
|
|
1249
|
-
if (!state.emittedToolIds.has(item.id)) {
|
|
1250
|
-
const toolUseId2 = generateToolUseId();
|
|
1251
|
-
state.emittedToolIds.add(item.id);
|
|
1252
|
-
state.toolIdMapping.set(item.id, toolUseId2);
|
|
1253
|
-
const toolUse = {
|
|
1254
|
-
type: "tool_use",
|
|
1255
|
-
id: toolUseId2,
|
|
1256
|
-
name: "Bash",
|
|
1257
|
-
input: { command: item.command }
|
|
1258
|
-
};
|
|
1259
|
-
currentMessageContent.push(toolUse);
|
|
1260
|
-
this.emitAssistantMessage(currentMessageId, model, [toolUse], void 0, null);
|
|
1261
|
-
state.hasReceivedDeltas = true;
|
|
1262
|
-
}
|
|
1263
|
-
const toolUseId = state.toolIdMapping.get(item.id);
|
|
1264
|
-
if (!toolUseId) return;
|
|
1265
|
-
let content = item.aggregated_output || "";
|
|
1266
|
-
if (!content && item.status === "completed" && item.exit_code === 0) {
|
|
1267
|
-
content = `Command executed successfully (exit code: 0)`;
|
|
1268
|
-
} else if (!content && item.status === "failed") {
|
|
1269
|
-
content = `Command failed (exit code: ${item.exit_code || "unknown"})`;
|
|
1270
|
-
}
|
|
1271
|
-
pendingToolResults.set(item.id, {
|
|
1272
|
-
type: "tool_result",
|
|
1273
|
-
tool_use_id: toolUseId,
|
|
1274
|
-
content: item.status === "failed" ? { is_error: true, error: content } : content
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
handleMcpToolCall(item, state, currentMessageContent, pendingToolResults, model, currentMessageId) {
|
|
1278
|
-
if (!state.emittedToolIds.has(item.id)) {
|
|
1279
|
-
const toolUseId2 = generateToolUseId();
|
|
1280
|
-
state.emittedToolIds.add(item.id);
|
|
1281
|
-
state.toolIdMapping.set(item.id, toolUseId2);
|
|
1282
|
-
const toolName = normalizeToolName(item.tool);
|
|
1283
|
-
const toolUse = {
|
|
1284
|
-
type: "tool_use",
|
|
1285
|
-
id: toolUseId2,
|
|
1286
|
-
name: toolName,
|
|
1287
|
-
input: normalizeToolInput(item.arguments || {})
|
|
1288
|
-
};
|
|
1289
|
-
currentMessageContent.push(toolUse);
|
|
1290
|
-
this.emitAssistantMessage(currentMessageId, model, [toolUse], void 0, null);
|
|
1291
|
-
state.hasReceivedDeltas = true;
|
|
1292
|
-
}
|
|
1293
|
-
const toolUseId = state.toolIdMapping.get(item.id);
|
|
1294
|
-
if (!toolUseId) return;
|
|
1295
|
-
let content = "";
|
|
1296
|
-
if (item.result?.content) {
|
|
1297
|
-
content = item.result.content.map((block) => {
|
|
1298
|
-
if (block.type === "text") return block.text;
|
|
1299
|
-
return JSON.stringify(block);
|
|
1300
|
-
}).join("\n");
|
|
1301
|
-
}
|
|
1302
|
-
pendingToolResults.set(item.id, {
|
|
1303
|
-
type: "tool_result",
|
|
1304
|
-
tool_use_id: toolUseId,
|
|
1305
|
-
content: item.status === "failed" ? { is_error: true, error: item.error?.message || "Failed" } : content
|
|
1306
|
-
});
|
|
1307
|
-
}
|
|
1308
|
-
handleFileChange(item, state, currentMessageContent, pendingToolResults, model, currentMessageId) {
|
|
1309
|
-
if (!state.emittedToolIds.has(item.id)) {
|
|
1310
|
-
const toolUseId2 = generateToolUseId();
|
|
1311
|
-
state.emittedToolIds.add(item.id);
|
|
1312
|
-
state.toolIdMapping.set(item.id, toolUseId2);
|
|
1313
|
-
const change2 = item.changes[0];
|
|
1314
|
-
const toolName = change2.kind === "add" ? "Write" : "Edit";
|
|
1315
|
-
const toolUse = {
|
|
1316
|
-
type: "tool_use",
|
|
1317
|
-
id: toolUseId2,
|
|
1318
|
-
name: toolName,
|
|
1319
|
-
input: { file_path: change2.path }
|
|
1320
|
-
};
|
|
1321
|
-
currentMessageContent.push(toolUse);
|
|
1322
|
-
this.emitAssistantMessage(currentMessageId, model, [toolUse], void 0, null);
|
|
1323
|
-
state.hasReceivedDeltas = true;
|
|
1324
|
-
}
|
|
1325
|
-
const toolUseId = state.toolIdMapping.get(item.id);
|
|
1326
|
-
if (!toolUseId) return;
|
|
1327
|
-
const change = item.changes[0];
|
|
1328
|
-
const content = `${change.kind}: ${change.path}`;
|
|
1329
|
-
pendingToolResults.set(item.id, {
|
|
1330
|
-
type: "tool_result",
|
|
1331
|
-
tool_use_id: toolUseId,
|
|
1332
|
-
content: item.status === "failed" ? { is_error: true, error: "File change failed" } : content
|
|
1333
|
-
});
|
|
1334
|
-
}
|
|
1335
|
-
emitAssistantMessage(messageId, model, content, usage, stopReason) {
|
|
1336
|
-
const message = {
|
|
1337
|
-
type: "assistant",
|
|
1338
|
-
message: {
|
|
1339
|
-
id: messageId,
|
|
1340
|
-
type: "message",
|
|
1341
|
-
role: "assistant",
|
|
1342
|
-
model,
|
|
1343
|
-
content,
|
|
1344
|
-
usage: usage ? {
|
|
1345
|
-
input_tokens: usage.input_tokens,
|
|
1346
|
-
output_tokens: usage.output_tokens,
|
|
1347
|
-
cache_read_input_tokens: usage.cached_input_tokens
|
|
1348
|
-
} : void 0,
|
|
1349
|
-
stop_reason: stopReason || null
|
|
1350
|
-
}
|
|
1351
|
-
};
|
|
1352
|
-
emit(message);
|
|
1353
|
-
verboseLog(this.args.verbose, "Emitted assistant message");
|
|
1354
|
-
}
|
|
1355
|
-
emitToolResult(result) {
|
|
1356
|
-
const message = {
|
|
1357
|
-
type: "user",
|
|
1358
|
-
message: {
|
|
1359
|
-
role: "user",
|
|
1360
|
-
content: [result]
|
|
1361
|
-
}
|
|
1362
|
-
};
|
|
1363
|
-
emit(message);
|
|
1364
|
-
verboseLog(this.args.verbose, "Emitted tool result");
|
|
1365
|
-
}
|
|
1366
|
-
emitSyntheticError(error, _model) {
|
|
1367
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1368
|
-
const message = {
|
|
1369
|
-
type: "assistant",
|
|
1370
|
-
message: {
|
|
1371
|
-
id: NIL_UUID,
|
|
1372
|
-
type: "message",
|
|
1373
|
-
role: "assistant",
|
|
1374
|
-
model: "<synthetic>",
|
|
1375
|
-
content: [
|
|
1376
|
-
{
|
|
1377
|
-
type: "text",
|
|
1378
|
-
text: `API Error: ${errorMessage}`
|
|
1379
|
-
}
|
|
1380
|
-
],
|
|
1381
|
-
stop_reason: null
|
|
1382
|
-
}
|
|
1383
|
-
};
|
|
1384
|
-
emit(message);
|
|
1385
|
-
verboseLog(this.args.verbose, "Emitted synthetic error");
|
|
1386
|
-
}
|
|
1387
|
-
emitResult(state, isError, result) {
|
|
1388
|
-
const duration = Date.now() - state.startTime;
|
|
1389
|
-
const apiDuration = state.apiStartTime > 0 ? Date.now() - state.apiStartTime : 0;
|
|
1390
|
-
const message = {
|
|
1391
|
-
type: "result",
|
|
1392
|
-
subtype: isError ? "error" : "success",
|
|
1393
|
-
is_error: isError,
|
|
1394
|
-
duration_ms: duration,
|
|
1395
|
-
duration_api_ms: apiDuration,
|
|
1396
|
-
num_turns: state.numTurns,
|
|
1397
|
-
result,
|
|
1398
|
-
session_id: state.sessionId,
|
|
1399
|
-
usage: state.totalUsage
|
|
1400
|
-
};
|
|
1401
|
-
emit(message);
|
|
1402
|
-
verboseLog(this.args.verbose, "Emitted result");
|
|
1403
|
-
}
|
|
1404
|
-
saveSession(threadId, state) {
|
|
1405
|
-
try {
|
|
1406
|
-
const sessionData = {
|
|
1407
|
-
sessionId: state.sessionId,
|
|
1408
|
-
agentSessionId: threadId,
|
|
1409
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1410
|
-
metadata: {
|
|
1411
|
-
totalUsage: state.totalUsage,
|
|
1412
|
-
numTurns: state.numTurns
|
|
1413
|
-
}
|
|
1414
|
-
};
|
|
1415
|
-
this.sessionManager.saveSession(sessionData);
|
|
1416
|
-
verboseLog(this.args.verbose, `Saved session to ${this.sessionManager.getSessionsDir()}`);
|
|
1417
|
-
} catch (error) {
|
|
1418
|
-
verboseLog(this.args.verbose, `Failed to save session: ${error}`);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
saveRawEvent(sessionId, event) {
|
|
1422
|
-
try {
|
|
1423
|
-
if (!this.args.debugDir) return;
|
|
1424
|
-
fs4.mkdirSync(this.args.debugDir, { recursive: true });
|
|
1425
|
-
const rawLogPath = path4.join(this.args.debugDir, `session-${sessionId}.raw.jsonl`);
|
|
1426
|
-
fs4.appendFileSync(rawLogPath, `${JSON.stringify(event)}
|
|
1427
|
-
`);
|
|
1428
|
-
this.createRawLogFile(sessionId);
|
|
1429
|
-
} catch (error) {
|
|
1430
|
-
verboseLog(this.args.verbose, `Failed to save raw event: ${error}`);
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
savePreInitError(error) {
|
|
1434
|
-
try {
|
|
1435
|
-
if (!this.args.debugDir) return;
|
|
1436
|
-
fs4.mkdirSync(this.args.debugDir, { recursive: true });
|
|
1437
|
-
const errorLogPath = path4.join(this.args.debugDir, "session-unknown.raw.log");
|
|
1438
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1439
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1440
|
-
fs4.appendFileSync(errorLogPath, `[${timestamp}] ${errorMessage}
|
|
1441
|
-
`);
|
|
1442
|
-
} catch (err) {
|
|
1443
|
-
verboseLog(this.args.verbose, `Failed to save pre-init error: ${err}`);
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
saveRuntimeError(sessionId, error) {
|
|
1447
|
-
try {
|
|
1448
|
-
if (!this.args.debugDir) return;
|
|
1449
|
-
fs4.mkdirSync(this.args.debugDir, { recursive: true });
|
|
1450
|
-
const errorLogPath = path4.join(this.args.debugDir, `session-${sessionId}.raw.log`);
|
|
1451
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1452
|
-
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
1453
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1454
|
-
let logEntry = `[${timestamp}] RUNTIME ERROR: ${errorMessage}
|
|
1455
|
-
`;
|
|
1456
|
-
if (errorStack) {
|
|
1457
|
-
logEntry += `Stack trace:
|
|
1458
|
-
${errorStack}
|
|
1459
|
-
`;
|
|
1460
|
-
}
|
|
1461
|
-
fs4.appendFileSync(errorLogPath, logEntry);
|
|
1462
|
-
} catch (err) {
|
|
1463
|
-
verboseLog(this.args.verbose, `Failed to save runtime error: ${err}`);
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
createRawLogFile(sessionId) {
|
|
1467
|
-
try {
|
|
1468
|
-
if (!this.args.debugDir) return;
|
|
1469
|
-
const rawLogPath = path4.join(this.args.debugDir, `session-${sessionId}.raw.log`);
|
|
1470
|
-
if (!fs4.existsSync(rawLogPath)) {
|
|
1471
|
-
fs4.writeFileSync(
|
|
1472
|
-
rawLogPath,
|
|
1473
|
-
`# Codex SDK raw log (session ${sessionId})
|
|
1474
|
-
# Note: SDK doesn't expose agent stderr
|
|
1475
|
-
`
|
|
1476
|
-
);
|
|
1477
|
-
}
|
|
1478
|
-
} catch (error) {
|
|
1479
|
-
verboseLog(this.args.verbose, `Failed to create raw log file: ${error}`);
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
};
|
|
1483
|
-
|
|
1484
|
-
// src/utils/args.ts
|
|
1485
|
-
async function readStdin() {
|
|
1486
|
-
return new Promise((resolve, reject) => {
|
|
1487
|
-
let data = "";
|
|
1488
|
-
process.stdin.setEncoding("utf8");
|
|
1489
|
-
process.stdin.on("data", (chunk) => {
|
|
1490
|
-
data += chunk;
|
|
1491
|
-
});
|
|
1492
|
-
process.stdin.on("end", () => {
|
|
1493
|
-
resolve(data.trim());
|
|
1494
|
-
});
|
|
1495
|
-
process.stdin.on("error", (err) => {
|
|
1496
|
-
reject(err);
|
|
1497
|
-
});
|
|
1498
|
-
});
|
|
1499
|
-
}
|
|
1500
|
-
function printVersion() {
|
|
1501
|
-
console.log("codex-shim 1.0.0");
|
|
1502
|
-
}
|
|
1503
|
-
function printHelp() {
|
|
1504
|
-
console.log(`
|
|
1505
|
-
codex-shim - OpenAI Codex shim for standardized agent interface
|
|
1506
|
-
|
|
1507
|
-
USAGE:
|
|
1508
|
-
echo "prompt" | codex-shim --model <model>[-<reasoning>] [options]
|
|
1509
|
-
|
|
1510
|
-
REQUIRED:
|
|
1511
|
-
--model <model> OpenAI model identifier (shortname, openai/model, or full ID)
|
|
1512
|
-
Optionally append reasoning effort:
|
|
1513
|
-
-minimal, -low, -medium, -high, or -xhigh
|
|
1514
|
-
|
|
1515
|
-
OPTIONS:
|
|
1516
|
-
-p Indicates prompt via stdin (optional, stdin always read)
|
|
1517
|
-
--resume <session_id> Continue existing session
|
|
1518
|
-
--verbose Enable verbose logging to stderr
|
|
1519
|
-
--append-system-prompt Additional system prompt to append
|
|
1520
|
-
--idle-timeout <seconds> Max seconds between agent events before aborting (default: 120)
|
|
1521
|
-
--debug-dir <path> Directory for debug logs and session data
|
|
1522
|
-
--sandbox <level> Sandbox level: none (default), standard, or strict
|
|
1523
|
-
--self-test Run environment verification
|
|
1524
|
-
--version Print version and exit
|
|
1525
|
-
--help Print this help and exit
|
|
1526
|
-
|
|
1527
|
-
ENVIRONMENT:
|
|
1528
|
-
MODEL Default model if --model not provided
|
|
1529
|
-
OPENAI_API_KEY OpenAI API key for authentication
|
|
1530
|
-
CODEX_API_KEY Codex-specific API key (alternative)
|
|
1531
|
-
|
|
1532
|
-
EXAMPLES:
|
|
1533
|
-
echo "Hello" | codex-shim --model codex
|
|
1534
|
-
echo "Complex task" | codex-shim --model codex-high --verbose
|
|
1535
|
-
echo "Fix bug" | codex-shim --model gpt-5.2-xhigh
|
|
1536
|
-
echo "Continue" | codex-shim --model codex --resume <session-id>
|
|
1537
|
-
`);
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
// src/index.ts
|
|
1541
|
-
async function main() {
|
|
1542
|
-
try {
|
|
1543
|
-
const args = parseArgs(process.argv.slice(2));
|
|
1544
|
-
if (!args.model && !args.selfTest && !args.version && !args.help) {
|
|
1545
|
-
args.model = process.env.MODEL || "codex";
|
|
1546
|
-
}
|
|
1547
|
-
if (args.version) {
|
|
1548
|
-
printVersion();
|
|
1549
|
-
process.exit(0);
|
|
1550
|
-
}
|
|
1551
|
-
if (args.help) {
|
|
1552
|
-
printHelp();
|
|
1553
|
-
process.exit(0);
|
|
1554
|
-
}
|
|
1555
|
-
if (args.selfTest) {
|
|
1556
|
-
const result = await runSelfTest();
|
|
1557
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1558
|
-
process.exit(result.overall.passed ? 0 : 1);
|
|
1559
|
-
}
|
|
1560
|
-
if (!args.model) {
|
|
1561
|
-
console.error("Error: --model argument is required");
|
|
1562
|
-
process.exit(1);
|
|
1563
|
-
}
|
|
1564
|
-
const prompt = await readStdin();
|
|
1565
|
-
if (!prompt) {
|
|
1566
|
-
verboseLog(args.verbose, "Empty prompt, exiting silently");
|
|
1567
|
-
process.exit(0);
|
|
1568
|
-
}
|
|
1569
|
-
verboseLog(args.verbose, "Starting Codex shim");
|
|
1570
|
-
verboseLog(args.verbose, `Model: ${args.model}`);
|
|
1571
|
-
verboseLog(args.verbose, `Prompt length: ${prompt.length} characters`);
|
|
1572
|
-
if (args.resume) {
|
|
1573
|
-
verboseLog(args.verbose, `Resuming session: ${args.resume}`);
|
|
1574
|
-
}
|
|
1575
|
-
const shim = new CodexShim(args);
|
|
1576
|
-
await shim.run(prompt);
|
|
1577
|
-
} catch (error) {
|
|
1578
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1579
|
-
console.error(`Error: ${errorMessage}`);
|
|
1580
|
-
process.exit(1);
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
main();
|