@janole/ai-sdk-provider-codex-asp 0.2.2 → 0.2.4

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.js CHANGED
@@ -1,4 +1,9 @@
1
1
  import { spawn } from 'child_process';
2
+ import { randomUUID } from 'crypto';
3
+ import { writeFile, unlink } from 'fs/promises';
4
+ import { tmpdir } from 'os';
5
+ import { join } from 'path';
6
+ import { fileURLToPath, pathToFileURL } from 'url';
2
7
  import { NoSuchModelError } from '@ai-sdk/provider';
3
8
 
4
9
  // src/utils/object.ts
@@ -272,6 +277,7 @@ var AppServerClient = class {
272
277
  // src/client/transport-persistent.ts
273
278
  var PersistentTransport = class {
274
279
  pool;
280
+ signal;
275
281
  worker = null;
276
282
  pendingInitializeId = null;
277
283
  initializeIntercepted = false;
@@ -280,9 +286,10 @@ var PersistentTransport = class {
280
286
  closeListeners = /* @__PURE__ */ new Set();
281
287
  constructor(settings) {
282
288
  this.pool = settings.pool;
289
+ this.signal = settings.signal;
283
290
  }
284
291
  async connect() {
285
- this.worker = this.pool.acquire();
292
+ this.worker = await this.pool.acquire(stripUndefined({ signal: this.signal }));
286
293
  await this.worker.ensureConnected();
287
294
  }
288
295
  disconnect() {
@@ -722,6 +729,7 @@ var CodexWorker = class {
722
729
  var CodexWorkerPool = class {
723
730
  workers;
724
731
  shutdownCalled = false;
732
+ waiters = [];
725
733
  constructor(settings) {
726
734
  const size = settings.poolSize ?? 1;
727
735
  const idleTimeoutMs = settings.idleTimeoutMs ?? 3e5;
@@ -733,7 +741,7 @@ var CodexWorkerPool = class {
733
741
  })
734
742
  );
735
743
  }
736
- acquire() {
744
+ async acquire(options) {
737
745
  if (this.shutdownCalled) {
738
746
  throw new CodexProviderError("Worker pool has been shut down.");
739
747
  }
@@ -741,20 +749,61 @@ var CodexWorkerPool = class {
741
749
  (w) => w.state === "idle" || w.state === "disconnected"
742
750
  );
743
751
  if (!worker) {
744
- throw new CodexProviderError(
745
- "All workers are busy. Try again later or increase poolSize."
746
- );
752
+ if (options?.signal?.aborted) {
753
+ throw new CodexProviderError("Worker acquisition aborted while waiting.");
754
+ }
755
+ return new Promise((resolve, reject) => {
756
+ const waiter = {
757
+ resolve,
758
+ reject,
759
+ signal: options?.signal,
760
+ abortHandler: void 0
761
+ };
762
+ if (waiter.signal) {
763
+ waiter.abortHandler = () => {
764
+ this.removeWaiter(waiter);
765
+ waiter.reject(new CodexProviderError("Worker acquisition aborted while waiting."));
766
+ };
767
+ waiter.signal.addEventListener("abort", waiter.abortHandler, { once: true });
768
+ }
769
+ this.waiters.push(waiter);
770
+ });
747
771
  }
748
772
  worker.acquire();
749
773
  return worker;
750
774
  }
751
775
  release(worker) {
752
- worker.release();
776
+ const waiter = this.waiters.shift();
777
+ if (waiter) {
778
+ this.clearWaiterAbortHandler(waiter);
779
+ waiter.resolve(worker);
780
+ } else {
781
+ worker.release();
782
+ }
753
783
  }
754
784
  async shutdown() {
755
785
  this.shutdownCalled = true;
786
+ while (this.waiters.length > 0) {
787
+ const waiter = this.waiters.shift();
788
+ this.clearWaiterAbortHandler(waiter);
789
+ waiter.reject(new CodexProviderError("Worker pool has been shut down."));
790
+ }
756
791
  await Promise.all(this.workers.map((w) => w.shutdown()));
757
792
  }
793
+ removeWaiter(target) {
794
+ const index = this.waiters.indexOf(target);
795
+ if (index >= 0) {
796
+ this.waiters.splice(index, 1);
797
+ }
798
+ }
799
+ /** Remove the abort listener so it doesn't fire after the waiter is already served. */
800
+ clearWaiterAbortHandler(waiter) {
801
+ if (!waiter.signal || !waiter.abortHandler) {
802
+ return;
803
+ }
804
+ waiter.signal.removeEventListener("abort", waiter.abortHandler);
805
+ waiter.abortHandler = void 0;
806
+ }
758
807
  };
759
808
 
760
809
  // src/dynamic-tools.ts
@@ -884,7 +933,7 @@ var DynamicToolsDispatcher = class {
884
933
  // package.json
885
934
  var package_default = {
886
935
  name: "@janole/ai-sdk-provider-codex-asp",
887
- version: "0.2.2"};
936
+ version: "0.2.4"};
888
937
 
889
938
  // src/package-info.ts
890
939
  var PACKAGE_NAME = package_default.name;
@@ -1271,8 +1320,6 @@ var CodexEventMapper = class {
1271
1320
  return parts;
1272
1321
  }
1273
1322
  };
1274
-
1275
- // src/protocol/prompt-mapper.ts
1276
1323
  function mapSystemPrompt(prompt) {
1277
1324
  const chunks = [];
1278
1325
  for (const message of prompt) {
@@ -1285,8 +1332,111 @@ function mapSystemPrompt(prompt) {
1285
1332
  }
1286
1333
  return chunks.length > 0 ? chunks.join("\n\n") : void 0;
1287
1334
  }
1288
- function mapPromptToTurnInput(prompt, isResume = false) {
1289
- if (isResume) {
1335
+ function textItem(text) {
1336
+ return { type: "text", text, text_elements: [] };
1337
+ }
1338
+ var MEDIA_TYPE_TO_EXT = {
1339
+ "image/png": ".png",
1340
+ "image/jpeg": ".jpg",
1341
+ "image/gif": ".gif",
1342
+ "image/webp": ".webp",
1343
+ "image/svg+xml": ".svg",
1344
+ "image/bmp": ".bmp",
1345
+ "image/tiff": ".tiff"
1346
+ };
1347
+ function extensionForMediaType(mediaType) {
1348
+ return MEDIA_TYPE_TO_EXT[mediaType] ?? ".bin";
1349
+ }
1350
+ var LocalFileWriter = class {
1351
+ async write(data, mediaType) {
1352
+ const ext = extensionForMediaType(mediaType);
1353
+ const filename = `codex-ai-sdk-${randomUUID()}${ext}`;
1354
+ const filepath = join(tmpdir(), filename);
1355
+ const buffer = typeof data === "string" ? Buffer.from(data, "base64") : data;
1356
+ await writeFile(filepath, buffer);
1357
+ return pathToFileURL(filepath);
1358
+ }
1359
+ async cleanup(urls) {
1360
+ await Promise.allSettled(
1361
+ urls.filter((u) => u.protocol === "file:").map((u) => unlink(u))
1362
+ );
1363
+ }
1364
+ };
1365
+ var PromptFileResolver = class {
1366
+ writer;
1367
+ written = [];
1368
+ constructor(writer) {
1369
+ this.writer = writer ?? new LocalFileWriter();
1370
+ }
1371
+ /**
1372
+ * Resolve inline file data and map user content to Codex input items.
1373
+ *
1374
+ * - Inline image data (base64 / Uint8Array) is written via the
1375
+ * {@link FileWriter} and converted to `localImage` or `image` items.
1376
+ * - URL-based image file parts are converted directly.
1377
+ * - Inline text file data is decoded and inlined as text.
1378
+ * - Unsupported media types are silently skipped.
1379
+ *
1380
+ * @param isResume - When true only the last user message is extracted.
1381
+ * When false (fresh thread) all user text is accumulated with images
1382
+ * flushing the text buffer to preserve ordering.
1383
+ */
1384
+ async resolve(prompt, isResume = false) {
1385
+ if (isResume) {
1386
+ return this.resolveResumed(prompt);
1387
+ }
1388
+ return this.resolveFresh(prompt);
1389
+ }
1390
+ /**
1391
+ * Remove all files created by previous {@link resolve} calls.
1392
+ * Best-effort — never throws.
1393
+ */
1394
+ async cleanup() {
1395
+ const urls = this.written.splice(0);
1396
+ if (urls.length > 0) {
1397
+ await this.writer.cleanup(urls);
1398
+ }
1399
+ }
1400
+ /**
1401
+ * Convert a resolved image URL to a Codex input item.
1402
+ */
1403
+ mapImageUrl(mediaType, data) {
1404
+ if (!mediaType.startsWith("image/")) {
1405
+ return null;
1406
+ }
1407
+ if (data.protocol === "file:") {
1408
+ return { type: "localImage", path: fileURLToPath(data) };
1409
+ }
1410
+ return { type: "image", url: data.href };
1411
+ }
1412
+ /**
1413
+ * Resolve a single file part: write inline data via the writer, then
1414
+ * convert to a Codex input item. Text files are decoded and returned
1415
+ * as text items. Returns `null` for unsupported media types.
1416
+ */
1417
+ async resolveFilePart(part) {
1418
+ const { mediaType, data } = part;
1419
+ if (mediaType.startsWith("text/")) {
1420
+ if (data instanceof URL) {
1421
+ return textItem(data.href);
1422
+ }
1423
+ const text = typeof data === "string" ? Buffer.from(data, "base64").toString("utf-8") : new TextDecoder().decode(data);
1424
+ return textItem(text);
1425
+ }
1426
+ if (mediaType.startsWith("image/") && !(data instanceof URL)) {
1427
+ const url = await this.writer.write(data, mediaType);
1428
+ this.written.push(url);
1429
+ return this.mapImageUrl(mediaType, url);
1430
+ }
1431
+ if (data instanceof URL) {
1432
+ return this.mapImageUrl(mediaType, data);
1433
+ }
1434
+ return null;
1435
+ }
1436
+ /**
1437
+ * Resume path: extract parts from the last user message individually.
1438
+ */
1439
+ async resolveResumed(prompt) {
1290
1440
  for (let i = prompt.length - 1; i >= 0; i--) {
1291
1441
  const message = prompt[i];
1292
1442
  if (message?.role === "user") {
@@ -1295,7 +1445,12 @@ function mapPromptToTurnInput(prompt, isResume = false) {
1295
1445
  if (part.type === "text") {
1296
1446
  const text = part.text.trim();
1297
1447
  if (text.length > 0) {
1298
- items.push({ type: "text", text, text_elements: [] });
1448
+ items.push(textItem(text));
1449
+ }
1450
+ } else if (part.type === "file") {
1451
+ const mapped = await this.resolveFilePart(part);
1452
+ if (mapped) {
1453
+ items.push(mapped);
1299
1454
  }
1300
1455
  }
1301
1456
  }
@@ -1304,21 +1459,45 @@ function mapPromptToTurnInput(prompt, isResume = false) {
1304
1459
  }
1305
1460
  return [];
1306
1461
  }
1307
- const chunks = [];
1308
- for (const message of prompt) {
1309
- if (message.role === "user") {
1310
- for (const part of message.content) {
1311
- if (part.type === "text") {
1312
- const text = part.text.trim();
1313
- if (text.length > 0) {
1314
- chunks.push(text);
1462
+ /**
1463
+ * Fresh thread path: accumulate text chunks across all user messages,
1464
+ * flushing before each image to preserve ordering.
1465
+ */
1466
+ async resolveFresh(prompt) {
1467
+ const items = [];
1468
+ const textChunks = [];
1469
+ const flushText = () => {
1470
+ if (textChunks.length > 0) {
1471
+ items.push(textItem(textChunks.join("\n\n")));
1472
+ textChunks.length = 0;
1473
+ }
1474
+ };
1475
+ for (const message of prompt) {
1476
+ if (message.role === "user") {
1477
+ for (const part of message.content) {
1478
+ if (part.type === "text") {
1479
+ const text = part.text.trim();
1480
+ if (text.length > 0) {
1481
+ textChunks.push(text);
1482
+ }
1483
+ } else if (part.type === "file") {
1484
+ const mapped = await this.resolveFilePart(part);
1485
+ if (mapped) {
1486
+ if (mapped.type === "text") {
1487
+ textChunks.push(mapped.text);
1488
+ } else {
1489
+ flushText();
1490
+ items.push(mapped);
1491
+ }
1492
+ }
1315
1493
  }
1316
1494
  }
1317
1495
  }
1318
1496
  }
1497
+ flushText();
1498
+ return items;
1319
1499
  }
1320
- return [{ type: "text", text: chunks.join("\n\n"), text_elements: [] }];
1321
- }
1500
+ };
1322
1501
 
1323
1502
  // src/model.ts
1324
1503
  function createEmptyUsage() {
@@ -1553,7 +1732,7 @@ var CodexLanguageModel = class {
1553
1732
  });
1554
1733
  }
1555
1734
  doStream(options) {
1556
- const transport = this.config.providerSettings.transportFactory ? this.config.providerSettings.transportFactory() : this.config.providerSettings.transport?.type === "websocket" ? new WebSocketTransport(this.config.providerSettings.transport.websocket) : new StdioTransport(this.config.providerSettings.transport?.stdio);
1735
+ 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);
1557
1736
  const packetLogger = this.config.providerSettings.debug?.logPackets === true ? this.config.providerSettings.debug.logger ?? ((packet) => {
1558
1737
  if (packet.direction === "inbound") {
1559
1738
  console.debug("[codex packet]", packet.message);
@@ -1585,6 +1764,7 @@ var CodexLanguageModel = class {
1585
1764
  debugLog?.("outbound", "turn/interrupt", interruptParams);
1586
1765
  await client.request("turn/interrupt", interruptParams, interruptTimeoutMs);
1587
1766
  };
1767
+ const fileResolver = new PromptFileResolver();
1588
1768
  const stream = new ReadableStream({
1589
1769
  start: (controller) => {
1590
1770
  let closed = false;
@@ -1597,6 +1777,7 @@ var CodexLanguageModel = class {
1597
1777
  try {
1598
1778
  controller.close();
1599
1779
  } finally {
1780
+ await fileResolver.cleanup();
1600
1781
  await client.disconnect();
1601
1782
  }
1602
1783
  };
@@ -1608,6 +1789,7 @@ var CodexLanguageModel = class {
1608
1789
  try {
1609
1790
  controller.close();
1610
1791
  } finally {
1792
+ await fileResolver.cleanup();
1611
1793
  await client.disconnect();
1612
1794
  }
1613
1795
  };
@@ -1832,7 +2014,7 @@ var CodexLanguageModel = class {
1832
2014
  closeSuccessfully
1833
2015
  );
1834
2016
  }
1835
- const turnInput = mapPromptToTurnInput(options.prompt, !!resumeThreadId);
2017
+ const turnInput = await fileResolver.resolve(options.prompt, !!resumeThreadId);
1836
2018
  const turnStartParams = stripUndefined({
1837
2019
  threadId,
1838
2020
  input: turnInput,
@@ -1856,6 +2038,7 @@ var CodexLanguageModel = class {
1856
2038
  await interruptTurnIfPossible();
1857
2039
  } catch {
1858
2040
  }
2041
+ await fileResolver.cleanup();
1859
2042
  await client.disconnect();
1860
2043
  }
1861
2044
  });
@@ -1967,7 +2150,7 @@ function createCodexAppServer(settings = {}) {
1967
2150
  });
1968
2151
  }
1969
2152
  const persistentPool = persistentPoolHandle?.pool ?? null;
1970
- const effectiveTransportFactory = persistentPool ? () => new PersistentTransport({ pool: persistentPool }) : baseTransportFactory;
2153
+ const effectiveTransportFactory = persistentPool ? (signal) => new PersistentTransport(stripUndefined({ pool: persistentPool, signal })) : baseTransportFactory;
1971
2154
  const resolvedSettings = Object.freeze(stripUndefined({
1972
2155
  defaultModel: settings.defaultModel,
1973
2156
  experimentalApi: settings.experimentalApi,
@@ -2026,6 +2209,6 @@ function createCodexAppServer(settings = {}) {
2026
2209
  var codexAppServer = createCodexAppServer();
2027
2210
  var createCodexProvider = createCodexAppServer;
2028
2211
 
2029
- export { AppServerClient, ApprovalsDispatcher, CODEX_PROVIDER_ID, CodexEventMapper, CodexLanguageModel, CodexNotImplementedError, CodexProviderError, CodexWorker, CodexWorkerPool, DynamicToolsDispatcher, JsonRpcError, PACKAGE_NAME, PACKAGE_VERSION, PersistentTransport, StdioTransport, WebSocketTransport, codexAppServer, codexProviderMetadata, createCodexAppServer, createCodexProvider, mapPromptToTurnInput, mapSystemPrompt, withProviderMetadata };
2212
+ export { AppServerClient, ApprovalsDispatcher, CODEX_PROVIDER_ID, CodexEventMapper, CodexLanguageModel, CodexNotImplementedError, CodexProviderError, CodexWorker, CodexWorkerPool, DynamicToolsDispatcher, JsonRpcError, LocalFileWriter, PACKAGE_NAME, PACKAGE_VERSION, PersistentTransport, PromptFileResolver, StdioTransport, WebSocketTransport, codexAppServer, codexProviderMetadata, createCodexAppServer, createCodexProvider, mapSystemPrompt, withProviderMetadata };
2030
2213
  //# sourceMappingURL=index.js.map
2031
2214
  //# sourceMappingURL=index.js.map