@ai-sdk/harness-codex 1.0.42 → 1.0.43

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.
@@ -14,9 +14,7 @@ import {
14
14
  type BridgeEvent,
15
15
  type BridgeTurn,
16
16
  } from '@ai-sdk/harness/bridge';
17
- import type { HarnessV1BuiltinToolName } from '@ai-sdk/harness';
18
17
  import type { StartMessage } from '../codex-bridge-protocol';
19
- import { randomUUID } from 'node:crypto';
20
18
  import { mkdir, writeFile } from 'node:fs/promises';
21
19
  // Temporary workaround for upstream codex MCP-tool bug — see ./cli-relay.ts
22
20
  import {
@@ -24,11 +22,11 @@ import {
24
22
  buildCliShimScript,
25
23
  parseToolRelayCommands,
26
24
  } from './cli-relay';
25
+ import { createCodexStepTracker, defaultUsage } from './codex-step-tracker';
27
26
  import {
28
- createCodexStepTracker,
29
- defaultUsage,
30
- type CodexStepTracker,
31
- } from './codex-step-tracker';
27
+ createEmitStreamEvent,
28
+ type CodexEvent,
29
+ } from './create-emit-stream-event';
32
30
  import { startAuthorizedToolRelay, type ToolRelay } from './tool-relay';
33
31
  import { argv, env as procEnv, stdout } from 'node:process';
34
32
 
@@ -51,20 +49,6 @@ import { argv, env as procEnv, stdout } from 'node:process';
51
49
  */
52
50
  import * as codexSdkModule from '@openai/codex-sdk';
53
51
 
54
- /*
55
- * Native Codex tool name → cross-harness common name. Tools outside this map
56
- * (e.g. MCP tools the model invokes by name) have no common equivalent; their
57
- * native name is forwarded as-is on `tool-call` events.
58
- */
59
- const NATIVE_TO_COMMON: Readonly<Record<string, HarnessV1BuiltinToolName>> = {
60
- shell: 'bash',
61
- web_search: 'webSearch',
62
- };
63
-
64
- function toCommonName(nativeName: string): HarnessV1BuiltinToolName | string {
65
- return NATIVE_TO_COMMON[nativeName] ?? nativeName;
66
- }
67
-
68
52
  const args = parseArgs(argv.slice(2));
69
53
  const workdir = requireArg({ value: args.workdir, name: '--workdir' });
