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.
Files changed (79) hide show
  1. package/README.md +12 -11
  2. package/dist/base-process-manager.d.ts +30 -0
  3. package/dist/budget.d.ts +315 -0
  4. package/dist/checkpoint-git.d.ts +98 -0
  5. package/dist/claude-agent-sdk-manager.d.ts +144 -0
  6. package/dist/claude-log-parser.d.ts +63 -0
  7. package/dist/claude-runtime-extractor.d.ts +73 -0
  8. package/dist/codex-runtime-extractor.d.ts +107 -0
  9. package/dist/codon-runner.d.ts +278 -0
  10. package/dist/config-validation/model-validator.d.ts +16 -0
  11. package/dist/config-validation/sentinel.schema.d.ts +6967 -0
  12. package/dist/config.d.ts +40815 -0
  13. package/dist/cost-tracker.d.ts +72 -0
  14. package/dist/execution-planner.d.ts +62 -0
  15. package/dist/execution-thread.d.ts +71 -0
  16. package/dist/exports/schemas.d.ts +9 -0
  17. package/dist/exports/schemas.js +1019 -0
  18. package/dist/exports/types.d.ts +15 -0
  19. package/dist/exports/types.js +60 -0
  20. package/dist/file-resolver.d.ts +33 -0
  21. package/dist/index.js +380 -293
  22. package/dist/index.js.map +33 -29
  23. package/dist/llm/llm-provider-registry.d.ts +207 -0
  24. package/dist/llm/models-dev-schema.d.ts +679 -0
  25. package/dist/llm/provider-config.d.ts +30 -0
  26. package/dist/prompt-builder.d.ts +75 -0
  27. package/dist/prompt-frontmatter.d.ts +61 -0
  28. package/dist/replay-process-manager.d.ts +82 -0
  29. package/dist/runtime-extractor-base.d.ts +120 -0
  30. package/dist/schemas/event-schemas.d.ts +8389 -0
  31. package/dist/schemas/websocket-log-schemas.d.ts +4502 -0
  32. package/dist/shim-process-manager.d.ts +98 -0
  33. package/dist/shim-runtime-extractor.d.ts +51 -0
  34. package/dist/shims/codex/README.md +129 -0
  35. package/dist/shims/codex/THIRDPARTY.md +18 -0
  36. package/dist/shims/codex/VERSION +1 -0
  37. package/dist/shims/codex/common/package.json +24 -0
  38. package/dist/shims/codex/index.js +1154 -970
  39. package/dist/shims/codex/package.json +46 -0
  40. package/dist/shims/codex/tsup.config.ts +16 -0
  41. package/dist/shims/gemini/README.md +59 -0
  42. package/dist/shims/gemini/THIRDPARTY.md +32 -0
  43. package/dist/shims/gemini/VERSION +1 -0
  44. package/dist/shims/gemini/common/package.json +24 -0
  45. package/dist/shims/gemini/index.js +1359 -30
  46. package/dist/shims/gemini/package.json +37 -0
  47. package/dist/shims/opencode/README.md +82 -0
  48. package/dist/shims/opencode/THIRDPARTY.md +32 -0
  49. package/dist/shims/opencode/VERSION +1 -0
  50. package/dist/shims/opencode/common/package.json +24 -0
  51. package/dist/shims/opencode/index.js +1476 -0
  52. package/dist/shims/opencode/package.json +38 -0
  53. package/dist/shims/pi/README.md +87 -0
  54. package/dist/shims/pi/THIRDPARTY.md +24 -0
  55. package/dist/shims/pi/VERSION +1 -0
  56. package/dist/shims/pi/common/package.json +24 -0
  57. package/dist/shims/pi/index.js +249832 -0
  58. package/dist/shims/pi/package.json +53 -0
  59. package/dist/state-manager.d.ts +161 -0
  60. package/dist/state-transition-guards.d.ts +37 -0
  61. package/dist/telemetry/telemetry-types.d.ts +206 -0
  62. package/dist/typed-event-emitter.d.ts +57 -0
  63. package/dist/types/branded-types.d.ts +15 -0
  64. package/dist/types/budget-types.d.ts +82 -0
  65. package/dist/types/claude-session-schema.d.ts +2430 -0
  66. package/dist/types/error-types.d.ts +44 -0
  67. package/dist/types/input-ai-types.d.ts +1070 -0
  68. package/dist/types/llm-call-types.d.ts +3829 -0
  69. package/dist/types/sentinel-types.d.ts +66 -0
  70. package/dist/types/state-types.d.ts +1099 -0
  71. package/dist/types/tool-types.d.ts +86 -0
  72. package/dist/types/types.d.ts +367 -0
  73. package/dist/types/websocket-log-types.d.ts +7 -0
  74. package/dist/utils.d.ts +452 -0
  75. package/package.json +15 -2
  76. package/schemas/hank.schema.json +158 -3
  77. package/schemas/hankweave.schema.json +17 -1
  78. package/shims/codex/index.js +0 -1583
  79. package/shims/gemini/index.js +0 -31
@@ -1,230 +1,19 @@
1
1
  #!/usr/bin/env node
2
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
3
  // 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";
4
+ import { spawn as spawn2 } from "child_process";
5
+ import fs4 from "fs";
222
6
  import os2 from "os";
7
+ import path6 from "path";
8
+
9
+ // node_modules/@openai/codex-sdk/dist/index.js
10
+ import { promises as fs } from "fs";
11
+ import os from "os";
12
+ import path from "path";
13
+ import { spawn } from "child_process";
223
14
  import path2 from "path";
224
- import { spawn as spawn2 } from "child_process";
225
- import path22 from "path";
226
15
  import readline from "readline";
