@codex-native/sdk 0.0.1 → 0.0.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 CHANGED
@@ -12,6 +12,24 @@ npm install @codex-native/sdk
12
12
 
13
13
  Requires Node.js 18+.
14
14
 
15
+ ## API Compatibility
16
+
17
+ The Native SDK provides **full API compatibility** with the [TypeScript SDK](../typescript/). All core functionality—threads, streaming, structured output, and basic operations—works identically across both SDKs. The Native SDK adds Rust-powered performance and custom tool registration while maintaining the same interface.
18
+
19
+ ### Migration from TypeScript SDK
20
+
21
+ Simply replace the import:
22
+
23
+ ```typescript
24
+ // Before (TypeScript SDK)
25
+ import { Codex } from "@openai/codex-sdk";
26
+
27
+ // After (Native SDK - same API!)
28
+ import { Codex } from "@codex-native/sdk";
29
+ ```
30
+
31
+ All your existing code continues to work without changes.
32
+
15
33
  ## Quickstart
16
34
 
17
35
  ```typescript
@@ -50,9 +68,32 @@ for await (const event of events) {
50
68
  }
51
69
  ```
52
70
 
71
+ ### Mid-turn notifications
72
+
73
+ You can publish lightweight updates during an active turn without adding another user
74
+ message. Call `thread.sendBackgroundEvent()` inside a `runStreamed()` loop after the
75
+ turn has started:
76
+
77
+ ```typescript
78
+ const { events } = await thread.runStreamed("Generate the release notes");
79
+
80
+ for await (const event of events) {
81
+ if (event.type === "turn.started") {
82
+ await thread.sendBackgroundEvent("Gathering changelog entries…");
83
+ } else if (event.type === "background_event") {
84
+ console.log(event.message);
85
+ }
86
+ }
87
+ ```
88
+
89
+ The streaming API surfaces these as `background_event` items so downstream consumers
90
+ can display progress indicators or status notifications while the agent continues its
91
+ turn.
92
+
53
93
  ### Structured output
54
94
 
55
- The Codex agent can produce a JSON response that conforms to a specified schema. The schema can be provided for each turn as a plain JSON object.
95
+ The Codex agent can produce a JSON response that conforms to a specified schema. The schema
96
+ can be provided for each turn as a plain JSON object.
56
97
 
57
98
  ```typescript
58
99
  const schema = {
@@ -69,7 +110,9 @@ const turn = await thread.run("Summarize repository status", { outputSchema: sch
69
110
  console.log(turn.finalResponse);
70
111
  ```
71
112
 
