@codex-native/sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,638 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/outputSchemaFile.ts
23
+ import { promises as fs } from "fs";
24
+ import os from "os";
25
+ import path from "path";
26
+ async function createOutputSchemaFile(schema) {
27
+ if (schema === void 0) {
28
+ return { cleanup: async () => {
29
+ } };
30
+ }
31
+ if (!isJsonObject(schema)) {
32
+ throw new Error("outputSchema must be a plain JSON object");
33
+ }
34
+ const schemaDir = await fs.mkdtemp(path.join(os.tmpdir(), "codex-output-schema-"));
35
+ const schemaPath = path.join(schemaDir, "schema.json");
36
+ const cleanup = async () => {
37
+ try {
38
+ await fs.rm(schemaDir, { recursive: true, force: true });
39
+ } catch {
40
+ }
41
+ };
42
+ try {
43
+ await fs.writeFile(schemaPath, JSON.stringify(schema), "utf8");
44
+ return { schemaPath, cleanup };
45
+ } catch (error) {
46
+ await cleanup();
47
+ throw error;
48
+ }
49
+ }
50
+ function isJsonObject(value) {
51
+ return typeof value === "object" && value !== null && !Array.isArray(value);
52
+ }
53
+ var init_outputSchemaFile = __esm({
54
+ "src/outputSchemaFile.ts"() {
55
+ "use strict";
56
+ }
57
+ });
58
+
59
+ // src/thread.ts
60
+ function normalizeInput(input) {
61
+ if (typeof input === "string") {
62
+ return { prompt: input, images: [] };
63
+ }
64
+ const promptParts = [];
65
+ const images = [];
66
+ for (const item of input) {
67
+ if (item.type === "text") {
68
+ promptParts.push(item.text);
69
+ } else if (item.type === "local_image") {
70
+ images.push(item.path);
71
+ }
72
+ }
73
+ return { prompt: promptParts.join("\n\n"), images };
74
+ }
75
+ var Thread;
76
+ var init_thread = __esm({
77
+ "src/thread.ts"() {
78
+ "use strict";
79
+ init_outputSchemaFile();
80
+ Thread = class {
81
+ _exec;
82
+ _options;
83
+ _id;
84
+ _threadOptions;
85
+ /** Returns the ID of the thread. Populated after the first turn starts. */
86
+ get id() {
87
+ return this._id;
88
+ }
89
+ /* @internal */
90
+ constructor(exec, options, threadOptions, id = null) {
91
+ this._exec = exec;
92
+ this._options = options;
93
+ this._id = id;
94
+ this._threadOptions = threadOptions;
95
+ }
96
+ /** Provides the input to the agent and streams events as they are produced during the turn. */
97
+ async runStreamed(input, turnOptions = {}) {
98
+ return { events: this.runStreamedInternal(input, turnOptions) };
99
+ }
100
+ async *runStreamedInternal(input, turnOptions = {}) {
101
+ const { schemaPath, cleanup } = await createOutputSchemaFile(turnOptions.outputSchema);
102
+ const options = this._threadOptions;
103
+ const { prompt, images } = normalizeInput(input);
104
+ const generator = this._exec.run({
105
+ input: prompt,
106
+ baseUrl: this._options.baseUrl,
107
+ apiKey: this._options.apiKey,
108
+ threadId: this._id,
109
+ images,
110
+ model: options?.model,
111
+ sandboxMode: options?.sandboxMode,
112
+ workingDirectory: options?.workingDirectory,
113
+ skipGitRepoCheck: options?.skipGitRepoCheck,
114
+ outputSchemaFile: schemaPath,
115
+ outputSchema: turnOptions.outputSchema
116
+ });
117
+ try {
118
+ for await (const item of generator) {
119
+ let parsed;
120
+ try {
121
+ parsed = JSON.parse(item);
122
+ } catch (error) {
123
+ throw new Error(`Failed to parse item: ${item}`, { cause: error });
124
+ }
125
+ if (parsed.type === "thread.started") {
126
+ this._id = parsed.thread_id;
127
+ }
128
+ yield parsed;
129
+ }
130
+ } finally {
131
+ await cleanup();
132
+ }
133
+ }
134
+ /** Provides the input to the agent and returns the completed turn. */
135
+ async run(input, turnOptions = {}) {
136
+ const generator = this.runStreamedInternal(input, turnOptions);
137
+ const items = [];
138
+ let finalResponse = "";
139
+ let usage = null;
140
+ let turnFailure = null;
141
+ for await (const event of generator) {
142
+ if (event.type === "item.completed") {
143
+ if (event.item.type === "agent_message") {
144
+ finalResponse = event.item.text;
145
+ }
146
+ items.push(event.item);
147
+ } else if (event.type === "turn.completed") {
148
+ usage = event.usage;
149
+ } else if (event.type === "turn.failed") {
150
+ turnFailure = event.error;
151
+ break;
152
+ }
153
+ }
154
+ if (turnFailure) {
155
+ throw new Error(turnFailure.message);
156
+ }
157
+ return { items, finalResponse, usage };
158
+ }
159
+ };
160
+ }
161
+ });
162
+
163
+ // src/nativeBinding.ts
164
+ import { createRequire } from "module";
165
+ function getNativeBinding() {
166
+ if (cachedBinding !== void 0) {
167
+ return cachedBinding;
168
+ }
169
+ const require2 = createRequire(import.meta.url);
170
+ try {
171
+ const binding = require2("../index.js");
172
+ cachedBinding = binding;
173
+ return cachedBinding;
174
+ } catch (error) {
175
+ console.warn("Failed to load native NAPI binding:", error);
176
+ cachedBinding = null;
177
+ return cachedBinding;
178
+ }
179
+ }
180
+ var cachedBinding;
181
+ var init_nativeBinding = __esm({
182
+ "src/nativeBinding.ts"() {
183
+ "use strict";
184
+ }
185
+ });
186
+
187
+ // src/exec.ts
188
+ var CodexExec, AsyncQueue;
189
+ var init_exec = __esm({
190
+ "src/exec.ts"() {
191
+ "use strict";
192
+ init_nativeBinding();
193
+ CodexExec = class {
194
+ native;
195
+ constructor() {
196
+ const nativeBinding = getNativeBinding();
197
+ if (!nativeBinding) {
198
+ throw new Error(
199
+ "Native NAPI binding not available. Make sure @openai/codex-native is properly installed and built."
200
+ );
201
+ }
202
+ this.native = nativeBinding;
203
+ }
204
+ async *run(args) {
205
+ const binding = this.native;
206
+ const queue = new AsyncQueue();
207
+ const request = {
208
+ prompt: args.input,
209
+ threadId: args.threadId ?? void 0,
210
+ images: args.images && args.images.length > 0 ? args.images : void 0,
211
+ model: args.model,
212
+ sandboxMode: args.sandboxMode,
213
+ workingDirectory: args.workingDirectory,
214
+ skipGitRepoCheck: args.skipGitRepoCheck,
215
+ outputSchema: args.outputSchema,
216
+ baseUrl: args.baseUrl,
217
+ apiKey: args.apiKey
218
+ };
219
+ let runPromise;
220
+ try {
221
+ runPromise = binding.runThreadStream(request, (err, eventJson) => {
222
+ if (err) {
223
+ queue.fail(err);
224
+ return;
225
+ }
226
+ try {
227
+ queue.push(eventJson ?? "null");
228
+ } catch (error) {
229
+ queue.fail(error);
230
+ }
231
+ }).then(
232
+ () => {
233
+ queue.end();
234
+ },
235
+ (error) => {
236
+ queue.fail(error);
237
+ }
238
+ );
239
+ } catch (error) {
240
+ queue.fail(error);
241
+ throw error;
242
+ }
243
+ let loopError;
244
+ try {
245
+ for await (const value of queue) {
246
+ yield value;
247
+ }
248
+ await runPromise;
249
+ } catch (error) {
250
+ loopError = error;
251
+ throw error;
252
+ } finally {
253
+ queue.end();
254
+ if (loopError) {
255
+ await runPromise.catch(() => {
256
+ });
257
+ }
258
+ }
259
+ }
260
+ };
261
+ AsyncQueue = class {
262
+ buffer = [];
263
+ waiters = [];
264
+ ended = false;
265
+ error;
266
+ push(value) {
267
+ if (this.ended) return;
268
+ if (this.waiters.length > 0) {
269
+ const waiter = this.waiters.shift();
270
+ waiter.resolve({ value, done: false });
271
+ return;
272
+ }
273
+ this.buffer.push(value);
274
+ }
275
+ end() {
276
+ if (this.ended) return;
277
+ this.ended = true;
278
+ const waiters = this.waiters;
279
+ this.waiters = [];
280
+ for (const waiter of waiters) {
281
+ waiter.resolve({ value: void 0, done: true });
282
+ }
283
+ }
284
+ fail(error) {
285
+ if (this.ended) return;
286
+ this.error = error;
287
+ this.ended = true;
288
+ const waiters = this.waiters;
289
+ this.waiters = [];
290
+ for (const waiter of waiters) {
291
+ waiter.reject(error);
292
+ }
293
+ }
294
+ async next() {
295
+ if (this.buffer.length > 0) {
296
+ const value = this.buffer.shift();
297
+ return { value, done: false };
298
+ }
299
+ if (this.error) {
300
+ return Promise.reject(this.error);
301
+ }
302
+ if (this.ended) {
303
+ return { value: void 0, done: true };
304
+ }
305
+ return new Promise((resolve, reject) => {
306
+ this.waiters.push({ resolve, reject });
307
+ });
308
+ }
309
+ [Symbol.asyncIterator]() {
310
+ return this;
311
+ }
312
+ };
313
+ }
314
+ });
315
+
316
+ // src/codex.ts
317
+ var codex_exports = {};
318
+ __export(codex_exports, {
319
+ Codex: () => Codex
320
+ });
321
+ var Codex;
322
+ var init_codex = __esm({
323
+ "src/codex.ts"() {
324
+ "use strict";
325
+ init_exec();
326
+ init_nativeBinding();
327
+ init_thread();
328
+ Codex = class {
329
+ exec;
330
+ options;
331
+ nativeBinding;
332
+ constructor(options = {}) {
333
+ const predefinedTools = options.tools ? [...options.tools] : [];
334
+ this.nativeBinding = getNativeBinding();
335
+ this.options = { ...options, tools: [] };
336
+ if (this.nativeBinding) {
337
+ if (typeof this.nativeBinding.clearRegisteredTools === "function") {
338
+ this.nativeBinding.clearRegisteredTools();
339
+ }
340
+ for (const tool of predefinedTools) {
341
+ this.registerTool(tool);
342
+ }
343
+ }
344
+ this.exec = new CodexExec();
345
+ }
346
+ registerTool(tool) {
347
+ if (!this.nativeBinding) {
348
+ throw new Error("Native tool registration requires the NAPI binding");
349
+ }
350
+ if (typeof this.nativeBinding.registerTool !== "function") {
351
+ console.warn("registerTool is not available in this build - tools feature may be incomplete");
352
+ return;
353
+ }
354
+ const { handler, ...info } = tool;
355
+ this.nativeBinding.registerTool(info, handler);
356
+ if (!this.options.tools) {
357
+ this.options.tools = [];
358
+ }
359
+ this.options.tools.push(tool);
360
+ }
361
+ /**
362
+ * Starts a new conversation with an agent.
363
+ * @returns A new thread instance.
364
+ */
365
+ startThread(options = {}) {
366
+ return new Thread(this.exec, this.options, options);
367
+ }
368
+ /**
369
+ * Resumes a conversation with an agent based on the thread id.
370
+ * Threads are persisted in ~/.codex/sessions.
371
+ *
372
+ * @param id The id of the thread to resume.
373
+ * @returns A new thread instance.
374
+ */
375
+ resumeThread(id, options = {}) {
376
+ return new Thread(this.exec, this.options, options, id);
377
+ }
378
+ };
379
+ }
380
+ });
381
+
382
+ // src/index.ts
383
+ init_thread();
384
+ init_codex();
385
+
386
+ // src/agents/CodexProvider.ts
387
+ var CodexProvider = class {
388
+ codex = null;
389
+ options;
390
+ constructor(options = {}) {
391
+ this.options = {
392
+ workingDirectory: options.workingDirectory || process.cwd(),
393
+ skipGitRepoCheck: options.skipGitRepoCheck ?? false,
394
+ ...options
395
+ };
396
+ }
397
+ /**
398
+ * Lazy initialization of Codex instance
399
+ */
400
+ getCodex() {
401
+ if (!this.codex) {
402
+ const { Codex: CodexClass } = (init_codex(), __toCommonJS(codex_exports));
403
+ this.codex = new CodexClass({
404
+ apiKey: this.options.apiKey,
405
+ baseUrl: this.options.baseUrl
406
+ });
407
+ }
408
+ return this.codex;
409
+ }
410
+ getModel(modelName) {
411
+ const model = modelName || this.options.defaultModel;
412
+ return new CodexModel(this.getCodex(), model, this.options);
413
+ }
414
+ };
415
+ var CodexModel = class {
416
+ codex;
417
+ modelName;
418
+ thread = null;
419
+ options;
420
+ constructor(codex, modelName, options) {
421
+ this.codex = codex;
422
+ this.modelName = modelName;
423
+ this.options = options;
424
+ }
425
+ /**
426
+ * Get or create the thread for this model instance
427
+ */
428
+ getThread(conversationId) {
429
+ if (conversationId && !this.thread) {
430
+ this.thread = this.codex.resumeThread(conversationId, this.getThreadOptions());
431
+ } else if (!this.thread) {
432
+ this.thread = this.codex.startThread(this.getThreadOptions());
433
+ }
434
+ return this.thread;
435
+ }
436
+ getThreadOptions() {
437
+ return {
438
+ model: this.modelName,
439
+ workingDirectory: this.options.workingDirectory,
440
+ skipGitRepoCheck: this.options.skipGitRepoCheck
441
+ };
442
+ }
443
+ async getResponse(request) {
444
+ const thread = this.getThread(request.conversationId || request.previousResponseId);
445
+ const input = this.convertRequestToInput(request);
446
+ const turn = await thread.run(input, {
447
+ outputSchema: request.outputType?.schema
448
+ });
449
+ return {
450
+ usage: this.convertUsage(turn.usage),
451
+ output: this.convertItemsToOutput(turn.items, turn.finalResponse),
452
+ responseId: thread.id || void 0
453
+ };
454
+ }
455
+ async *getStreamedResponse(request) {
456
+ const thread = this.getThread(request.conversationId || request.previousResponseId);
457
+ const input = this.convertRequestToInput(request);
458
+ const { events } = await thread.runStreamed(input, {
459
+ outputSchema: request.outputType?.schema
460
+ });
461
+ let accumulatedText = "";
462
+ for await (const event of events) {
463
+ const streamEvents = this.convertCodexEventToStreamEvent(event, accumulatedText);
464
+ if (event.type === "item.completed" && event.item.type === "agent_message") {
465
+ accumulatedText = event.item.text;
466
+ }
467
+ for (const streamEvent of streamEvents) {
468
+ yield streamEvent;
469
+ }
470
+ }
471
+ }
472
+ /**
473
+ * Convert ModelRequest to Codex Input format
474
+ */
475
+ convertRequestToInput(request) {
476
+ const parts = [];
477
+ if (request.systemInstructions) {
478
+ parts.push({
479
+ type: "text",
480
+ text: `<system>
481
+ ${request.systemInstructions}
482
+ </system>
483
+
484
+ `
485
+ });
486
+ }
487
+ if (typeof request.input === "string") {
488
+ parts.push({ type: "text", text: request.input });
489
+ } else {
490
+ for (const item of request.input) {
491
+ if (item.type === "input_text") {
492
+ parts.push({ type: "text", text: item.text });
493
+ } else if (item.type === "input_image") {
494
+ if (typeof item.image === "string") {
495
+ continue;
496
+ } else if ("url" in item.image) {
497
+ continue;
498
+ } else if ("fileId" in item.image) {
499
+ continue;
500
+ }
501
+ }
502
+ }
503
+ }
504
+ if (parts.length === 1 && parts[0].type === "text") {
505
+ return parts[0].text;
506
+ }
507
+ return parts;
508
+ }
509
+ /**
510
+ * Convert Codex Usage to ModelResponse Usage
511
+ */
512
+ convertUsage(usage) {
513
+ if (!usage) {
514
+ return {
515
+ inputTokens: 0,
516
+ outputTokens: 0,
517
+ totalTokens: 0
518
+ };
519
+ }
520
+ const inputTokensDetails = usage.cached_input_tokens ? [{ cachedTokens: usage.cached_input_tokens }] : void 0;
521
+ return {
522
+ inputTokens: usage.input_tokens,
523
+ outputTokens: usage.output_tokens,
524
+ totalTokens: usage.input_tokens + usage.output_tokens,
525
+ inputTokensDetails
526
+ };
527
+ }
528
+ /**
529
+ * Convert Codex ThreadItems to AgentOutputItems
530
+ */
531
+ convertItemsToOutput(items, finalResponse) {
532
+ const output = [];
533
+ for (const item of items) {
534
+ switch (item.type) {
535
+ case "agent_message": {
536
+ const content = [
537
+ {
538
+ type: "output_text",
539
+ text: item.text
540
+ }
541
+ ];
542
+ output.push({
543
+ type: "message",
544
+ role: "assistant",
545
+ status: "completed",
546
+ content
547
+ });
548
+ break;
549
+ }
550
+ case "reasoning": {
551
+ output.push({
552
+ type: "reasoning",
553
+ reasoning: item.text
554
+ });
555
+ break;
556
+ }
557
+ // Codex handles tools internally, so we don't expose them as function calls
558
+ // The results are already incorporated into the agent_message
559
+ case "command_execution":
560
+ case "file_change":
561
+ case "mcp_tool_call":
562
+ break;
563
+ default:
564
+ break;
565
+ }
566
+ }
567
+ if (output.length === 0 && finalResponse) {
568
+ output.push({
569
+ type: "message",
570
+ role: "assistant",
571
+ status: "completed",
572
+ content: [
573
+ {
574
+ type: "output_text",
575
+ text: finalResponse
576
+ }
577
+ ]
578
+ });
579
+ }
580
+ return output;
581
+ }
582
+ /**
583
+ * Convert Codex ThreadEvent to OpenAI Agents StreamEvent
584
+ */
585
+ convertCodexEventToStreamEvent(event, previousText) {
586
+ const events = [];
587
+ switch (event.type) {
588
+ case "thread.started":
589
+ events.push({ type: "response_started" });
590
+ break;
591
+ case "turn.started":
592
+ break;
593
+ case "item.started":
594
+ break;
595
+ case "item.completed":
596
+ if (event.item.type === "agent_message") {
597
+ events.push({
598
+ type: "output_text_done",
599
+ text: event.item.text
600
+ });
601
+ } else if (event.item.type === "reasoning") {
602
+ events.push({
603
+ type: "reasoning_done",
604
+ reasoning: event.item.text
605
+ });
606
+ }
607
+ break;
608
+ case "turn.completed":
609
+ events.push({
610
+ type: "response_done",
611
+ response: {
612
+ usage: this.convertUsage(event.usage),
613
+ output: [],
614
+ // Items were already emitted
615
+ responseId: this.thread?.id || void 0
616
+ }
617
+ });
618
+ break;
619
+ case "turn.failed":
620
+ events.push({
621
+ type: "error",
622
+ error: {
623
+ message: event.error.message
624
+ }
625
+ });
626
+ break;
627
+ default:
628
+ break;
629
+ }
630
+ return events;
631
+ }
632
+ };
633
+ export {
634
+ Codex,
635
+ CodexProvider,
636
+ Thread
637
+ };
638
+ //# sourceMappingURL=index.mjs.map