@janole/ai-sdk-provider-codex-asp 0.2.4 → 0.3.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/README.md CHANGED
@@ -147,6 +147,7 @@ See [`src/provider.ts`](src/provider.ts) for full type definitions.
147
147
  See the [`examples/`](examples/) directory:
148
148
 
149
149
  - [`generate-text.ts`](examples/generate-text.ts) — Non-streaming text generation
150
+ - [`generate-object.ts`](examples/generate-object.ts) — Structured output with `generateText` + `Output.object`
150
151
  - [`stream-text.ts`](examples/stream-text.ts) — Streaming text generation
151
152
  - [`cross-call-tools.ts`](examples/cross-call-tools.ts) — Standard AI SDK tools via Codex
152
153
  - [`dynamic-tools.ts`](examples/dynamic-tools.ts) — Provider-level dynamic tools
package/dist/index.cjs CHANGED
@@ -280,6 +280,7 @@ var AppServerClient = class {
280
280
  var PersistentTransport = class {
281
281
  pool;
282
282
  signal;
283
+ threadId;
283
284
  worker = null;
284
285
  pendingInitializeId = null;
285
286
  initializeIntercepted = false;
@@ -289,9 +290,10 @@ var PersistentTransport = class {
289
290
  constructor(settings) {
290
291
  this.pool = settings.pool;
291
292
  this.signal = settings.signal;
293
+ this.threadId = settings.threadId;
292
294
  }
293
295
  async connect() {
294
- this.worker = await this.pool.acquire(stripUndefined({ signal: this.signal }));
296
+ this.worker = await this.pool.acquire(stripUndefined({ signal: this.signal, threadId: this.threadId }));
295
297
  await this.worker.ensureConnected();
296
298
  }
297
299
  disconnect() {
@@ -663,6 +665,7 @@ var CodexWorker = class {
663
665
  await this.inner.connect();
664
666
  }
665
667
  acquire() {
668
+ this.clearSessionListeners();
666
669
  if (this.idleTimer) {
667
670
  clearTimeout(this.idleTimer);
668
671
  this.idleTimer = null;
@@ -747,8 +750,17 @@ var CodexWorkerPool = class {
747
750
  if (this.shutdownCalled) {
748
751
  throw new CodexProviderError("Worker pool has been shut down.");
749
752
  }
753
+ if (options?.threadId) {
754
+ const reserved = this.workers.find(
755
+ (w) => (w.state === "idle" || w.state === "disconnected") && w.pendingToolCall?.threadId === options.threadId
756
+ );
757
+ if (reserved) {
758
+ reserved.acquire();
759
+ return reserved;
760
+ }
761
+ }
750
762
  const worker = this.workers.find(
751
- (w) => w.state === "idle" || w.state === "disconnected"
763
+ (w) => (w.state === "idle" || w.state === "disconnected") && !w.pendingToolCall
752
764
  );
753
765
  if (!worker) {
754
766
  if (options?.signal?.aborted) {
@@ -756,6 +768,7 @@ var CodexWorkerPool = class {
756
768
  }
757
769
  return new Promise((resolve, reject) => {
758
770
  const waiter = {
771
+ threadId: options?.threadId,
759
772
  resolve,
760
773
  reject,
761
774
  signal: options?.signal,
@@ -775,6 +788,16 @@ var CodexWorkerPool = class {
775
788
  return worker;
776
789
  }
777
790
  release(worker) {
791
+ worker.clearSessionListeners();
792
+ if (worker.pendingToolCall) {
793
+ const idx = this.waiters.findIndex((w) => w.threadId === worker.pendingToolCall?.threadId);
794
+ if (idx >= 0) {
795
+ const [waiter2] = this.waiters.splice(idx, 1);
796
+ this.clearWaiterAbortHandler(waiter2);
797
+ waiter2.resolve(worker);
798
+ return;
799
+ }
800
+ }
778
801
  const waiter = this.waiters.shift();
779
802
  if (waiter) {
780
803
  this.clearWaiterAbortHandler(waiter);
@@ -935,7 +958,7 @@ var DynamicToolsDispatcher = class {
935
958
  // package.json
936
959
  var package_default = {
937
960
  name: "@janole/ai-sdk-provider-codex-asp",
938
- version: "0.2.4"};
961
+ version: "0.3.1"};
939
962
 
940
963
  // src/package-info.ts
941
964
  var PACKAGE_NAME = package_default.name;
@@ -943,14 +966,14 @@ var PACKAGE_VERSION = package_default.version;
943
966
 
944
967
  // src/protocol/provider-metadata.ts
945
968
  var CODEX_PROVIDER_ID = "@janole/ai-sdk-provider-codex-asp";
946
- function codexProviderMetadata(threadId) {
969
+ function codexProviderMetadata(threadId, turnId) {
947
970
  if (!threadId) {
948
971
  return void 0;
949
972
  }
950
- return { [CODEX_PROVIDER_ID]: { threadId } };
973
+ return { [CODEX_PROVIDER_ID]: stripUndefined({ threadId, turnId }) };
951
974
  }
952
- function withProviderMetadata(part, threadId) {
953
- const meta = codexProviderMetadata(threadId);
975
+ function withProviderMetadata(part, threadId, turnId) {
976
+ const meta = codexProviderMetadata(threadId, turnId);
954
977
  return meta ? { ...part, providerMetadata: meta } : part;
955
978
  }
956
979
 
@@ -989,6 +1012,7 @@ var CodexEventMapper = class {
989
1012
  openToolCalls = /* @__PURE__ */ new Map();
990
1013
  planSequenceByTurnId = /* @__PURE__ */ new Map();
991
1014
  threadId;
1015
+ turnId;
992
1016
  latestUsage;
993
1017
  constructor(options) {
994
1018
  this.options = {
@@ -998,6 +1022,12 @@ var CodexEventMapper = class {
998
1022
  setThreadId(threadId) {
999
1023
  this.threadId = threadId;
1000
1024
  }
1025
+ setTurnId(turnId) {
1026
+ this.turnId = turnId;
1027
+ }
1028
+ getTurnId() {
1029
+ return this.turnId;
1030
+ }
1001
1031
  nextPlanSequence(turnId) {
1002
1032
  const next = (this.planSequenceByTurnId.get(turnId) ?? 0) + 1;
1003
1033
  this.planSequenceByTurnId.set(turnId, next);
@@ -1005,7 +1035,7 @@ var CodexEventMapper = class {
1005
1035
  }
1006
1036
  map(event) {
1007
1037
  const parts = [];
1008
- const withMeta = (part) => withProviderMetadata(part, this.threadId);
1038
+ const withMeta = (part) => withProviderMetadata(part, this.threadId, this.turnId);
1009
1039
  const pushStreamStart = () => {
1010
1040
  if (!this.streamStarted) {
1011
1041
  parts.push({ type: "stream-start", warnings: [] });
@@ -1025,6 +1055,10 @@ var CodexEventMapper = class {
1025
1055
  };
1026
1056
  switch (event.method) {
1027
1057
  case "turn/started": {
1058
+ const turnStartedParams = event.params;
1059
+ if (turnStartedParams?.turn?.id) {
1060
+ this.turnId = turnStartedParams.turn.id;
1061
+ }
1028
1062
  pushStreamStart();
1029
1063
  break;
1030
1064
  }
@@ -1322,6 +1356,75 @@ var CodexEventMapper = class {
1322
1356
  return parts;
1323
1357
  }
1324
1358
  };
1359
+
1360
+ // src/session.ts
1361
+ var CodexSessionImpl = class {
1362
+ _threadId;
1363
+ _turnId;
1364
+ _active = true;
1365
+ client;
1366
+ interruptTimeoutMs;
1367
+ constructor(opts) {
1368
+ this.client = opts.client;
1369
+ this._threadId = opts.threadId;
1370
+ this._turnId = opts.turnId;
1371
+ this.interruptTimeoutMs = opts.interruptTimeoutMs;
1372
+ }
1373
+ get threadId() {
1374
+ return this._threadId;
1375
+ }
1376
+ get turnId() {
1377
+ return this._turnId;
1378
+ }
1379
+ /** @internal Called by the model when turn/started arrives with a turnId. */
1380
+ setTurnId(turnId) {
1381
+ this._turnId = turnId;
1382
+ }
1383
+ /** @internal Called by the model when the turn completes or the stream closes. */
1384
+ markInactive() {
1385
+ this._active = false;
1386
+ }
1387
+ isActive() {
1388
+ return this._active;
1389
+ }
1390
+ /**
1391
+ * Inject follow-up input into the current thread.
1392
+ *
1393
+ * Uses turn/start which the app-server routes through steer_input when a
1394
+ * turn is already active, or starts a new turn otherwise. This avoids the
1395
+ * strict timing requirements of turn/steer (which needs codex/event/task_started
1396
+ * before it accepts input). We may revisit turn/steer in the future.
1397
+ */
1398
+ async injectMessage(input) {
1399
+ if (!this._active) {
1400
+ throw new Error("Session is no longer active.");
1401
+ }
1402
+ const userInput = typeof input === "string" ? [{ type: "text", text: input, text_elements: [] }] : input;
1403
+ const turnStartParams = {
1404
+ threadId: this._threadId,
1405
+ input: userInput
1406
+ };
1407
+ const result = await this.client.request("turn/start", turnStartParams);
1408
+ const newTurnId = result.turnId ?? result.turn?.id;
1409
+ if (newTurnId) {
1410
+ this._turnId = newTurnId;
1411
+ }
1412
+ }
1413
+ async interrupt() {
1414
+ if (!this._active || !this._turnId) {
1415
+ return;
1416
+ }
1417
+ const interruptParams = {
1418
+ threadId: this._threadId,
1419
+ turnId: this._turnId
1420
+ };
1421
+ await this.client.request(
1422
+ "turn/interrupt",
1423
+ interruptParams,
1424
+ this.interruptTimeoutMs
1425
+ );
1426
+ }
1427
+ };
1325
1428
  function mapSystemPrompt(prompt) {
1326
1429
  const chunks = [];
1327
1430
  for (const message of prompt) {
@@ -1734,7 +1837,8 @@ var CodexLanguageModel = class {
1734
1837
  });
1735
1838
  }
1736
1839
  doStream(options) {
1737
- const transport = this.config.providerSettings.transportFactory ? this.config.providerSettings.transportFactory(options.abortSignal) : this.config.providerSettings.transport?.type === "websocket" ? new WebSocketTransport(this.config.providerSettings.transport.websocket) : new StdioTransport(this.config.providerSettings.transport?.stdio);
1840
+ const resumeThreadId = extractResumeThreadId(options.prompt);
1841
+ const transport = this.config.providerSettings.transportFactory ? this.config.providerSettings.transportFactory(options.abortSignal, resumeThreadId) : this.config.providerSettings.transport?.type === "websocket" ? new WebSocketTransport(this.config.providerSettings.transport.websocket) : new StdioTransport(this.config.providerSettings.transport?.stdio);
1738
1842
  const packetLogger = this.config.providerSettings.debug?.logPackets === true ? this.config.providerSettings.debug.logger ?? ((packet) => {
1739
1843
  if (packet.direction === "inbound") {
1740
1844
  console.debug("[codex packet]", packet.message);
@@ -1754,6 +1858,7 @@ var CodexLanguageModel = class {
1754
1858
  }));
1755
1859
  let activeThreadId;
1756
1860
  let activeTurnId;
1861
+ let session;
1757
1862
  const interruptTimeoutMs = this.config.providerSettings.interruptTimeoutMs ?? 1e4;
1758
1863
  const interruptTurnIfPossible = async () => {
1759
1864
  if (!activeThreadId || !activeTurnId) {
@@ -1774,6 +1879,7 @@ var CodexLanguageModel = class {
1774
1879
  if (closed) {
1775
1880
  return;
1776
1881
  }
1882
+ session?.markInactive();
1777
1883
  controller.enqueue({ type: "error", error });
1778
1884
  closed = true;
1779
1885
  try {
@@ -1787,6 +1893,7 @@ var CodexLanguageModel = class {
1787
1893
  if (closed) {
1788
1894
  return;
1789
1895
  }
1896
+ session?.markInactive();
1790
1897
  closed = true;
1791
1898
  try {
1792
1899
  controller.close();
@@ -1893,6 +2000,11 @@ var CodexLanguageModel = class {
1893
2000
  approvalsDispatcher.attach(client);
1894
2001
  client.onAnyNotification((method, params) => {
1895
2002
  const parts = mapper.map({ method, params });
2003
+ const mappedTurnId = mapper.getTurnId();
2004
+ if (mappedTurnId && mappedTurnId !== activeTurnId) {
2005
+ activeTurnId = mappedTurnId;
2006
+ session?.setTurnId(mappedTurnId);
2007
+ }
1896
2008
  for (const part of parts) {
1897
2009
  controller.enqueue(part);
1898
2010
  if (part.type === "finish") {
@@ -1929,7 +2041,6 @@ var CodexLanguageModel = class {
1929
2041
  await client.request("initialize", initializeParams);
1930
2042
  await client.notification("initialized");
1931
2043
  debugLog?.("inbound", "prompt", options.prompt);
1932
- const resumeThreadId = extractResumeThreadId(options.prompt);
1933
2044
  debugLog?.("inbound", "extractResumeThreadId", { resumeThreadId });
1934
2045
  const developerInstructions = mapSystemPrompt(options.prompt);
1935
2046
  let threadId;
@@ -1990,10 +2101,13 @@ var CodexLanguageModel = class {
1990
2101
  }
1991
2102
  }
1992
2103
  } else {
2104
+ const mcpServers = this.config.providerSettings.mcpServers;
2105
+ const config = mcpServers ? { mcp_servers: mcpServers } : void 0;
1993
2106
  const threadStartParams = stripUndefined({
1994
2107
  model: this.config.providerSettings.defaultModel ?? this.modelId,
1995
2108
  dynamicTools,
1996
2109
  developerInstructions,
2110
+ config,
1997
2111
  cwd: this.config.providerSettings.defaultThreadSettings?.cwd,
1998
2112
  approvalPolicy: this.config.providerSettings.defaultThreadSettings?.approvalPolicy,
1999
2113
  sandbox: this.config.providerSettings.defaultThreadSettings?.sandbox
@@ -2025,17 +2139,26 @@ var CodexLanguageModel = class {
2025
2139
  sandboxPolicy: this.config.providerSettings.defaultTurnSettings?.sandboxPolicy,
2026
2140
  model: this.config.providerSettings.defaultTurnSettings?.model,
2027
2141
  effort: this.config.providerSettings.defaultTurnSettings?.effort,
2028
- summary: this.config.providerSettings.defaultTurnSettings?.summary
2142
+ summary: this.config.providerSettings.defaultTurnSettings?.summary,
2143
+ outputSchema: options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0
2029
2144
  });
2030
2145
  debugLog?.("outbound", "turn/start", turnStartParams);
2031
2146
  const turnStartResult = await client.request("turn/start", turnStartParams);
2032
2147
  activeTurnId = extractTurnId(turnStartResult);
2148
+ session = new CodexSessionImpl({
2149
+ client,
2150
+ threadId: activeThreadId,
2151
+ turnId: activeTurnId,
2152
+ interruptTimeoutMs
2153
+ });
2154
+ this.config.providerSettings.onSessionCreated?.(session);
2033
2155
  } catch (error) {
2034
2156
  await closeWithError(error);
2035
2157
  }
2036
2158
  })();
2037
2159
  },
2038
2160
  cancel: async () => {
2161
+ session?.markInactive();
2039
2162
  try {
2040
2163
  await interruptTurnIfPossible();
2041
2164
  } catch {
@@ -2132,6 +2255,12 @@ function acquirePersistentPool(settings) {
2132
2255
  }
2133
2256
 
2134
2257
  // src/provider.ts
2258
+ var poolHandleCleanup = new FinalizationRegistry(
2259
+ (handle) => {
2260
+ void handle.release().catch(() => {
2261
+ });
2262
+ }
2263
+ );
2135
2264
  function createNoSuchModelError(modelId, modelType) {
2136
2265
  return new provider.NoSuchModelError({ modelId, modelType });
2137
2266
  }
@@ -2152,7 +2281,7 @@ function createCodexAppServer(settings = {}) {
2152
2281
  });
2153
2282
  }
2154
2283
  const persistentPool = persistentPoolHandle?.pool ?? null;
2155
- const effectiveTransportFactory = persistentPool ? (signal) => new PersistentTransport(stripUndefined({ pool: persistentPool, signal })) : baseTransportFactory;
2284
+ const effectiveTransportFactory = persistentPool ? (signal, threadId) => new PersistentTransport(stripUndefined({ pool: persistentPool, signal, threadId })) : baseTransportFactory;
2156
2285
  const resolvedSettings = Object.freeze(stripUndefined({
2157
2286
  defaultModel: settings.defaultModel,
2158
2287
  experimentalApi: settings.experimentalApi,
@@ -2170,12 +2299,15 @@ function createCodexAppServer(settings = {}) {
2170
2299
  defaultTurnSettings: settings.defaultTurnSettings ? { ...settings.defaultTurnSettings } : void 0,
2171
2300
  compaction: settings.compaction ? { ...settings.compaction } : void 0,
2172
2301
  transportFactory: effectiveTransportFactory,
2302
+ mcpServers: settings.mcpServers ? { ...settings.mcpServers } : void 0,
2173
2303
  tools: settings.tools ? { ...settings.tools } : void 0,
2174
2304
  toolHandlers: settings.toolHandlers ? { ...settings.toolHandlers } : void 0,
2175
2305
  toolTimeoutMs: settings.toolTimeoutMs,
2176
2306
  interruptTimeoutMs: settings.interruptTimeoutMs,
2177
2307
  approvals: settings.approvals ? { ...settings.approvals } : void 0,
2178
- debug: settings.debug ? { ...settings.debug } : void 0
2308
+ debug: settings.debug ? { ...settings.debug } : void 0,
2309
+ emitPlanUpdates: settings.emitPlanUpdates,
2310
+ onSessionCreated: settings.onSessionCreated
2179
2311
  }));
2180
2312
  const createLanguageModel = (modelId, modelSettings = {}) => new CodexLanguageModel(modelId, modelSettings, {
2181
2313
  provider: CODEX_PROVIDER_ID,
@@ -2197,15 +2329,44 @@ function createCodexAppServer(settings = {}) {
2197
2329
  imageModel(modelId) {
2198
2330
  throw createNoSuchModelError(modelId, "imageModel");
2199
2331
  },
2332
+ async listModels(params) {
2333
+ const transport = effectiveTransportFactory ? effectiveTransportFactory() : resolvedSettings.transport?.type === "websocket" ? new WebSocketTransport(resolvedSettings.transport.websocket) : new StdioTransport(resolvedSettings.transport?.stdio);
2334
+ const client = new AppServerClient(transport);
2335
+ try {
2336
+ await client.connect();
2337
+ const initializeParams = stripUndefined({
2338
+ clientInfo: resolvedSettings.clientInfo ?? {
2339
+ name: PACKAGE_NAME,
2340
+ version: PACKAGE_VERSION
2341
+ }
2342
+ });
2343
+ await client.request("initialize", initializeParams);
2344
+ await client.notification("initialized");
2345
+ const models = [];
2346
+ let cursor;
2347
+ do {
2348
+ const response = await client.request("model/list", stripUndefined({ ...params, cursor }));
2349
+ models.push(...response.data);
2350
+ cursor = response.nextCursor ?? void 0;
2351
+ } while (cursor);
2352
+ return models;
2353
+ } finally {
2354
+ await client.disconnect();
2355
+ }
2356
+ },
2200
2357
  async shutdown() {
2201
2358
  if (!persistentPoolHandle) {
2202
2359
  return;
2203
2360
  }
2361
+ poolHandleCleanup.unregister(provider);
2204
2362
  const handle = persistentPoolHandle;
2205
2363
  persistentPoolHandle = null;
2206
2364
  await handle.release();
2207
2365
  }
2208
2366
  });
2367
+ if (persistentPoolHandle) {
2368
+ poolHandleCleanup.register(provider, persistentPoolHandle, provider);
2369
+ }
2209
2370
  return provider;
2210
2371
  }
2211
2372
  var codexAppServer = createCodexAppServer();