72
- You can also create a JSON schema from a [Zod schema](https://github.com/colinhacks/zod) using the [`zod-to-json-schema`](https://www.npmjs.com/package/zod-to-json-schema) package and setting the `target` to `"openAi"`.
113
+ You can also create a JSON schema from a [Zod schema](https://github.com/colinhacks/zod)
114
+ using the [`zod-to-json-schema`](https://www.npmjs.com/package/zod-to-json-schema)
115
+ package and setting the `target` to "openAi".
73
116
 
74
117
  ```typescript
75
118
  const schema = z.object({
@@ -83,6 +126,75 @@ const turn = await thread.run("Summarize repository status", {
83
126
  console.log(turn.finalResponse);
84
127
  ```
85
128
 
129
+ ## Command-line interface
130
+
131
+ In addition to the programmatic API, the package ships a `codex-native` CLI that mirrors the
132
+ Rust `codex` binary. The CLI is available after `pnpm install` (or via `npx codex-native`).
133
+
134
+ ```bash
135
+ # Run a one-off Codex turn using the native SDK
136
+ codex-native run "Diagnose the failing integration test"
137
+
138
+ # Launch the full-screen TUI backed by the native bindings
139
+ codex-native tui
140
+ ```
141
+
142
+ ### Configuration discovery
143
+
144
+ `codex-native` automatically merges configuration from the following locations (in priority order):
145
+
146
+ 1. The CLI flags supplied on the command line
147
+ 2. A `codex.config.*` file in the working directory (`.js`, `.cjs`, `.mjs`, `.ts`)
148
+ 3. A `codexNative` field in `package.json`
149
+
150
+ Configuration files can export either an object or a function. The function form receives a
151
+ context `{ cwd, configPath }` so you can derive settings dynamically.
152
+
153
+ ```typescript
154
+ // codex.config.ts
155
+ import type { CodexNativeConfig } from "@codex-native/sdk/cli";
156
+
157
+ const config: CodexNativeConfig = {
158
+ defaults: {
159
+ run: { model: "gpt-5-codex" },
160
+ tui: { resumePicker: false },
161
+ },
162
+ tools: [
163
+ {
164
+ name: "read_me",
165
+ description: "Show the project README",
166
+ handler: async () => ({ output: await fs.promises.readFile("README.md", "utf8") }),
167
+ },
168
+ ],
169
+ interceptors: [
170
+ {
171
+ toolName: "shell",
172
+ handler: async ({ invocation, callBuiltin }) => {
173
+ console.log("shell call", invocation);
174
+ return callBuiltin();
175
+ },
176
+ },
177
+ ],
178
+ };
179
+
180
+ export default config;
181
+ ```
182
+
183
+ Multiple `--plugin` flags can be passed to load additional modules. A plugin may export either a
184
+ config object or a function that returns one. Plugin configs are merged on top of the base config.
185
+
186
+ ### Hooks and approvals
187
+
188
+ Config files can specify hooks that run before every turn (`beforeStart`) and when streaming
189
+ events (`onEvent`). You can also register an `approvals` callback to gate sensitive tool usage.
190
+ These map directly to the native bindings so the same logic applies in both CLI and SDK usage.
191
+
192
+ ### TUI mode
193
+
194
+ `codex-native tui` launches the same Ratatui-based interface used by the Rust `codex` tool. The
195
+ CLI honors the same configuration sources listed above, so you can keep CLI, SDK, and TUI
196
+ settings in a single `codex.config.ts` file.
197
+
86
198
  ### Attaching images
87
199
 
88
200
  Provide structured input entries when you need to include images alongside text. Text entries are concatenated into the final prompt while image entries are passed to Codex via the native bridge.
@@ -105,9 +217,80 @@ const thread = codex.resumeThread(savedThreadId);
105
217
  await thread.run("Implement the fix");
106
218
  ```
107
219
 
220
+ ### Forking a conversation
221
+
222
+ Use `thread.fork()` to branch from an earlier user message and explore an alternate path without losing the original history. Provide the zero-based index of the user message you want to fork **before**.
223
+
224
+ ```typescript
225
+ const codex = new Codex();
226
+ const thread = codex.startThread({ skipGitRepoCheck: true });
227
+
228
+ await thread.run("List flaky integration tests");
229
+ await thread.run("Propose fixes for each flaky test");
230
+
231
+ // Fork before the second user message (index 1)
232
+ const branch = await thread.fork({
233
+ nthUserMessage: 1,
234
+ threadOptions: { model: "gpt-5-codex-mini" },
235
+ });
236
+
237
+ await branch.run("Focus on the payment suite instead");
238
+ ```
239
+
240
+ The original `thread` continues unchanged while `branch` contains the forked history and a fresh thread id.
241
+
242
+ ### Running code reviews
243
+
244
+ Invoke the native review workflow without crafting prompts manually. The SDK provides presets that mirror the `/review` slash command:
245
+
246
+ ```typescript
247
+ const codex = new Codex();
248
+
249
+ // Review everything that is staged, unstaged, or untracked
250
+ const review = await codex.review({
251
+ target: { type: "current_changes" },
252
+ });
253
+
254
+ for (const finding of review.items) {
255
+ if (finding.type === "agent_message") {
256
+ console.log(finding.text);
257
+ }
258
+ }
259
+ ```
260
+
261
+ Additional presets let you review against another branch or a specific commit:
262
+
263
+ ```typescript
264
+ await codex.review({
265
+ target: { type: "branch", baseBranch: "main" },
266
+ });
267
+
268
+ await codex.review({
269
+ target: {
270
+ type: "commit",
271
+ sha: "abc1234def5678",
272
+ subject: "Tighten input validation",
273
+ },
274
+ });
275
+ ```
276
+
277
+ For bespoke instructions, pass a custom prompt and optional hint:
278
+
279
+ ```typescript
280
+ await codex.review({
281
+ target: {
282
+ type: "custom",
283
+ prompt: "Review only the data-access layer for regression risks.",
284
+ hint: "data-access layer",
285
+ },
286
+ });
287
+ ```
288
+
108
289
  ### Working directory controls
109
290
 
110
- Codex runs in the current working directory by default. To avoid unrecoverable errors, Codex requires the working directory to be a Git repository. You can skip the Git repository check by passing the `skipGitRepoCheck` option when creating a thread.
291
+ Codex runs in the current working directory by default. To avoid unrecoverable errors, Codex
292
+ requires the working directory to be a Git repository. You can skip the Git repository check by
293
+ passing the `skipGitRepoCheck` option when creating a thread.
111
294
 
112
295
  ```typescript
113
296
  const thread = codex.startThread({
@@ -124,6 +307,134 @@ The Native SDK provides additional capabilities beyond the TypeScript SDK:
124
307
 
125
308
  Register JavaScript functions as tools that Codex can discover and invoke during execution. Tools are registered globally on the `Codex` instance and become available to all threads and agents.
126
309
 
310
+ > **Override built-ins:** If you register a tool whose `name` matches one of Codex's built-in tools (for example `read_file`, `local_shell`, or `apply_patch`), the native implementation is replaced for the lifetime of that `Codex` instance. This lets you customize or disable default behaviors while keeping the same tool interface.
311
+
312
+ #### Built-in tool override cheat sheet
313
+
314
+ All snippets assume you already created an instance with `const codex = new Codex();`. Registering any of these names swaps out Codex's default implementation.
315
+
316
+ - `shell` – sandboxed shell command runner (models without unified exec)
317
+ ```typescript
318
+ codex.registerTool({
319
+ name: "shell",
320
+ handler: () => ({ error: "Shell disabled by policy", success: false }),
321
+ });
322
+ ```
323
+
324
+ - `exec_command` – streaming command execution (available when unified exec is enabled)
325
+ ```typescript
326
+ codex.registerTool({
327
+ name: "exec_command",
328
+ handler: (_, invocation) => ({
329
+ output: `Pretend ran: ${invocation.arguments}`,
330
+ success: true,
331
+ }),
332
+ });
333
+ ```
334
+
335
+ - `write_stdin` – feeds additional input into an in-flight `exec_command`
336
+ ```typescript
337
+ codex.registerTool({
338
+ name: "write_stdin",
339
+ handler: () => ({ output: "stdin blocked", success: false }),
340
+ });
341
+ ```
342
+
343
+ - `local_shell` – simplified shell command helper (models that prefer local shell over unified exec)
344
+ ```typescript
345
+ codex.registerTool({
346
+ name: "local_shell",
347
+ handler: () => ({ output: "local shell override", success: true }),
348
+ });
349
+ ```
350
+
351
+ - `list_mcp_resources`, `list_mcp_resource_templates`, `read_mcp_resource` – MCP discovery helpers
352
+ ```typescript
353
+ for (const name of [
354
+ "list_mcp_resources",
355
+ "list_mcp_resource_templates",
356
+ "read_mcp_resource",
357
+ ]) {
358
+ codex.registerTool({
359
+ name,
360
+ handler: () => ({ output: JSON.stringify({ notice: `${name} overridden` }) }),
361
+ });
362
+ }
363
+ ```
364
+
365
+ - `update_plan` – emits high-level plan updates back to the host UI
366
+ ```typescript
367
+ codex.registerTool({
368
+ name: "update_plan",
369
+ handler: () => ({ output: "Plan updates disabled" }),
370
+ });
371
+ ```
372
+
373
+ - `apply_patch` – applies patches authored by the agent
374
+ ```typescript
375
+ codex.registerTool({
376
+ name: "apply_patch",
377
+ handler: (_, { arguments }) => ({
378
+ output: `Custom patch handler received: ${arguments}`,
379
+ success: true,
380
+ }),
381
+ });
382
+ ```
383
+
384
+ - `web_search` – performs outbound web searches (only on models with the feature enabled)
385
+ ```typescript
386
+ codex.registerTool({
387
+ name: "web_search",
388
+ handler: (_, { arguments }) => ({
389
+ output: `Search stub: ${arguments}`,
390
+ success: true,
391
+ }),
392
+ });
393
+ ```
394
+
395
+ - `view_image` – attaches a local image for the model to inspect
396
+ ```typescript
397
+ codex.registerTool({
398
+ name: "view_image",
399
+ handler: (_, { arguments }) => ({
400
+ output: `Ignoring image path ${arguments}`,
401
+ success: true,
402
+ }),
403
+ });
404
+ ```
405
+
406
+ - `grep_files`, `read_file`, `list_dir` – workspace inspection helpers (enabled via experimental flags)
407
+ ```typescript
408
+ for (const name of ["grep_files", "read_file", "list_dir"]) {
409
+ codex.registerTool({
410
+ name,
411
+ handler: (_, { arguments }) => ({
412
+ output: JSON.stringify({ name, arguments, overridden: true }),
413
+ success: true,
414
+ }),
415
+ });
416
+ }
417
+ ```
418
+
419
+ - `test_sync_tool` – synchronization helper used in concurrency tests
420
+ ```typescript
421
+ codex.registerTool({
422
+ name: "test_sync_tool",
423
+ handler: () => ({ output: "Barrier skipped" }),
424
+ });
425
+ ```
426
+
427
+ - MCP server tools – any name of the form `server::tool`
428
+ ```typescript
429
+ codex.registerTool({
430
+ name: "jira::create_issue",
431
+ handler: (_, { arguments }) => ({
432
+ output: `Custom Jira integration received ${arguments}`,
433
+ success: true,
434
+ }),
435
+ });
436
+ ```
437
+
127
438
  ```typescript
128
439
  const codex = new Codex();
129
440
 
@@ -182,6 +493,63 @@ Return an object with:
182
493
  - `success` (optional): Boolean indicating success/failure
183
494
  - `error` (optional): Error message if the tool execution failed
184
495
 
496
+ ### Tool Interceptors
497
+
498
+ For more advanced use cases, register **tool interceptors** that can wrap built-in Codex tools with pre/post-processing logic while still executing the original implementation.
499
+
500
+ ```typescript
501
+ const codex = new Codex();
502
+
503
+ // Intercept exec_command calls to add custom timeout and logging
504
+ codex.registerToolInterceptor("exec_command", async (invocation) => {
505
+ // Pre-processing: modify the arguments
506
+ const args = JSON.parse(invocation.arguments ?? "{}");
507
+ const enhancedArgs = {
508
+ ...args,
509
+ timeout_ms: args.timeout_ms ?? 10000, // Default 10s timeout
510
+ justification: args.justification ?? "intercepted",
511
+ };
512
+
513
+ // For now, interceptors return a placeholder response
514
+ // Future versions will support calling the builtin implementation
515
+ return {
516
+ output: `[INTERCEPTED] Would execute: ${JSON.stringify(enhancedArgs)}`,
517
+ success: true,
518
+ };
519
+ });
520
+
521
+ // Intercept apply_patch to add validation
522
+ codex.registerToolInterceptor("apply_patch", async (invocation) => {
523
+ const args = JSON.parse(invocation.arguments ?? "{}");
524
+
525
+ // Add custom validation or preprocessing
526
+ if (!args.patch_content?.includes("diff")) {
527
+ return {
528
+ output: "Invalid patch format - must contain diff data",
529
+ success: false,
530
+ };
531
+ }
532
+
533
+ // Return modified result
534
+ return {
535
+ output: `[VALIDATED] ${args.patch_content}`,
536
+ success: true,
537
+ };
538
+ });
539
+ ```
540
+
541
+ **Key Differences from Tool Overrides:**
542
+
543
+ - **Interceptors wrap** built-in tools instead of replacing them entirely
544
+ - **Preserve sandboxing** - interceptors cannot bypass Codex's security policies
545
+ - **Chainable** - multiple interceptors can be registered for the same tool
546
+ - **Future enhancement** - interceptors will be able to call the underlying builtin implementation
547
+
548
+ **Current Notes:**
549
+
550
+ - Tool interceptors support decorating responses by calling `context.callBuiltin()`
551
+ - Multiple interceptors per tool will be composed in registration order in a future release
552
+
185
553
  ### Agent Orchestration
186
554
 
187
555
  Create specialized agents with custom system prompts and tools for multi-agent workflows.
@@ -238,13 +606,63 @@ const securityResult = await securityAgent.run(
238
606
 
239
607
  Agents automatically have access to the conversation history, enabling seamless handoffs between specialized agents.
240
608
 
609
+ ### Reverie Archive APIs
610
+
611
+ Query past Codex sessions directly from Node.js to surface relevant prior work without leaving the terminal.
612
+
613
+ ```typescript
614
+ import {
615
+ reverieListConversations,
616
+ reverieSearchConversations,
617
+ reverieGetConversationInsights,
618
+ } from "@codex-native/sdk";
619
+
620
+ const codexHome = process.env.CODEX_HOME ?? `${process.env.HOME}/.codex`;
621
+
622
+ // List the newest conversations (newest first)
623
+ const conversations = await reverieListConversations(codexHome, 10);
624
+
625
+ // Search for conversations mentioning "authentication"
626
+ const matches = await reverieSearchConversations(codexHome, "authentication issue", 5);
627
+
628
+ // Read the highlights/insights from a specific conversation rollout
629
+ const insights = await reverieGetConversationInsights(matches[0].conversation.path, "JWT");
630
+ ```
631
+
632
+ Results include `headRecords` and `tailRecords`, plus the TOON-encoded `headRecordsToon` and `tailRecordsToon` previews used by the Rust CLI/TUI, so you can plug them into custom dashboards or route them back into an agent as `<system notification>`s without wasting tokens.
633
+
634
+ Need to compact your own JSON payloads before feeding them to an LLM? Call `encodeToToon(value)` from JavaScript to get the same Token-Oriented Object Notation that Codex now uses for reverie search/indexing.
635
+
636
+ ### Tokenizer Helpers (tiktoken)
637
+
638
+ Access the same tiktoken-powered tokenizer used by Codex from JavaScript for budgeting prompts or implementing local ranking logic.
639
+
640
+ ```typescript
641
+ import {
642
+ tokenizerCount,
643
+ tokenizerEncode,
644
+ tokenizerDecode,
645
+ } from "@codex-native/sdk";
646
+
647
+ const text = "hello world";
648
+
649
+ // Count tokens using a specific encoding or model alias
650
+ const tokens = tokenizerEncode(text, { encoding: "cl100k_base" });
651
+ const count = tokenizerCount(text, { encoding: "cl100k_base" });
652
+
653
+ // Round-trip
654
+ const decoded = tokenizerDecode(tokens, { encoding: "cl100k_base" });
655
+ ```
656
+
657
+ `encoding` accepts `"o200k_base"` or `"cl100k_base"`, and you can also pass `model: "gpt-5"` to mirror Codex’s model-to-encoding mapping. Set `withSpecialTokens: true` when you need precise accounting for schema-guided prompts.
658
+
241
659
  ## API Options
242
660
 
243
661
  ### Codex Constructor Options
244
662
 
245
663
  ```typescript
246
664
  interface CodexOptions {
247
- apiKey?: string; // Responses API key (defaults to ANTHROPIC_API_KEY env var)
665
+ apiKey?: string; // Responses API key (defaults to OPENAI_API_KEY env var)
248
666
  baseUrl?: string; // API base URL override
249
667
  skipGitRepoCheck?: boolean; // Skip Git repository validation
250
668
  }
@@ -254,13 +672,62 @@ interface CodexOptions {
254
672
 
255
673
  ```typescript
256
674
  interface ThreadOptions {
257
- model?: string; // Model to use (e.g., "claude-sonnet-4")
675
+ model?: string; // Model to use (e.g., "gpt-5-codex")
258
676
  sandboxMode?: "read-only" | "workspace-write" | "danger-full-access";
677
+ approvalMode?: "never" | "on-request" | "on-failure" | "untrusted";
678
+ workspaceWriteOptions?: {
679
+ networkAccess?: boolean; // Enable network in workspace-write mode (default: false)
680
+ writableRoots?: string[]; // Additional writable directories
681
+ excludeTmpdirEnvVar?: boolean; // Exclude TMPDIR from writable roots
682
+ excludeSlashTmp?: boolean; // Exclude /tmp from writable roots (Unix only)
683
+ };
259
684
  workingDirectory?: string; // Directory to run Codex in
260
685
  skipGitRepoCheck?: boolean; // Skip Git repository validation
686
+ fullAuto?: boolean; // @deprecated Use sandboxMode and approvalMode
261
687
  }
262
688
  ```
263
689
 
690
+ #### Sandbox Modes
691
+
692
+ - **`read-only`**: AI can only read files, must approve all edits
693
+ - **`workspace-write`**: AI can edit workspace files freely (with optional network)
694
+ - **`danger-full-access`**: No sandbox (dangerous!)
695
+
696
+ #### Approval Policies
697
+
698
+ - **`never`**: Never ask for approval (commands execute automatically)
699
+ - **`on-request`**: Model decides when to ask (default)
700
+ - **`on-failure`**: Auto-approve but escalate on failure
701
+ - **`untrusted`**: Only trusted commands auto-approved
702
+
703
+ #### Network Access Configuration
704
+
705
+ Enable network access in `workspace-write` mode:
706
+
707
+ ```typescript
708
+ const thread = codex.startThread({
709
+ sandboxMode: "workspace-write",
710
+ workspaceWriteOptions: {
711
+ networkAccess: true
712
+ }
713
+ });
714
+ ```
715
+
716
+ #### Advanced Sandbox Configuration
717
+
718
+ Configure additional writable directories:
719
+
720
+ ```typescript
721
+ const thread = codex.startThread({
722
+ sandboxMode: "workspace-write",
723
+ workspaceWriteOptions: {
724
+ writableRoots: ["/path/to/additional/dir"],
725
+ excludeTmpdirEnvVar: false,
726
+ excludeSlashTmp: false
727
+ }
728
+ });
729
+ ```
730
+
264
731
  ### Turn Options
265
732
 
266
733
  ```typescript
@@ -374,6 +841,16 @@ pnpm run release # Publish all packages
374
841
 
375
842
  npm automatically installs the correct platform package as an optional dependency.
376
843
 
844
+ ## Releasing
845
+
846
+ 1. Update the version in `package.json` and keep the optional dependency versions in sync.
847
+ 2. Record the changes in `CHANGELOG.md` (add a new section for the release).
848
+ 3. Regenerate build artifacts: `pnpm run build`.
849
+ 4. Run the full test suite: `pnpm run test` (which invokes the native Jest runner).
850
+ 5. Publish platform binaries: `pnpm run publish:platforms` (or `npm run publish:platforms`).
851
+ 6. Publish the SDK itself: `node scripts/publish-sdk.mjs` (the `release` script chains everything).
852
+ 7. Tag the release in git.
853
+
377
854
  ## License
378
855
 
379
856
  See [LICENSE](../../LICENSE)
Binary file