70
54
  const bridgeStateDir = requireArg({
@@ -202,9 +186,15 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
202
186
 
203
187
  const userMessage = start.prompt;
204
188
  let turnUsage: Record<string, unknown> | undefined;
205
- const textByItem = new Map<string, string>();
206
- const reasoningByItem = new Map<string, string>();
207
189
  const stepTracker = createCodexStepTracker({ send: emit });
190
+ const emitStreamEvent = createEmitStreamEvent({
191
+ send: emit,
192
+ stepTracker,
193
+ setTurnUsage: usage => (turnUsage = usage),
194
+ setThreadId: threadId => (threadState.id = threadId),
195
+ emitWarning: turn.emitWarning,
196
+ emitError: turn.emitError,
197
+ });
208
198
 
209
199
  try {
210
200
  const { events } = await thread.runStreamed(userMessage, {
@@ -212,14 +202,6 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
212
202
  });
213
203
  for await (const event of events as AsyncIterable<CodexEvent>) {
214
204
  if (turn.abortSignal.aborted) break;
215
- if (
216
- event.type === 'thread.started' &&
217
- typeof event.thread_id === 'string'
218
- ) {
219
- threadState.id = event.thread_id;
220
- // Announce to the host so it can include the id in resume state.
221
- emit({ type: 'bridge-thread', threadId: event.thread_id });
222
- }
223
205
  // Temporary workaround for upstream codex MCP-tool bug — see ./cli-relay.ts
224
206
  if (cliShimPath && event.item?.type === 'command_execution') {
225
207
  const relayCalls =
@@ -239,15 +221,7 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
239
221
  continue;
240
222
  }
241
223
  }
242
- translateAndEmit(event, {
243
- send: emit,
244
- textByItem,
245
- reasoningByItem,
246
- stepTracker,
247
- setTurnUsage: u => (turnUsage = u),
248
- emitWarning: turn.emitWarning,
249
- emitError: turn.emitError,
250
- });
224
+ emitStreamEvent(event);
251
225
  }
252
226
  } catch (err) {
253
227
  turn.emitError({ error: err, message: 'codex turn failed' });
@@ -265,259 +239,6 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
265
239
  void turn.pendingUserMessages; // accepted but only consumed when codex supports streamed user input
266
240
  }
267
241
 
268
- type CodexItem = {
269
- type: string;
270
- id?: string;
271
- text?: string;
272
- command?: string;
273
- exit_code?: number;
274
- aggregated_output?: string;
275
- status?: 'in_progress' | 'completed' | 'failed';
276
- server?: string;
277
- tool?: string;
278
- arguments?: unknown;
279
- result?: { content?: unknown; structured_content?: unknown } | unknown;
280
- error?: { message?: string };
281
- query?: string;
282
- message?: string;
283
- changes?: ReadonlyArray<{
284
- path: string;
285
- kind: 'add' | 'delete' | 'update';
286
- }>;
287
- };
288
-
289
- function extractMcpToolCallResult(item: CodexItem): unknown {
290
- if (
291
- item.result === undefined ||
292
- item.result === null ||
293
- typeof item.result !== 'object'
294
- ) {
295
- return item.error?.message ? { error: item.error.message } : null;
296
- }
297
- const result = item.result as {
298
- content?: unknown;
299
- structured_content?: unknown;
300
- };
301
- if (
302
- result.structured_content !== undefined &&
303
- result.structured_content !== null
304
- ) {
305
- return result.structured_content;
306
- }
307
- return result.content ?? null;
308
- }
309
-
310
- type CodexEvent = {
311
- type:
312
- | 'thread.started'
313
- | 'turn.completed'
314
- | 'turn.failed'
315
- | 'error'
316
- | 'item.started'
317
- | 'item.updated'
318
- | 'item.completed';
319
- item?: CodexItem;
320
- usage?: Record<string, number>;
321
- error?: { message: string };
322
- message?: string;
323
- thread_id?: string;
324
- };
325
-
326
- function translateAndEmit(
327
- event: CodexEvent,
328
- ctx: {
329
- send: Emit;
330
- textByItem: Map<string, string>;
331
- reasoningByItem: Map<string, string>;
332
- stepTracker: CodexStepTracker;
333
- setTurnUsage: (u: Record<string, unknown>) => void;
334
- emitWarning: BridgeTurn['emitWarning'];
335
- emitError: BridgeTurn['emitError'];
336
- },
337
- ): void {
338
- if (event.type === 'turn.completed') {
339
- if (event.usage) ctx.setTurnUsage(mapUsage(event.usage));
340
- ctx.stepTracker.finishStep();
341
- return;
342
- }
343
- if (event.type === 'turn.failed') {
344
- ctx.emitError({
345
- error: event.error?.message ?? 'codex turn failed',
346
- message: 'codex turn failed',
347
- });
348
- return;
349
- }
350
- if (event.type === 'error') {
351
- ctx.emitError({
352
- error: event.message ?? 'codex error',
353
- message: 'codex stream error',
354
- });
355
- return;
356
- }
357
- if (!event.item) return;
358
- const item = event.item;
359
- const id = item.id ?? randomUUID();
360
- const observeStep = (): void => {
361
- ctx.stepTracker.observeEvent({ event, itemId: id });
362
- };
363
-
364
- if (item.type === 'agent_message' && typeof item.text === 'string') {
365
- /*
366
- * The presence of `id` in `textByItem` — not the `item.started` event —
367
- * marks the text part as opened. Codex does not guarantee an
368
- * `item.started` event carrying text precedes the first `item.updated`
369
- * with text, so keying the `text-start` off the event type can emit a
370
- * `text-delta` for a part that was never opened. Opening lazily on the
371
- * first event with text keeps `text-start` before any `text-delta`.
372
- */
373
- if (!ctx.textByItem.has(id)) {
374
- ctx.send({ type: 'text-start', id });
375
- ctx.textByItem.set(id, '');
376
- }
377
- const last = ctx.textByItem.get(id) ?? '';
378
- const next = item.text;
379
- if (next.length > last.length) {
380
- ctx.send({ type: 'text-delta', id, delta: next.slice(last.length) });
381
- ctx.textByItem.set(id, next);
382
- }
383
- if (event.type === 'item.completed') ctx.send({ type: 'text-end', id });
384
- observeStep();
385
- return;
386
- }
387
-
388
- if (item.type === 'reasoning' && typeof item.text === 'string') {
389
- if (!ctx.reasoningByItem.has(id)) {
390
- ctx.send({ type: 'reasoning-start', id });
391
- ctx.reasoningByItem.set(id, '');
392
- }
393
- const last = ctx.reasoningByItem.get(id) ?? '';
394
- const next = item.text;
395
- if (next.length > last.length) {
396
- ctx.send({ type: 'reasoning-delta', id, delta: next.slice(last.length) });
397
- ctx.reasoningByItem.set(id, next);
398
- }
399
- if (event.type === 'item.completed')
400
- ctx.send({ type: 'reasoning-end', id });
401
- observeStep();
402
- return;
403
- }
404
-
405
- if (item.type === 'command_execution') {
406
- const nativeName = 'shell';
407
- if (event.type === 'item.started') {
408
- ctx.send({
409
- type: 'tool-call',
410
- toolCallId: id,
411
- toolName: toCommonName(nativeName),
412
- nativeName,
413
- input: JSON.stringify({ command: item.command ?? '' }),
414
- providerExecuted: true,
415
- });
416
- } else if (event.type === 'item.completed') {
417
- ctx.send({
418
- type: 'tool-result',
419
- toolCallId: id,
420
- toolName: toCommonName(nativeName),
421
- result: {
422
- exitCode: item.exit_code ?? null,
423
- output: item.aggregated_output ?? '',
424
- status: item.status ?? 'completed',
425
- },
426
- });
427
- }
428
- observeStep();
429
- return;
430
- }
431
-
432
- if (item.type === 'mcp_tool_call') {
433
- if (event.type === 'item.started') {
434
- ctx.send({
435
- type: 'tool-call',
436
- toolCallId: id,
437
- toolName: item.tool ?? 'unknown',
438
- nativeName: item.tool ?? 'unknown',
439
- input: JSON.stringify(item.arguments ?? {}),
440
- providerExecuted: true,
441
- });
442
- } else if (event.type === 'item.completed') {
443
- ctx.send({
444
- type: 'tool-result',
445
- toolCallId: id,
446
- toolName: item.tool ?? 'unknown',
447
- result: extractMcpToolCallResult(item),
448
- });
449
- }
450
- observeStep();
451
- return;
452
- }
453
-
454
- if (item.type === 'web_search') {
455
- const nativeName = 'web_search';
456
- if (event.type === 'item.started') {
457
- ctx.send({
458
- type: 'tool-call',
459
- toolCallId: id,
460
- toolName: toCommonName(nativeName),
461
- nativeName,
462
- input: JSON.stringify({ query: item.query ?? '' }),
463
- providerExecuted: true,
464
- });
465
- } else if (event.type === 'item.completed') {
466
- ctx.send({
467
- type: 'tool-result',
468
- toolCallId: id,
469
- toolName: toCommonName(nativeName),
470
- result: item.result ?? null,
471
- });
472
- }
473
- observeStep();
474
- return;
475
- }
476
-
477
- if (item.type === 'file_change' && event.type === 'item.completed') {
478
- for (const change of item.changes ?? []) {
479
- ctx.send({
480
- type: 'file-change',
481
- event:
482
- change.kind === 'add'
483
- ? 'create'
484
- : change.kind === 'delete'
485
- ? 'delete'
486
- : 'modify',
487
- path: change.path,
488
- });
489
- }
490
- observeStep();
491
- return;
492
- }
493
-
494
- if (item.type === 'error' && event.type === 'item.completed') {
495
- const message =
496
- typeof item.message === 'string' && item.message.trim()
497
- ? item.message
498
- : 'codex reported a non-fatal error item';
499
- ctx.emitWarning({ message });
500
- return;
501
- }
502
- }
503
-
504
- function mapUsage(usage: Record<string, number>): Record<string, unknown> {
505
- const input = usage.input_tokens ?? 0;
506
- const cacheRead = usage.cached_input_tokens ?? 0;
507
- return {
508
- inputTokens: {
509
- total: input,
510
- noCache: Math.max(0, input - cacheRead),
511
- cacheRead,
512
- cacheWrite: 0,
513
- },
514
- outputTokens: {
515
- total: usage.output_tokens ?? 0,
516
- text: usage.output_tokens ?? 0,
517
- },
518
- };
519
- }
520
-
521
242
  /**
522
243
  * Tool relay — HTTP server on 127.0.0.1:0. The CLI shim invoked by Codex POSTs
523
244
  * each tool invocation here; the relay forwards the call to the host (via the
@@ -60,12 +60,12 @@ type WriteSkillsResult = {
60
60
  * The model the adapter pins when the consumer configures none. The Codex SDK
61
61
  * does not report the model it resolves to at runtime (no model field on any
62
62
  * event), and exposes no default-model constant, so we pin the latest
63
- * codex-specialized model available for the bundled `@openai/codex@0.130.0`
64
- * (published 2026-05-08): `gpt-5.3-codex` (released 2026-02). Keep this in sync
65
- * when bumping the codex SDK/binary. Passing it explicitly makes the resolved
66
- * model deterministic and the telemetry (`gen_ai.request.model`) accurate.
63
+ * default model resolved by the bundled `@openai/codex@0.144.5`:
64
+ * `gpt-5.6-sol`. Keep this in sync when bumping the codex SDK/binary. Passing
65
+ * it explicitly makes the resolved model deterministic and the telemetry
66
+ * (`gen_ai.request.model`) accurate.
67
67
  */
68
- const DEFAULT_CODEX_MODEL = 'gpt-5.3-codex';
68
+ const DEFAULT_CODEX_MODEL = 'gpt-5.6-sol';
69
69
 
70
70
  /**
71
71
  * Value to use in User-Agent and `x-client-app` headers.