227
- import { fileURLToPath } from "url";
16
+ import { createRequire } from "module";
228
17
  async function createOutputSchemaFile(schema) {
229
18
  if (schema === void 0) {
230
19
  return { cleanup: async () => {
@@ -233,16 +22,16 @@ async function createOutputSchemaFile(schema) {
233
22
  if (!isJsonObject(schema)) {
234
23
  throw new Error("outputSchema must be a plain JSON object");
235
24
  }
236
- const schemaDir = await fs2.mkdtemp(path2.join(os2.tmpdir(), "codex-output-schema-"));
237
- const schemaPath = path2.join(schemaDir, "schema.json");
25
+ const schemaDir = await fs.mkdtemp(path.join(os.tmpdir(), "codex-output-schema-"));
26
+ const schemaPath = path.join(schemaDir, "schema.json");
238
27
  const cleanup = async () => {
239
28
  try {
240
- await fs2.rm(schemaDir, { recursive: true, force: true });
29
+ await fs.rm(schemaDir, { recursive: true, force: true });
241
30
  } catch {
242
31
  }
243
32
  };
244
33
  try {
245
- await fs2.writeFile(schemaPath, JSON.stringify(schema), "utf8");
34
+ await fs.writeFile(schemaPath, JSON.stringify(schema), "utf8");
246
35
  return { schemaPath, cleanup };
247
36
  } catch (error) {
248
37
  await cleanup();
@@ -355,6 +144,16 @@ function normalizeInput(input) {
355
144
  }
356
145
  var INTERNAL_ORIGINATOR_ENV = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE";
357
146
  var TYPESCRIPT_SDK_ORIGINATOR = "codex_sdk_ts";
147
+ var CODEX_NPM_NAME = "@openai/codex";
148
+ var PLATFORM_PACKAGE_BY_TARGET = {
149
+ "x86_64-unknown-linux-musl": "@openai/codex-linux-x64",
150
+ "aarch64-unknown-linux-musl": "@openai/codex-linux-arm64",
151
+ "x86_64-apple-darwin": "@openai/codex-darwin-x64",
152
+ "aarch64-apple-darwin": "@openai/codex-darwin-arm64",
153
+ "x86_64-pc-windows-msvc": "@openai/codex-win32-x64",
154
+ "aarch64-pc-windows-msvc": "@openai/codex-win32-arm64"
155
+ };
156
+ var moduleRequire = createRequire(import.meta.url);
358
157
  var CodexExec = class {
359
158
  executablePath;
360
159
  envOverride;
@@ -437,7 +236,7 @@ var CodexExec = class {
437
236
  if (args.apiKey) {
438
237
  env.CODEX_API_KEY = args.apiKey;
439
238
  }
440
- const child = spawn2(this.executablePath, commandArgs, {
239
+ const child = spawn(this.executablePath, commandArgs, {
441
240
  env,
442
241
  signal: args.signal
443
242
  });
@@ -567,8 +366,6 @@ function formatTomlKey(key) {
567
366
  function isPlainObject(value) {
568
367
  return typeof value === "object" && value !== null && !Array.isArray(value);
569
368
  }
570
- var scriptFileName = fileURLToPath(import.meta.url);
571
- var scriptDirName = path22.dirname(scriptFileName);
572
369
  function findCodexPath() {
573
370
  const { platform, arch } = process;
574
371
  let targetTriple = null;
@@ -616,10 +413,24 @@ function findCodexPath() {
616
413
  if (!targetTriple) {
617
414
  throw new Error(`Unsupported platform: ${platform} (${arch})`);
618
415
  }
619
- const vendorRoot = path22.join(scriptDirName, "..", "vendor");
620
- const archRoot = path22.join(vendorRoot, targetTriple);
416
+ const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
417
+ if (!platformPackage) {
418
+ throw new Error(`Unsupported target triple: ${targetTriple}`);
419
+ }
420
+ let vendorRoot;
421
+ try {
422
+ const codexPackageJsonPath = moduleRequire.resolve(`${CODEX_NPM_NAME}/package.json`);
423
+ const codexRequire = createRequire(codexPackageJsonPath);
424
+ const platformPackageJsonPath = codexRequire.resolve(`${platformPackage}/package.json`);
425
+ vendorRoot = path2.join(path2.dirname(platformPackageJsonPath), "vendor");
426
+ } catch {
427
+ throw new Error(
428
+ `Unable to locate Codex CLI binaries. Ensure ${CODEX_NPM_NAME} is installed with optional dependencies.`
429
+ );
430
+ }
431
+ const archRoot = path2.join(vendorRoot, targetTriple);
621
432
  const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
622
- const binaryPath = path22.join(archRoot, "codex", codexBinaryName);
433
+ const binaryPath = path2.join(archRoot, "codex", codexBinaryName);
623
434
  return binaryPath;
624
435
  }
625
436
  var Codex = class {
@@ -649,9 +460,81 @@ var Codex = class {
649
460
  }
650
461
  };
651
462
 
652
- // ../common/src/sessions.ts
463
+ // node_modules/@shims/common/src/args.ts
464
+ var VALID_SANDBOX_LEVELS = ["none", "standard", "strict"];
465
+ function parseArgs(argv, aliases) {
466
+ const args = {
467
+ model: "",
468
+ verbose: false,
469
+ idleTimeout: 120,
470
+ sandbox: "none",
471
+ selfTest: false,
472
+ version: false,
473
+ help: false
474
+ };
475
+ for (let i = 0; i < argv.length; i++) {
476
+ let arg = argv[i];
477
+ if (arg.includes("=")) {
478
+ const [key, value] = arg.split("=", 2);
479
+ argv.splice(i, 1, key, value);
480
+ arg = key;
481
+ }
482
+ if (aliases && arg in aliases) {
483
+ arg = aliases[arg];
484
+ }
485
+ switch (arg) {
486
+ case "--model":
487
+ args.model = argv[++i];
488
+ break;
489
+ case "--resume":
490
+ args.resume = argv[++i];
491
+ break;
492
+ case "--verbose":
493
+ args.verbose = true;
494
+ break;
495
+ case "--append-system-prompt":
496
+ args.appendSystemPrompt = argv[++i];
497
+ break;
498
+ case "--debug-dir":
499
+ args.debugDir = argv[++i];
500
+ break;
501
+ case "--idle-timeout": {
502
+ const val = Number(argv[++i]);
503
+ if (!Number.isFinite(val) || val <= 0) {
504
+ console.error("Invalid --idle-timeout value: must be a positive number");
505
+ process.exit(1);
506
+ }
507
+ args.idleTimeout = val;
508
+ break;
509
+ }
510
+ case "--sandbox": {
511
+ const level = argv[++i];
512
+ if (!VALID_SANDBOX_LEVELS.includes(level)) {
513
+ console.error(
514
+ `Invalid --sandbox value: must be one of ${VALID_SANDBOX_LEVELS.join(", ")}`
515
+ );
516
+ process.exit(1);
517
+ }
518
+ args.sandbox = level;
519
+ break;
520
+ }
521
+ case "--self-test":
522
+ args.selfTest = true;
523
+ break;
524
+ case "--version":
525
+ args.version = true;
526
+ break;
527
+ case "--help":
528
+ args.help = true;
529
+ break;
530
+ }
531
+ }
532
+ return args;
533
+ }
534
+
535
+ // node_modules/@shims/common/src/sessions.ts
653
536
  import { randomUUID } from "crypto";
654
- import fs3 from "fs";
537
+ import fs2 from "fs";
655
538
  import path3 from "path";
656
539
  var SessionManager = class {
657
540
  sessionsDir;
@@ -665,7 +548,6 @@ var SessionManager = class {
665
548
  }
666
549
  this.sessionsDir = path3.join(home, ".shim", "sessions");
667
550
  }
668
- fs3.mkdirSync(this.sessionsDir, { recursive: true });
669
551
  }
670
552
  /**
671
553
  * Generate a new UUID v4 session ID
@@ -677,8 +559,9 @@ var SessionManager = class {
677
559
  * Save session data
678
560
  */
679
561
  saveSession(data) {
562
+ this.ensureSessionsDir();
680
563
  const sessionPath = path3.join(this.sessionsDir, `${data.sessionId}.json`);
681
- fs3.writeFileSync(sessionPath, JSON.stringify(data, null, 2), "utf8");
564
+ fs2.writeFileSync(sessionPath, JSON.stringify(data, null, 2), "utf8");
682
565
  }
683
566
  /**
684
567
  * Load session data by session ID
@@ -687,7 +570,7 @@ var SessionManager = class {
687
570
  loadSession(sessionId) {
688
571
  const sessionPath = path3.join(this.sessionsDir, `${sessionId}.json`);
689
572
  try {
690
- const content = fs3.readFileSync(sessionPath, "utf8");
573
+ const content = fs2.readFileSync(sessionPath, "utf8");
691
574
  return JSON.parse(content);
692
575
  } catch (error) {
693
576
  if (error.code === "ENOENT") {
@@ -701,7 +584,7 @@ var SessionManager = class {
701
584
  */
702
585
  sessionExists(sessionId) {
703
586
  const sessionPath = path3.join(this.sessionsDir, `${sessionId}.json`);
704
- return fs3.existsSync(sessionPath);
587
+ return fs2.existsSync(sessionPath);
705
588
  }
706
589
  /**
707
590
  * Delete session data
@@ -709,7 +592,7 @@ var SessionManager = class {
709
592
  deleteSession(sessionId) {
710
593
  const sessionPath = path3.join(this.sessionsDir, `${sessionId}.json`);
711
594
  try {
712
- fs3.unlinkSync(sessionPath);
595
+ fs2.unlinkSync(sessionPath);
713
596
  } catch (error) {
714
597
  if (error.code !== "ENOENT") {
715
598
  throw error;
@@ -721,7 +604,7 @@ var SessionManager = class {
721
604
  */
722
605
  listSessions() {
723
606
  try {
724
- const files = fs3.readdirSync(this.sessionsDir);
607
+ const files = fs2.readdirSync(this.sessionsDir);
725
608
  return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
726
609
  } catch (error) {
727
610
  if (error.code === "ENOENT") {
@@ -736,9 +619,12 @@ var SessionManager = class {
736
619
  getSessionsDir() {
737
620
  return this.sessionsDir;
738
621
  }
622
+ ensureSessionsDir() {
623
+ fs2.mkdirSync(this.sessionsDir, { recursive: true });
624
+ }
739
625
  };
740
626
 
741
- // ../common/src/timeout.ts
627
+ // node_modules/@shims/common/src/timeout.ts
742
628
  var IdleTimeoutError = class extends Error {
743
629
  timeoutMs;
744
630
  constructor(timeoutMs) {
@@ -747,21 +633,49 @@ var IdleTimeoutError = class extends Error {
747
633
  this.timeoutMs = timeoutMs;
748
634
  }
749
635
  };
750
- async function* withIdleTimeout(events, timeoutMs) {
636
+ var BusyStepTimeoutError = class extends Error {
637
+ timeoutMs;
638
+ constructor(timeoutMs) {
639
+ super(`Busy-step stall timeout: no observed activity for ${timeoutMs}ms`);
640
+ this.name = "BusyStepTimeoutError";
641
+ this.timeoutMs = timeoutMs;
642
+ }
643
+ };
644
+ async function* withAdaptiveTimeout(events, options) {
751
645
  const iterator = events[Symbol.asyncIterator]();
646
+ let state = "idle";
647
+ const busyTimeoutMs = Math.max(
648
+ options.busyTimeoutMs ?? options.idleTimeoutMs,
649
+ options.idleTimeoutMs
650
+ );
651
+ const controller = {
652
+ markBusy() {
653
+ state = "busy";
654
+ },
655
+ markIdle() {
656
+ state = "idle";
657
+ },
658
+ get state() {
659
+ return state;
660
+ }
661
+ };
752
662
  try {
753
663
  while (true) {
664
+ const timeoutMs = state === "busy" ? busyTimeoutMs : options.idleTimeoutMs;
754
665
  let timeoutId;
755
666
  try {
756
667
  const result = await Promise.race([
757
668
  iterator.next(),
758
669
  new Promise((_, reject) => {
759
670
  timeoutId = setTimeout(() => {
760
- reject(new IdleTimeoutError(timeoutMs));
671
+ reject(
672
+ state === "busy" ? new BusyStepTimeoutError(timeoutMs) : new IdleTimeoutError(timeoutMs)
673
+ );
761
674
  }, timeoutMs);
762
675
  })
763
676
  ]);
764
677
  if (result.done) break;
678
+ options.onEvent?.(result.value, controller);
765
679
  yield result.value;
766
680
  } finally {
767
681
  clearTimeout(timeoutId);
@@ -772,812 +686,1082 @@ async function* withIdleTimeout(events, timeoutMs) {
772
686
  }
773
687
  }
774
688
 
775
- // src/utils/ids.ts
689
+ // src/debug.ts
690
+ import fs3 from "fs";
691
+ import path4 from "path";
692
+ var DebugRecorder = class {
693
+ debugDir;
694
+ rawJsonlPath;
695
+ rawLogPath;
696
+ constructor(debugDir) {
697
+ this.debugDir = debugDir;
698
+ }
699
+ setSession(sessionId, init) {
700
+ if (!this.debugDir) {
701
+ return;
702
+ }
703
+ fs3.mkdirSync(this.debugDir, { recursive: true });
704
+ this.rawJsonlPath = path4.join(this.debugDir, `session-${sessionId}.raw.jsonl`);
705
+ this.rawLogPath = path4.join(this.debugDir, `session-${sessionId}.raw.log`);
706
+ this.touch(this.rawJsonlPath);
707
+ this.touch(this.rawLogPath);
708
+ this.logJson({
709
+ type: "init",
710
+ session_id: sessionId,
711
+ cwd: init.cwd,
712
+ model: init.model,
713
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
714
+ });
715
+ }
716
+ logSdkEvent(event) {
717
+ this.logJson(event);
718
+ }
719
+ logResult(status, extra = {}) {
720
+ this.logJson({
721
+ type: "result",
722
+ status,
723
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
724
+ ...extra
725
+ });
726
+ }
727
+ logLine(line) {
728
+ if (!this.rawLogPath) {
729
+ return;
730
+ }
731
+ fs3.appendFileSync(this.rawLogPath, line.endsWith("\n") ? line : `${line}
732
+ `, "utf8");
733
+ }
734
+ static logStartupError(debugDir, line) {
735
+ if (!debugDir) {
736
+ return;
737
+ }
738
+ fs3.mkdirSync(debugDir, { recursive: true });
739
+ const rawLogPath = path4.join(debugDir, "session-unknown.raw.log");
740
+ fs3.appendFileSync(rawLogPath, line.endsWith("\n") ? line : `${line}
741
+ `, "utf8");
742
+ }
743
+ logJson(value) {
744
+ if (!this.rawJsonlPath) {
745
+ return;
746
+ }
747
+ fs3.appendFileSync(this.rawJsonlPath, `${JSON.stringify(value)}
748
+ `, "utf8");
749
+ }
750
+ touch(filePath) {
751
+ fs3.closeSync(fs3.openSync(filePath, "a"));
752
+ }
753
+ };
754
+
755
+ // src/utils.ts
756
+ import { randomBytes, randomUUID as randomUUID2 } from "crypto";
757
+ import path5 from "path";
758
+ var NIL_UUID = "00000000-0000-0000-0000-000000000000";
759
+ var SESSION_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
760
+ function generateSessionId() {
761
+ return randomUUID2();
762
+ }
776
763
  function generateMessageId() {
777
- return `msg_${Date.now().toString(36)}${Math.random().toString(36).substring(2, 10)}`;
764
+ return `msg_${Date.now().toString(36)}${randomBytes(6).toString("hex")}`;
778
765
  }
779
766
  function generateToolUseId() {
780
- return `toolu_${Date.now().toString(36)}${Math.random().toString(36).substring(2, 12)}`;
767
+ return `toolu_${Date.now().toString(36)}${randomBytes(8).toString("hex")}`;
781
768
  }
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);
769
+ function isValidSessionId(value) {
770
+ return typeof value === "string" && SESSION_ID_REGEX.test(value);
799
771
  }
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 };
772
+ function resolveModel(modelFromArgs) {
773
+ const requestedModel = modelFromArgs?.trim() || process.env.MODEL?.trim() || "gpt-5.1-codex-max";
774
+ let sdkModel = requestedModel;
775
+ if (sdkModel.includes("/")) {
776
+ const [provider, model] = sdkModel.split("/", 2);
777
+ if (provider.toLowerCase() === "openai" && model) {
778
+ sdkModel = model;
779
+ }
816
780
  }
781
+ let reasoningEffort = "high";
782
+ const effortMatch = sdkModel.match(/^(.*)-(minimal|low|medium|high|xhigh)$/);
783
+ if (effortMatch) {
784
+ sdkModel = effortMatch[1];
785
+ reasoningEffort = effortMatch[2];
786
+ }
787
+ const publicModel = `openai/${sdkModel}`;
817
788
  return {
818
- providerID: "openai",
819
- modelID: fullModel,
789
+ requestedModel,
790
+ publicModel,
791
+ sdkModel,
820
792
  reasoningEffort
821
793
  };
822
794
  }
823
- function formatModelOutput(spec) {
824
- return `${spec.providerID}/${spec.modelID}`;
795
+ function mapSandbox(level) {
796
+ switch (level) {
797
+ case "strict":
798
+ return "read-only";
799
+ case "standard":
800
+ return "workspace-write";
801
+ case "none":
802
+ default:
803
+ return "danger-full-access";
804
+ }
805
+ }
806
+ function getCodexPathOverride() {
807
+ const value = process.env.CODEX_PATH_OVERRIDE?.trim();
808
+ return value ? value : void 0;
825
809
  }
826
- function getCodexModelId(spec) {
827
- return spec.modelID;
810
+ function toErrorMessage(error) {
811
+ return error instanceof Error ? error.message : String(error);
828
812
  }
829
-
830
- // src/utils/output.ts
831
- function emit(message) {
832
- console.log(JSON.stringify(message));
813
+ function camelToSnakeKey(key) {
814
+ return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
833
815
  }
834
- function verboseLog(verbose, ...args) {
835
- if (verbose) {
836
- console.error("[codex-shim]", ...args);
816
+ function topLevelCamelToSnake(value) {
817
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
818
+ return void 0;
837
819
  }
820
+ const result = {};
821
+ for (const [key, entry] of Object.entries(value)) {
822
+ result[camelToSnakeKey(key)] = entry;
823
+ }
824
+ return result;
838
825
  }
839
826
  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);
827
+ await new Promise((resolve, reject) => {
828
+ const done = (error) => {
829
+ if (error) {
830
+ reject(error);
831
+ } else {
832
+ resolve();
833
+ }
834
+ };
835
+ if (process.stdout.write("")) {
836
+ done();
837
+ return;
846
838
  }
839
+ process.stdout.once("drain", () => done());
840
+ process.stdout.once("error", (error) => done(error));
847
841
  });
848
842
  }
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;
843
+ function makeAbsoluteCwd(cwd) {
844
+ return path5.resolve(cwd);
879
845
  }
880
- function camelToSnake(str) {
881
- return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
846
+ function toRelativePath(filePath, cwd) {
847
+ const absoluteCwd = path5.resolve(cwd);
848
+ const absolutePath = path5.resolve(filePath);
849
+ const relative = path5.relative(absoluteCwd, absolutePath);
850
+ return relative && !relative.startsWith("..") ? relative : absolutePath;
882
851
  }
883
- function normalizeToolInput(input) {
884
- const normalized = {};
885
- for (const [key, value] of Object.entries(input)) {
886
- normalized[camelToSnake(key)] = value;
852
+
853
+ // src/tools.ts
854
+ function normalizeToolUse(item, cwd) {
855
+ switch (item.type) {
856
+ case "command_execution":
857
+ return normalizeCommandExecution(item);
858
+ case "file_change":
859
+ return normalizeFileChange(item, cwd);
860
+ case "mcp_tool_call":
861
+ return normalizeMcpToolCall(item);
862
+ case "web_search":
863
+ return normalizeWebSearch(item);
864
+ default:
865
+ return null;
866
+ }
867
+ }
868
+ function normalizeToolResult(item, cwd) {
869
+ switch (item.type) {
870
+ case "command_execution":
871
+ return normalizeCommandExecutionResult(item);
872
+ case "file_change":
873
+ return normalizeFileChangeResult(item, cwd);
874
+ case "mcp_tool_call":
875
+ return normalizeMcpToolCallResult(item);
876
+ case "web_search":
877
+ return {
878
+ isError: false,
879
+ content: `Web search completed for query: ${item.query}`
880
+ };
881
+ default:
882
+ return null;
883
+ }
884
+ }
885
+ function normalizeCommandExecution(item) {
886
+ const innerCommand = extractInnerCommand(item.command);
887
+ const simpleWrite = parseSimpleShellWrite(innerCommand);
888
+ if (simpleWrite) {
889
+ return {
890
+ name: "Write",
891
+ input: {
892
+ file_path: simpleWrite.filePath,
893
+ content: simpleWrite.content
894
+ }
895
+ };
896
+ }
897
+ const readMatch = innerCommand.match(/^cat\s+([^\s;&|]+)$/);
898
+ if (readMatch) {
899
+ return {
900
+ name: "Read",
901
+ input: { file_path: stripQuotes(readMatch[1]) }
902
+ };
903
+ }
904
+ const lsMatch = innerCommand.match(/^ls(?:\s+(-[A-Za-z-]+))*?(?:\s+([^;&|]+))?$/);
905
+ if (lsMatch) {
906
+ const target = lsMatch[2]?.trim();
907
+ return {
908
+ name: "LS",
909
+ input: target ? { path: stripQuotes(target) } : void 0
910
+ };
911
+ }
912
+ const grepMatch = innerCommand.match(/^(?:rg|grep)\b(.*)$/);
913
+ if (grepMatch) {
914
+ return {
915
+ name: "Grep",
916
+ input: { command: innerCommand }
917
+ };
918
+ }
919
+ const globMatch = innerCommand.match(/^(?:find|fd)\b(.*)$/);
920
+ if (globMatch) {
921
+ return {
922
+ name: "Glob",
923
+ input: { command: innerCommand }
924
+ };
925
+ }
926
+ return {
927
+ name: "Bash",
928
+ input: { command: innerCommand }
929
+ };
930
+ }
931
+ function normalizeCommandExecutionResult(item) {
932
+ const output = item.aggregated_output;
933
+ const innerCommand = extractInnerCommand(item.command);
934
+ const simpleWrite = parseSimpleShellWrite(innerCommand);
935
+ if (item.status === "failed" || typeof item.exit_code === "number" && item.exit_code !== 0) {
936
+ return {
937
+ isError: true,
938
+ content: {
939
+ is_error: true,
940
+ error: output.trim() || `Command failed with exit code ${item.exit_code ?? "unknown"}`
941
+ }
942
+ };
943
+ }
944
+ if (output.trim().length > 0) {
945
+ return {
946
+ isError: false,
947
+ content: output
948
+ };
949
+ }
950
+ if (simpleWrite) {
951
+ return {
952
+ isError: false,
953
+ content: `Wrote ${simpleWrite.filePath}`
954
+ };
955
+ }
956
+ return {
957
+ isError: false,
958
+ content: `Command completed with exit code ${item.exit_code ?? 0} (no output).`
959
+ };
960
+ }
961
+ function normalizeFileChange(item, cwd) {
962
+ const paths = item.changes.map((change) => toRelativePath(change.path, cwd));
963
+ const allAdds = item.changes.every((change) => change.kind === "add");
964
+ return {
965
+ name: allAdds ? "Write" : "Edit",
966
+ input: paths.length === 1 ? { file_path: paths[0] } : { paths }
967
+ };
968
+ }
969
+ function normalizeFileChangeResult(item, cwd) {
970
+ const summary = item.changes.map((change) => `${change.kind} ${toRelativePath(change.path, cwd)}`).join(", ");
971
+ if (item.status === "failed") {
972
+ return {
973
+ isError: true,
974
+ content: {
975
+ is_error: true,
976
+ error: summary || "File change failed"
977
+ }
978
+ };
979
+ }
980
+ return {
981
+ isError: false,
982
+ content: summary ? `Applied file change: ${summary}` : "Applied file change."
983
+ };
984
+ }
985
+ function normalizeMcpToolCall(item) {
986
+ const lowerServer = item.server.toLowerCase();
987
+ const lowerTool = item.tool.toLowerCase();
988
+ if (lowerTool.includes("search") || lowerServer.includes("search") || lowerServer.includes("web")) {
989
+ return {
990
+ name: "WebSearch",
991
+ input: topLevelCamelToSnake(item.arguments) ?? { arguments: item.arguments }
992
+ };
993
+ }
994
+ if (lowerTool.includes("fetch") || lowerTool.includes("browse")) {
995
+ return {
996
+ name: "WebFetch",
997
+ input: topLevelCamelToSnake(item.arguments) ?? { arguments: item.arguments }
998
+ };
999
+ }
1000
+ return {
1001
+ name: `${item.server}:${item.tool}`,
1002
+ input: topLevelCamelToSnake(item.arguments) ?? { arguments: item.arguments }
1003
+ };
1004
+ }
1005
+ function normalizeMcpToolCallResult(item) {
1006
+ if (item.status === "failed") {
1007
+ return {
1008
+ isError: true,
1009
+ content: {
1010
+ is_error: true,
1011
+ error: item.error?.message || "MCP tool call failed"
1012
+ }
1013
+ };
1014
+ }
1015
+ const textParts = item.result?.content?.map((block) => {
1016
+ if (block.type === "text") {
1017
+ return block.text;
1018
+ }
1019
+ return JSON.stringify(block);
1020
+ }).filter((value) => typeof value === "string" && value.length > 0);
1021
+ const combinedText = textParts?.join("\n").trim();
1022
+ return {
1023
+ isError: false,
1024
+ content: combinedText || (item.result?.structured_content !== void 0 ? JSON.stringify(item.result.structured_content) : `${item.server}:${item.tool} completed successfully.`)
1025
+ };
1026
+ }
1027
+ function normalizeWebSearch(item) {
1028
+ return {
1029
+ name: "WebSearch",
1030
+ input: { query: item.query }
1031
+ };
1032
+ }
1033
+ function shouldTreatAsToolItem(item) {
1034
+ return item.type === "command_execution" || item.type === "file_change" || item.type === "mcp_tool_call" || item.type === "web_search";
1035
+ }
1036
+ function extractInnerCommand(command) {
1037
+ const trimmed = command.trim();
1038
+ const zshMatch = trimmed.match(/-lc\s+([\s\S]+)$/);
1039
+ const shellCommand = zshMatch ? stripQuotes(zshMatch[1].trim()) : trimmed;
1040
+ return stripLeadingCdPrefix(shellCommand);
1041
+ }
1042
+ function stripLeadingCdPrefix(command) {
1043
+ const match = command.match(/^cd\s+(.+?)\s*&&\s*([\s\S]+)$/);
1044
+ if (!match) {
1045
+ return command;
1046
+ }
1047
+ return match[2].trim();
1048
+ }
1049
+ function parseSimpleShellWrite(command) {
1050
+ return parseHereDocShellWrite(command) ?? parseRedirectShellWrite(command);
1051
+ }
1052
+ function parseHereDocShellWrite(command) {
1053
+ const redirectFirst = command.match(
1054
+ /^cat\s*>\s*("(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|[^\s;&|]+)\s*<<['"]?([A-Za-z0-9_-]+)['"]?\n([\s\S]*?)\n\2$/
1055
+ );
1056
+ if (redirectFirst) {
1057
+ const [, rawFilePath, , body] = redirectFirst;
1058
+ return {
1059
+ filePath: stripQuotes(rawFilePath.trim()),
1060
+ content: `${body}
1061
+ `
1062
+ };
1063
+ }
1064
+ const heredocFirst = command.match(
1065
+ /^cat\s*<<['"]?([A-Za-z0-9_-]+)['"]?\s*>\s*("(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|[^\s;&|]+)\n([\s\S]*?)\n\1$/
1066
+ );
1067
+ if (heredocFirst) {
1068
+ const [, , rawFilePath, body] = heredocFirst;
1069
+ return {
1070
+ filePath: stripQuotes(rawFilePath.trim()),
1071
+ content: `${body}
1072
+ `
1073
+ };
1074
+ }
1075
+ return null;
1076
+ }
1077
+ function parseRedirectShellWrite(command) {
1078
+ const match = command.match(
1079
+ /^(printf|echo)(?:\s+(-n))?\s+([\s\S]+?)\s*>\s*("(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|[^\s;&|]+)$/
1080
+ );
1081
+ if (!match) {
1082
+ return null;
1083
+ }
1084
+ const [, commandName, noNewlineFlag, rawContent, rawFilePath] = match;
1085
+ const parsedContent = parseSimpleShellTextLiteral(rawContent.trim());
1086
+ if (parsedContent === void 0) {
1087
+ return null;
1088
+ }
1089
+ const content = commandName === "printf" ? decodePrintfEscapes(parsedContent) : noNewlineFlag === "-n" ? parsedContent : `${parsedContent}
1090
+ `;
1091
+ return {
1092
+ filePath: stripQuotes(rawFilePath.trim()),
1093
+ content
1094
+ };
1095
+ }
1096
+ function parseSimpleShellTextLiteral(value) {
1097
+ const trimmed = value.trim();
1098
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
1099
+ return stripQuotes(trimmed);
1100
+ }
1101
+ if (/^[^\s;&|<>]+$/.test(trimmed)) {
1102
+ return trimmed;
1103
+ }
1104
+ return void 0;
1105
+ }
1106
+ function decodePrintfEscapes(value) {
1107
+ return value.replace(/\\\\/g, "\\").replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ");
1108
+ }
1109
+ function stripQuotes(value) {
1110
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1111
+ return value.slice(1, -1);
1112
+ }
1113
+ return value;
1114
+ }
1115
+
1116
+ // src/timeout-state.ts
1117
+ function shouldTreatTurnStartAsBusy(idleTimeoutMs) {
1118
+ return idleTimeoutMs > 1e3;
1119
+ }
1120
+ function updateCodexTimeoutState(event, controller, idleTimeoutMs) {
1121
+ const turnBusy = shouldTreatTurnStartAsBusy(idleTimeoutMs);
1122
+ switch (event.type) {
1123
+ case "turn.started": {
1124
+ if (turnBusy) {
1125
+ controller.markBusy();
1126
+ }
1127
+ return;
1128
+ }
1129
+ case "item.started":
1130
+ case "item.updated":
1131
+ case "item.completed": {
1132
+ if (turnBusy || shouldTreatAsToolItem(event.item)) {
1133
+ controller.markBusy();
1134
+ }
1135
+ return;
1136
+ }
1137
+ case "turn.completed":
1138
+ case "turn.failed": {
1139
+ controller.markIdle();
1140
+ return;
1141
+ }
1142
+ default:
1143
+ return;
887
1144
  }
888
- return normalized;
889
1145
  }
890
1146
 
1147
+ // package.json
1148
+ var package_default = {
1149
+ name: "codex-shim",
1150
+ version: "0.1.0",
1151
+ private: true,
1152
+ description: "Self-contained Hankweave shim for OpenAI Codex via @openai/codex-sdk",
1153
+ type: "module",
1154
+ bin: {
1155
+ "codex-shim": "./index.js"
1156
+ },
1157
+ files: [
1158
+ "index.js",
1159
+ "dist",
1160
+ "src",
1161
+ "tests",
1162
+ "common",
1163
+ "docs",
1164
+ "README.md",
1165
+ "rebuild.sh",
1166
+ "VERSION",
1167
+ "THIRDPARTY.md",
1168
+ "tsconfig.json",
1169
+ "tsup.config.ts",
1170
+ "bun.lock"
1171
+ ],
1172
+ engines: {
1173
+ node: ">=18",
1174
+ bun: ">=1.1.0"
1175
+ },
1176
+ scripts: {
1177
+ build: "tsup",
1178
+ rebuild: "./rebuild.sh",
1179
+ test: "bun test",
1180
+ typecheck: "tsc --noEmit -p tsconfig.json",
1181
+ clean: `node -e "const fs=require('fs'); fs.rmSync('dist',{recursive:true,force:true}); fs.rmSync('index.js',{force:true});"`
1182
+ },
1183
+ dependencies: {
1184
+ "@openai/codex-sdk": "^0.112.0",
1185
+ "@shims/common": "file:./common"
1186
+ },
1187
+ devDependencies: {
1188
+ "@types/bun": "latest",
1189
+ "@types/node": "^20.10.0",
1190
+ tsup: "^8.0.1",
1191
+ typescript: "^5.3.0"
1192
+ }
1193
+ };
1194
+
891
1195
  // src/shim.ts
1196
+ var DEFAULT_TOOLS = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "LS", "WebSearch"];
1197
+ var HELP_TEXT = `codex-shim
1198
+
1199
+ Usage:
1200
+ codex-shim --model <model> [options]
1201
+
1202
+ Options:
1203
+ -p
1204
+ --model <model>
1205
+ --resume <session_id>
1206
+ --verbose
1207
+ --append-system-prompt <text>
1208
+ --idle-timeout <seconds>
1209
+ --debug-dir <path>
1210
+ --sandbox <none|standard|strict>
1211
+ --self-test
1212
+ --version
1213
+ --help
1214
+ `;
1215
+ function emit(message) {
1216
+ process.stdout.write(`${JSON.stringify(message)}
1217
+ `);
1218
+ }
1219
+ function usageToTokenUsage(usage) {
1220
+ if (!usage) {
1221
+ return void 0;
1222
+ }
1223
+ return {
1224
+ input_tokens: usage.input_tokens,
1225
+ output_tokens: usage.output_tokens,
1226
+ cache_read_input_tokens: usage.cached_input_tokens
1227
+ };
1228
+ }
1229
+ function createSystemMessage(cwd, sessionId, model, apiKeySource) {
1230
+ return {
1231
+ type: "system",
1232
+ subtype: "init",
1233
+ cwd,
1234
+ session_id: sessionId,
1235
+ tools: DEFAULT_TOOLS,
1236
+ model,
1237
+ permissionMode: "bypassPermissions",
1238
+ apiKeySource,
1239
+ mcp_servers: []
1240
+ };
1241
+ }
1242
+ function createAssistantMessage(model, content, stopReason = null, usage, id = generateMessageId()) {
1243
+ return {
1244
+ type: "assistant",
1245
+ message: {
1246
+ id,
1247
+ type: "message",
1248
+ role: "assistant",
1249
+ model,
1250
+ content,
1251
+ stop_reason: stopReason,
1252
+ ...usage ? { usage } : {}
1253
+ }
1254
+ };
1255
+ }
1256
+ function createToolResultMessage(toolUseId, content) {
1257
+ return {
1258
+ type: "user",
1259
+ message: {
1260
+ role: "user",
1261
+ content: [
1262
+ {
1263
+ type: "tool_result",
1264
+ tool_use_id: toolUseId,
1265
+ content
1266
+ }
1267
+ ]
1268
+ }
1269
+ };
1270
+ }
1271
+ function createResultMessage(ok, durationMs, durationApiMs, numTurns, summary, sessionId, usage) {
1272
+ return {
1273
+ type: "result",
1274
+ subtype: ok ? "success" : "error",
1275
+ is_error: !ok,
1276
+ duration_ms: durationMs,
1277
+ duration_api_ms: durationApiMs,
1278
+ num_turns: numTurns,
1279
+ result: summary,
1280
+ session_id: sessionId,
1281
+ ...usage ? { usage } : {}
1282
+ };
1283
+ }
1284
+ function composePrompt(prompt, appendSystemPrompt) {
1285
+ if (!appendSystemPrompt?.trim()) {
1286
+ return prompt;
1287
+ }
1288
+ return [
1289
+ "<SYSTEM_INSTRUCTIONS>",
1290
+ appendSystemPrompt.trim(),
1291
+ "</SYSTEM_INSTRUCTIONS>",
1292
+ "",
1293
+ "<USER_REQUEST>",
1294
+ prompt,
1295
+ "</USER_REQUEST>"
1296
+ ].join("\n");
1297
+ }
1298
+ function classifyRuntimeError(error) {
1299
+ if (error instanceof IdleTimeoutError || error instanceof BusyStepTimeoutError) {
1300
+ return `Agent Error: ${error.message}`;
1301
+ }
1302
+ const message = toErrorMessage(error);
1303
+ if (/rate limit|authentication|not supported|invalid model|insufficient|quota|api|unsupported/i.test(
1304
+ message
1305
+ )) {
1306
+ return `API Error: ${message}`;
1307
+ }
1308
+ return `Agent Error: ${message}`;
1309
+ }
1310
+ async function readStdin() {
1311
+ const chunks = [];
1312
+ for await (const chunk of process.stdin) {
1313
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1314
+ }
1315
+ return Buffer.concat(chunks).toString("utf8").trim();
1316
+ }
1317
+ function writeStartupError(message, debugDir) {
1318
+ DebugRecorder.logStartupError(debugDir, message);
1319
+ process.stderr.write(`${message}
1320
+ `);
1321
+ process.exit(1);
1322
+ }
1323
+ function findVendoredCodexExe(npmPrefix) {
1324
+ const arch = process.arch === "arm64" ? "aarch64-pc-windows-msvc" : "x86_64-pc-windows-msvc";
1325
+ const pkgName = process.arch === "arm64" ? "@openai/codex-win32-arm64" : "@openai/codex-win32-x64";
1326
+ const tail = path6.join(...pkgName.split("/"), "vendor", arch, "codex", "codex.exe");
1327
+ const candidates = [
1328
+ path6.join(npmPrefix, "node_modules", tail),
1329
+ path6.join(npmPrefix, "node_modules", "@openai", "codex", "node_modules", tail)
1330
+ ];
1331
+ for (const candidate of candidates) {
1332
+ if (fs4.existsSync(candidate)) return candidate;
1333
+ }
1334
+ return null;
1335
+ }
1336
+ async function resolveCodexPath(command) {
1337
+ const isWindows = process.platform === "win32";
1338
+ if (path6.isAbsolute(command) || command.includes(path6.sep)) {
1339
+ if (fs4.existsSync(command)) return command;
1340
+ if (isWindows) {
1341
+ for (const ext of [".cmd", ".exe"]) {
1342
+ if (fs4.existsSync(command + ext)) return command + ext;
1343
+ }
1344
+ }
1345
+ return null;
1346
+ }
1347
+ const whichCommand = isWindows ? "where" : "which";
1348
+ const resolved = await new Promise((resolve) => {
1349
+ const proc = spawn2(whichCommand, [command], {
1350
+ shell: isWindows,
1351
+ stdio: ["ignore", "pipe", "ignore"],
1352
+ env: process.env
1353
+ });
1354
+ let stdout = "";
1355
+ proc.stdout?.on("data", (chunk) => {
1356
+ stdout += chunk.toString();
1357
+ });
1358
+ proc.on("error", () => resolve(null));
1359
+ proc.on("close", (code) => {
1360
+ if (code === 0 && stdout.trim()) {
1361
+ resolve(stdout.trim().split(/\r?\n/)[0]);
1362
+ } else {
1363
+ resolve(null);
1364
+ }
1365
+ });
1366
+ });
1367
+ if (!resolved) return null;
1368
+ if (isWindows) {
1369
+ const npmPrefix = path6.dirname(resolved);
1370
+ const vendored = findVendoredCodexExe(npmPrefix);
1371
+ if (vendored) return vendored;
1372
+ }
1373
+ return resolved;
1374
+ }
1375
+ async function resolveAgentVersion(codexPath) {
1376
+ const isWindows = process.platform === "win32";
1377
+ return await new Promise((resolve) => {
1378
+ const proc = spawn2(codexPath, ["--version"], {
1379
+ shell: isWindows,
1380
+ stdio: ["ignore", "pipe", "pipe"],
1381
+ env: process.env
1382
+ });
1383
+ let stdout = "";
1384
+ let stderr = "";
1385
+ proc.stdout?.on("data", (chunk) => {
1386
+ stdout += chunk.toString();
1387
+ });
1388
+ proc.stderr?.on("data", (chunk) => {
1389
+ stderr += chunk.toString();
1390
+ });
1391
+ proc.on("error", () => resolve("unknown"));
1392
+ proc.on("close", (code) => {
1393
+ if (code === 0) {
1394
+ resolve(stdout.trim() || stderr.trim() || package_default.dependencies["@openai/codex-sdk"] || "unknown");
1395
+ } else {
1396
+ resolve("unknown");
1397
+ }
1398
+ });
1399
+ });
1400
+ }
892
1401
  var CodexShim = class {
893
1402
  args;
894
- codex;
1403
+ prompt;
895
1404
  cwd;
896
- startTime;
897
- apiStartTime;
898
- interrupted = false;
1405
+ debug;
1406
+ model;
899
1407
  sessionManager;
900
- authConfig;
901
- constructor(args) {
1408
+ runtime = {
1409
+ numTurns: 0,
1410
+ finalText: ""
1411
+ };
1412
+ toolIdMap = /* @__PURE__ */ new Map();
1413
+ codex;
1414
+ sessionId;
1415
+ interrupted = false;
1416
+ currentAbortController;
1417
+ constructor(args, prompt) {
902
1418
  this.args = args;
903
- this.cwd = process.cwd();
904
- this.startTime = Date.now();
905
- this.apiStartTime = 0;
1419
+ this.prompt = prompt;
1420
+ this.cwd = makeAbsoluteCwd(process.cwd());
1421
+ this.debug = new DebugRecorder(args.debugDir);
1422
+ this.model = resolveModel(this.args.model);
906
1423
  this.sessionManager = new SessionManager({ debugDir: args.debugDir });
907
- this.authConfig = getApiKey();
1424
+ this.sessionId = args.resume || generateSessionId();
1425
+ }
1426
+ get resolvedApiKey() {
1427
+ return process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || void 0;
1428
+ }
1429
+ get apiKeySource() {
1430
+ if (process.env.OPENAI_API_KEY) return "OPENAI_API_KEY";
1431
+ if (process.env.CODEX_API_KEY) return "CODEX_API_KEY";
1432
+ if (fs4.existsSync(path6.join(os2.homedir(), ".codex", "auth.json"))) return "~/.codex/auth.json";
1433
+ return "none";
1434
+ }
1435
+ get isAuthConfigured() {
1436
+ return this.apiKeySource !== "none";
1437
+ }
1438
+ async runSelfTest() {
1439
+ const override = getCodexPathOverride() || "codex";
1440
+ const resolvedPath = await resolveCodexPath(override);
1441
+ const agentFound = resolvedPath !== null;
1442
+ const agentVersion = resolvedPath ? await resolveAgentVersion(resolvedPath) : "unknown";
1443
+ const checks = [
1444
+ {
1445
+ name: "agent_found",
1446
+ passed: agentFound,
1447
+ message: agentFound ? `Found codex at ${resolvedPath}` : `Could not find codex via ${override}`
1448
+ },
1449
+ {
1450
+ name: "api_key",
1451
+ passed: this.isAuthConfigured,
1452
+ message: this.isAuthConfigured ? `Authentication source available: ${this.apiKeySource}` : "No OPENAI_API_KEY, CODEX_API_KEY, or ~/.codex/auth.json found"
1453
+ }
1454
+ ];
1455
+ const overallPassed = checks.every((check) => check.passed);
1456
+ process.stdout.write(
1457
+ `${JSON.stringify(
1458
+ {
1459
+ shim: { name: "codex-shim", version: package_default.version },
1460
+ agent: { name: "codex", version: agentVersion, found: agentFound },
1461
+ checks,
1462
+ overall: {
1463
+ passed: overallPassed,
1464
+ message: overallPassed ? "All checks passed" : "One or more checks failed"
1465
+ }
1466
+ },
1467
+ null,
1468
+ 2
1469
+ )}
1470
+ `
1471
+ );
1472
+ return overallPassed ? 0 : 1;
1473
+ }
1474
+ async run() {
1475
+ const codexPath = await resolveCodexPath(getCodexPathOverride() || "codex");
1476
+ if (!codexPath) {
1477
+ writeStartupError("Agent not found: could not locate codex via CODEX_PATH_OVERRIDE or PATH", this.args.debugDir);
1478
+ }
1479
+ if (!this.isAuthConfigured) {
1480
+ writeStartupError("Missing API key: set OPENAI_API_KEY, CODEX_API_KEY, or ~/.codex/auth.json", this.args.debugDir);
1481
+ }
908
1482
  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"
1483
+ apiKey: this.resolvedApiKey,
1484
+ codexPathOverride: codexPath,
1485
+ env: Object.fromEntries(
1486
+ Object.entries(process.env).filter((entry) => entry[1] !== void 0)
1487
+ )
913
1488
  });
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
1489
  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());
1490
+ if (this.args.resume) {
1491
+ if (!isValidSessionId(this.args.resume)) {
1492
+ writeStartupError(`Invalid session ID: ${this.args.resume}`, this.args.debugDir);
968
1493
  }
969
- } catch (error) {
970
- verboseLog(this.args.verbose, "Pre-init error:", error);
971
- if (this.args.debugDir) {
972
- this.savePreInitError(error);
1494
+ let sessionData;
1495
+ try {
1496
+ sessionData = this.sessionManager.loadSession(this.args.resume);
1497
+ } catch (error) {
1498
+ writeStartupError(toErrorMessage(error), this.args.debugDir);
973
1499
  }
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
- };
1500
+ this.logVerbose(`Resuming session ${this.args.resume} -> ${sessionData.agentSessionId}`);
1501
+ thread = this.codex.resumeThread(sessionData.agentSessionId, this.getThreadOptions());
1502
+ this.sessionId = this.args.resume;
1503
+ } else {
1504
+ thread = this.codex.startThread(this.getThreadOptions());
1505
+ }
1506
+ const systemMessage = createSystemMessage(this.cwd, this.sessionId, this.model.publicModel, this.apiKeySource);
1507
+ emit(systemMessage);
1508
+ this.debug.setSession(this.sessionId, { cwd: this.cwd, model: this.model.publicModel });
1509
+ const startedAt = Date.now();
1510
+ const apiStartedAt = Date.now();
1511
+ this.installSignalHandlers();
996
1512
  try {
997
- await this.processPrompt(state, prompt, modelOutput);
998
- this.emitResult(state, false, "Completed successfully");
1513
+ const prompt = composePrompt(this.prompt, this.args.appendSystemPrompt);
1514
+ const stream = await thread.runStreamed(prompt, { signal: this.getAbortController().signal });
1515
+ await this.consumeEvents(stream.events);
1516
+ const durationMs = Date.now() - startedAt;
1517
+ const durationApiMs = Date.now() - apiStartedAt;
1518
+ const resultMessage = createResultMessage(
1519
+ true,
1520
+ durationMs,
1521
+ durationApiMs,
1522
+ Math.max(this.runtime.numTurns, 1),
1523
+ this.runtime.finalText || "Completed successfully.",
1524
+ this.sessionId,
1525
+ this.runtime.lastUsage
1526
+ );
1527
+ emit(resultMessage);
1528
+ this.debug.logResult("success", {
1529
+ session_id: this.sessionId,
1530
+ usage: this.runtime.lastUsage
1531
+ });
1532
+ await flushStdout();
1533
+ return 0;
999
1534
  } 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));
1535
+ const summary = classifyRuntimeError(error);
1536
+ const durationMs = Date.now() - startedAt;
1537
+ const durationApiMs = Date.now() - apiStartedAt;
1538
+ emit(
1539
+ createAssistantMessage(
1540
+ "<synthetic>",
1541
+ [{ type: "text", text: summary }],
1542
+ null,
1543
+ void 0,
1544
+ NIL_UUID
1545
+ )
1546
+ );
1547
+ const resultMessage = createResultMessage(
1548
+ false,
1549
+ durationMs,
1550
+ durationApiMs,
1551
+ Math.max(this.runtime.numTurns, 1),
1552
+ summary,
1553
+ this.sessionId,
1554
+ this.runtime.lastUsage
1555
+ );
1556
+ emit(resultMessage);
1557
+ this.debug.logLine(summary);
1558
+ this.debug.logResult("error", {
1559
+ session_id: this.sessionId,
1560
+ error: summary
1561
+ });
1006
1562
  await flushStdout();
1007
- process.exit(1);
1563
+ return 1;
1008
1564
  }
1009
- await flushStdout();
1010
- process.exit(0);
1011
1565
  }
1012
1566
  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 = {
1567
+ return {
1568
+ model: this.model.sdkModel,
1569
+ modelReasoningEffort: this.model.reasoningEffort,
1570
+ sandboxMode: mapSandbox(this.args.sandbox),
1020
1571
  workingDirectory: this.cwd,
1021
1572
  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"
1573
+ approvalPolicy: "never"
1029
1574
  };
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
1575
  }
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;
1576
+ async consumeEvents(events) {
1577
+ const idleTimeoutMs = this.args.idleTimeout * 1e3;
1578
+ const timedEvents = withAdaptiveTimeout(events, {
1579
+ idleTimeoutMs,
1580
+ busyTimeoutMs: Math.max(idleTimeoutMs, 3e5),
1581
+ onEvent: (event, controller) => {
1582
+ updateCodexTimeoutState(event, controller, idleTimeoutMs);
1583
+ }
1584
+ });
1073
1585
  for await (const event of timedEvents) {
1074
- if (this.interrupted) {
1075
- verboseLog(this.args.verbose, "Interrupted, stopping event processing");
1586
+ this.debug.logSdkEvent(event);
1587
+ this.logVerbose(`SDK event: ${event.type}`);
1588
+ await this.handleEvent(event);
1589
+ }
1590
+ }
1591
+ async handleEvent(event) {
1592
+ switch (event.type) {
1593
+ case "thread.started": {
1594
+ this.sessionManager.saveSession({
1595
+ sessionId: this.sessionId,
1596
+ agentSessionId: event.thread_id,
1597
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1598
+ metadata: {
1599
+ model: this.model.publicModel,
1600
+ cwd: this.cwd
1601
+ }
1602
+ });
1076
1603
  break;
1077
1604
  }
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
- }
1605
+ case "turn.started": {
1606
+ break;
1084
1607
  }
1085
- if (!state.workStarted) {
1086
- if (event.type === "item.started" || event.type === "item.updated" || event.type === "turn.started") {
1087
- state.workStarted = true;
1088
- }
1608
+ case "turn.completed": {
1609
+ this.runtime.numTurns += 1;
1610
+ this.runtime.lastUsage = usageToTokenUsage(event.usage);
1611
+ break;
1089
1612
  }
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;
1613
+ case "turn.failed": {
1614
+ this.runtime.numTurns += 1;
1615
+ throw new Error(event.error.message);
1163
1616
  }
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
- }
1617
+ case "error": {
1618
+ throw new Error(event.message);
1619
+ }
1620
+ case "item.started": {
1621
+ if (event.item.type !== "web_search") {
1622
+ await this.emitToolUseIfNeeded(event.item);
1195
1623
  }
1196
1624
  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 });
1625
+ }
1626
+ case "item.updated": {
1627
+ if (event.item.type !== "web_search") {
1628
+ await this.emitToolUseIfNeeded(event.item);
1214
1629
  }
1215
1630
  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
- );
1631
+ }
1632
+ case "item.completed": {
1633
+ await this.handleCompletedItem(event.item);
1235
1634
  break;
1236
- case "file_change":
1237
- this.handleFileChange(
1238
- item,
1239
- state,
1240
- currentMessageContent,
1241
- pendingToolResults,
1242
- model,
1243
- currentMessageId
1244
- );
1635
+ }
1636
+ default:
1245
1637
  break;
1246
1638
  }
1247
1639
  }
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: [
1640
+ async handleCompletedItem(item) {
1641
+ if (item.type === "reasoning") {
1642
+ emit(
1643
+ createAssistantMessage(this.model.publicModel, [
1644
+ {
1645
+ type: "thinking",
1646
+ thinking: item.text
1647
+ }
1648
+ ])
1649
+ );
1650
+ return;
1651
+ }
1652
+ if (item.type === "agent_message") {
1653
+ this.runtime.finalText = item.text;
1654
+ emit(
1655
+ createAssistantMessage(this.model.publicModel, [
1376
1656
  {
1377
1657
  type: "text",
1378
- text: `API Error: ${errorMessage}`
1658
+ text: item.text
1379
1659
  }
1380
- ],
1381
- stop_reason: null
1660
+ ], "end_turn")
1661
+ );
1662
+ return;
1663
+ }
1664
+ if (item.type === "error") {
1665
+ this.logVerbose(`Codex non-fatal error item: ${item.message}`);
1666
+ return;
1667
+ }
1668
+ if (shouldTreatAsToolItem(item)) {
1669
+ const toolId = await this.emitToolUseIfNeeded(item);
1670
+ const normalizedResult = normalizeToolResult(item, this.cwd);
1671
+ if (!toolId || !normalizedResult) {
1672
+ return;
1382
1673
  }
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}`);
1674
+ emit(createToolResultMessage(toolId, normalizedResult.content));
1419
1675
  }
1420
1676
  }
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
- }
1677
+ async emitToolUseIfNeeded(item) {
1678
+ if (!shouldTreatAsToolItem(item)) {
1679
+ return void 0;
1680
+ }
1681
+ const existing = this.toolIdMap.get(item.id);
1682
+ if (existing) {
1683
+ return existing;
1684
+ }
1685
+ const normalizedTool = normalizeToolUse(item, this.cwd);
1686
+ if (!normalizedTool) {
1687
+ return void 0;
1688
+ }
1689
+ const publicId = generateToolUseId();
1690
+ this.toolIdMap.set(item.id, publicId);
1691
+ emit(
1692
+ createAssistantMessage(this.model.publicModel, [
1693
+ {
1694
+ type: "tool_use",
1695
+ id: publicId,
1696
+ name: normalizedTool.name,
1697
+ ...normalizedTool.input ? { input: normalizedTool.input } : {}
1698
+ }
1699
+ ], "tool_use")
1700
+ );
1701
+ return publicId;
1432
1702
  }
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}`);
1703
+ getAbortController() {
1704
+ if (!this.currentAbortController) {
1705
+ this.currentAbortController = new AbortController();
1444
1706
  }
1707
+ return this.currentAbortController;
1445
1708
  }
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
- `;
1709
+ installSignalHandlers() {
1710
+ const handle = (signal) => {
1711
+ if (this.interrupted) {
1712
+ return;
1460
1713
  }
1461
- fs4.appendFileSync(errorLogPath, logEntry);
1462
- } catch (err) {
1463
- verboseLog(this.args.verbose, `Failed to save runtime error: ${err}`);
1464
- }
1714
+ this.interrupted = true;
1715
+ this.logVerbose(`Received ${signal}, aborting current turn`);
1716
+ this.debug.logLine(`Received ${signal}, aborting current turn`);
1717
+ this.currentAbortController?.abort();
1718
+ };
1719
+ process.once("SIGINT", () => handle("SIGINT"));
1720
+ process.once("SIGTERM", () => handle("SIGTERM"));
1465
1721
  }
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}`);
1722
+ logVerbose(message) {
1723
+ if (!this.args.verbose) {
1724
+ return;
1480
1725
  }
1726
+ process.stderr.write(`${message}
1727
+ `);
1728
+ this.debug.logLine(message);
1481
1729
  }
1482
1730
  };
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>
1731
+ async function main(argv = process.argv.slice(2)) {
1732
+ const args = parseArgs(argv, { "-h": "--help" });
1733
+ if (args.help) {
1734
+ process.stdout.write(HELP_TEXT);
1735
+ return 0;
1736
+ }
1737
+ if (args.version) {
1738
+ process.stdout.write(`${package_default.version}
1537
1739
  `);
1740
+ return 0;
1741
+ }
1742
+ if (args.selfTest) {
1743
+ return await new CodexShim(args, "").runSelfTest();
1744
+ }
1745
+ const prompt = await readStdin();
1746
+ if (!prompt) {
1747
+ return 0;
1748
+ }
1749
+ const shim = new CodexShim(args, prompt);
1750
+ return await shim.run();
1538
1751
  }
1539
-
1540
- // src/index.ts
1541
- async function main() {
1752
+ async function runCli() {
1542
1753
  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);
1754
+ const exitCode = await main();
1755
+ await flushStdout();
1756
+ process.exit(exitCode);
1577
1757
  } catch (error) {
1578
- const errorMessage = error instanceof Error ? error.message : String(error);
1579
- console.error(`Error: ${errorMessage}`);
1758
+ const message = toErrorMessage(error);
1759
+ process.stderr.write(`${message}
1760
+ `);
1761
+ await flushStdout();
1580
1762
  process.exit(1);
1581
1763
  }
1582
1764
  }
1583
- main();
1765
+
1766
+ // src/index.ts
1767
+ await runCli();