@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/README.md +1 -1
- package/dist/index.cjs +210 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +130 -22
- package/dist/index.d.ts +130 -22
- package/dist/index.js +209 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`@janole/ai-sdk-provider-codex-asp` is a [Vercel AI SDK](https://ai-sdk.dev/) v6 custom provider for the Codex App Server Protocol.
|
|
4
4
|
|
|
5
|
-
Status: POC feature-complete for language model usage. Currently tested with [codex-cli](https://github.com/openai/codex/releases/tag/rust-v0.
|
|
5
|
+
Status: POC feature-complete for language model usage. Currently tested with [codex-cli](https://github.com/openai/codex/releases/tag/rust-v0.106.0) 0.106.0.
|
|
6
6
|
|
|
7
7
|
- `LanguageModelV3` provider implementation
|
|
8
8
|
- Streaming (`streamText`) and non-streaming (`generateText`)
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var child_process = require('child_process');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
var promises = require('fs/promises');
|
|
6
|
+
var os = require('os');
|
|
7
|
+
var path = require('path');
|
|
8
|
+
var url = require('url');
|
|
4
9
|
var provider = require('@ai-sdk/provider');
|
|
5
10
|
|
|
6
11
|
// src/utils/object.ts
|
|
@@ -274,6 +279,7 @@ var AppServerClient = class {
|
|
|
274
279
|
// src/client/transport-persistent.ts
|
|
275
280
|
var PersistentTransport = class {
|
|
276
281
|
pool;
|
|
282
|
+
signal;
|
|
277
283
|
worker = null;
|
|
278
284
|
pendingInitializeId = null;
|
|
279
285
|
initializeIntercepted = false;
|
|
@@ -282,9 +288,10 @@ var PersistentTransport = class {
|
|
|
282
288
|
closeListeners = /* @__PURE__ */ new Set();
|
|
283
289
|
constructor(settings) {
|
|
284
290
|
this.pool = settings.pool;
|
|
291
|
+
this.signal = settings.signal;
|
|
285
292
|
}
|
|
286
293
|
async connect() {
|
|
287
|
-
this.worker = this.pool.acquire();
|
|
294
|
+
this.worker = await this.pool.acquire(stripUndefined({ signal: this.signal }));
|
|
288
295
|
await this.worker.ensureConnected();
|
|
289
296
|
}
|
|
290
297
|
disconnect() {
|
|
@@ -724,6 +731,7 @@ var CodexWorker = class {
|
|
|
724
731
|
var CodexWorkerPool = class {
|
|
725
732
|
workers;
|
|
726
733
|
shutdownCalled = false;
|
|
734
|
+
waiters = [];
|
|
727
735
|
constructor(settings) {
|
|
728
736
|
const size = settings.poolSize ?? 1;
|
|
729
737
|
const idleTimeoutMs = settings.idleTimeoutMs ?? 3e5;
|
|
@@ -735,7 +743,7 @@ var CodexWorkerPool = class {
|
|
|
735
743
|
})
|
|
736
744
|
);
|
|
737
745
|
}
|
|
738
|
-
acquire() {
|
|
746
|
+
async acquire(options) {
|
|
739
747
|
if (this.shutdownCalled) {
|
|
740
748
|
throw new CodexProviderError("Worker pool has been shut down.");
|
|
741
749
|
}
|
|
@@ -743,20 +751,61 @@ var CodexWorkerPool = class {
|
|
|
743
751
|
(w) => w.state === "idle" || w.state === "disconnected"
|
|
744
752
|
);
|
|
745
753
|
if (!worker) {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
754
|
+
if (options?.signal?.aborted) {
|
|
755
|
+
throw new CodexProviderError("Worker acquisition aborted while waiting.");
|
|
756
|
+
}
|
|
757
|
+
return new Promise((resolve, reject) => {
|
|
758
|
+
const waiter = {
|
|
759
|
+
resolve,
|
|
760
|
+
reject,
|
|
761
|
+
signal: options?.signal,
|
|
762
|
+
abortHandler: void 0
|
|
763
|
+
};
|
|
764
|
+
if (waiter.signal) {
|
|
765
|
+
waiter.abortHandler = () => {
|
|
766
|
+
this.removeWaiter(waiter);
|
|
767
|
+
waiter.reject(new CodexProviderError("Worker acquisition aborted while waiting."));
|
|
768
|
+
};
|
|
769
|
+
waiter.signal.addEventListener("abort", waiter.abortHandler, { once: true });
|
|
770
|
+
}
|
|
771
|
+
this.waiters.push(waiter);
|
|
772
|
+
});
|
|
749
773
|
}
|
|
750
774
|
worker.acquire();
|
|
751
775
|
return worker;
|
|
752
776
|
}
|
|
753
777
|
release(worker) {
|
|
754
|
-
|
|
778
|
+
const waiter = this.waiters.shift();
|
|
779
|
+
if (waiter) {
|
|
780
|
+
this.clearWaiterAbortHandler(waiter);
|
|
781
|
+
waiter.resolve(worker);
|
|
782
|
+
} else {
|
|
783
|
+
worker.release();
|
|
784
|
+
}
|
|
755
785
|
}
|
|
756
786
|
async shutdown() {
|
|
757
787
|
this.shutdownCalled = true;
|
|
788
|
+
while (this.waiters.length > 0) {
|
|
789
|
+
const waiter = this.waiters.shift();
|
|
790
|
+
this.clearWaiterAbortHandler(waiter);
|
|
791
|
+
waiter.reject(new CodexProviderError("Worker pool has been shut down."));
|
|
792
|
+
}
|
|
758
793
|
await Promise.all(this.workers.map((w) => w.shutdown()));
|
|
759
794
|
}
|
|
795
|
+
removeWaiter(target) {
|
|
796
|
+
const index = this.waiters.indexOf(target);
|
|
797
|
+
if (index >= 0) {
|
|
798
|
+
this.waiters.splice(index, 1);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
/** Remove the abort listener so it doesn't fire after the waiter is already served. */
|
|
802
|
+
clearWaiterAbortHandler(waiter) {
|
|
803
|
+
if (!waiter.signal || !waiter.abortHandler) {
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
waiter.signal.removeEventListener("abort", waiter.abortHandler);
|
|
807
|
+
waiter.abortHandler = void 0;
|
|
808
|
+
}
|
|
760
809
|
};
|
|
761
810
|
|
|
762
811
|
// src/dynamic-tools.ts
|
|
@@ -886,7 +935,7 @@ var DynamicToolsDispatcher = class {
|
|
|
886
935
|
// package.json
|
|
887
936
|
var package_default = {
|
|
888
937
|
name: "@janole/ai-sdk-provider-codex-asp",
|
|
889
|
-
version: "0.2.
|
|
938
|
+
version: "0.2.4"};
|
|
890
939
|
|
|
891
940
|
// src/package-info.ts
|
|
892
941
|
var PACKAGE_NAME = package_default.name;
|
|
@@ -1273,8 +1322,6 @@ var CodexEventMapper = class {
|
|
|
1273
1322
|
return parts;
|
|
1274
1323
|
}
|
|
1275
1324
|
};
|
|
1276
|
-
|
|
1277
|
-
// src/protocol/prompt-mapper.ts
|
|
1278
1325
|
function mapSystemPrompt(prompt) {
|
|
1279
1326
|
const chunks = [];
|
|
1280
1327
|
for (const message of prompt) {
|
|
@@ -1287,8 +1334,111 @@ function mapSystemPrompt(prompt) {
|
|
|
1287
1334
|
}
|
|
1288
1335
|
return chunks.length > 0 ? chunks.join("\n\n") : void 0;
|
|
1289
1336
|
}
|
|
1290
|
-
function
|
|
1291
|
-
|
|
1337
|
+
function textItem(text) {
|
|
1338
|
+
return { type: "text", text, text_elements: [] };
|
|
1339
|
+
}
|
|
1340
|
+
var MEDIA_TYPE_TO_EXT = {
|
|
1341
|
+
"image/png": ".png",
|
|
1342
|
+
"image/jpeg": ".jpg",
|
|
1343
|
+
"image/gif": ".gif",
|
|
1344
|
+
"image/webp": ".webp",
|
|
1345
|
+
"image/svg+xml": ".svg",
|
|
1346
|
+
"image/bmp": ".bmp",
|
|
1347
|
+
"image/tiff": ".tiff"
|
|
1348
|
+
};
|
|
1349
|
+
function extensionForMediaType(mediaType) {
|
|
1350
|
+
return MEDIA_TYPE_TO_EXT[mediaType] ?? ".bin";
|
|
1351
|
+
}
|
|
1352
|
+
var LocalFileWriter = class {
|
|
1353
|
+
async write(data, mediaType) {
|
|
1354
|
+
const ext = extensionForMediaType(mediaType);
|
|
1355
|
+
const filename = `codex-ai-sdk-${crypto.randomUUID()}${ext}`;
|
|
1356
|
+
const filepath = path.join(os.tmpdir(), filename);
|
|
1357
|
+
const buffer = typeof data === "string" ? Buffer.from(data, "base64") : data;
|
|
1358
|
+
await promises.writeFile(filepath, buffer);
|
|
1359
|
+
return url.pathToFileURL(filepath);
|
|
1360
|
+
}
|
|
1361
|
+
async cleanup(urls) {
|
|
1362
|
+
await Promise.allSettled(
|
|
1363
|
+
urls.filter((u) => u.protocol === "file:").map((u) => promises.unlink(u))
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
var PromptFileResolver = class {
|
|
1368
|
+
writer;
|
|
1369
|
+
written = [];
|
|
1370
|
+
constructor(writer) {
|
|
1371
|
+
this.writer = writer ?? new LocalFileWriter();
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Resolve inline file data and map user content to Codex input items.
|
|
1375
|
+
*
|
|
1376
|
+
* - Inline image data (base64 / Uint8Array) is written via the
|
|
1377
|
+
* {@link FileWriter} and converted to `localImage` or `image` items.
|
|
1378
|
+
* - URL-based image file parts are converted directly.
|
|
1379
|
+
* - Inline text file data is decoded and inlined as text.
|
|
1380
|
+
* - Unsupported media types are silently skipped.
|
|
1381
|
+
*
|
|
1382
|
+
* @param isResume - When true only the last user message is extracted.
|
|
1383
|
+
* When false (fresh thread) all user text is accumulated with images
|
|
1384
|
+
* flushing the text buffer to preserve ordering.
|
|
1385
|
+
*/
|
|
1386
|
+
async resolve(prompt, isResume = false) {
|
|
1387
|
+
if (isResume) {
|
|
1388
|
+
return this.resolveResumed(prompt);
|
|
1389
|
+
}
|
|
1390
|
+
return this.resolveFresh(prompt);
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Remove all files created by previous {@link resolve} calls.
|
|
1394
|
+
* Best-effort — never throws.
|
|
1395
|
+
*/
|
|
1396
|
+
async cleanup() {
|
|
1397
|
+
const urls = this.written.splice(0);
|
|
1398
|
+
if (urls.length > 0) {
|
|
1399
|
+
await this.writer.cleanup(urls);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Convert a resolved image URL to a Codex input item.
|
|
1404
|
+
*/
|
|
1405
|
+
mapImageUrl(mediaType, data) {
|
|
1406
|
+
if (!mediaType.startsWith("image/")) {
|
|
1407
|
+
return null;
|
|
1408
|
+
}
|
|
1409
|
+
if (data.protocol === "file:") {
|
|
1410
|
+
return { type: "localImage", path: url.fileURLToPath(data) };
|
|
1411
|
+
}
|
|
1412
|
+
return { type: "image", url: data.href };
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Resolve a single file part: write inline data via the writer, then
|
|
1416
|
+
* convert to a Codex input item. Text files are decoded and returned
|
|
1417
|
+
* as text items. Returns `null` for unsupported media types.
|
|
1418
|
+
*/
|
|
1419
|
+
async resolveFilePart(part) {
|
|
1420
|
+
const { mediaType, data } = part;
|
|
1421
|
+
if (mediaType.startsWith("text/")) {
|
|
1422
|
+
if (data instanceof URL) {
|
|
1423
|
+
return textItem(data.href);
|
|
1424
|
+
}
|
|
1425
|
+
const text = typeof data === "string" ? Buffer.from(data, "base64").toString("utf-8") : new TextDecoder().decode(data);
|
|
1426
|
+
return textItem(text);
|
|
1427
|
+
}
|
|
1428
|
+
if (mediaType.startsWith("image/") && !(data instanceof URL)) {
|
|
1429
|
+
const url = await this.writer.write(data, mediaType);
|
|
1430
|
+
this.written.push(url);
|
|
1431
|
+
return this.mapImageUrl(mediaType, url);
|
|
1432
|
+
}
|
|
1433
|
+
if (data instanceof URL) {
|
|
1434
|
+
return this.mapImageUrl(mediaType, data);
|
|
1435
|
+
}
|
|
1436
|
+
return null;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Resume path: extract parts from the last user message individually.
|
|
1440
|
+
*/
|
|
1441
|
+
async resolveResumed(prompt) {
|
|
1292
1442
|
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
1293
1443
|
const message = prompt[i];
|
|
1294
1444
|
if (message?.role === "user") {
|
|
@@ -1297,7 +1447,12 @@ function mapPromptToTurnInput(prompt, isResume = false) {
|
|
|
1297
1447
|
if (part.type === "text") {
|
|
1298
1448
|
const text = part.text.trim();
|
|
1299
1449
|
if (text.length > 0) {
|
|
1300
|
-
items.push(
|
|
1450
|
+
items.push(textItem(text));
|
|
1451
|
+
}
|
|
1452
|
+
} else if (part.type === "file") {
|
|
1453
|
+
const mapped = await this.resolveFilePart(part);
|
|
1454
|
+
if (mapped) {
|
|
1455
|
+
items.push(mapped);
|
|
1301
1456
|
}
|
|
1302
1457
|
}
|
|
1303
1458
|
}
|
|
@@ -1306,21 +1461,45 @@ function mapPromptToTurnInput(prompt, isResume = false) {
|
|
|
1306
1461
|
}
|
|
1307
1462
|
return [];
|
|
1308
1463
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1464
|
+
/**
|
|
1465
|
+
* Fresh thread path: accumulate text chunks across all user messages,
|
|
1466
|
+
* flushing before each image to preserve ordering.
|
|
1467
|
+
*/
|
|
1468
|
+
async resolveFresh(prompt) {
|
|
1469
|
+
const items = [];
|
|
1470
|
+
const textChunks = [];
|
|
1471
|
+
const flushText = () => {
|
|
1472
|
+
if (textChunks.length > 0) {
|
|
1473
|
+
items.push(textItem(textChunks.join("\n\n")));
|
|
1474
|
+
textChunks.length = 0;
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
for (const message of prompt) {
|
|
1478
|
+
if (message.role === "user") {
|
|
1479
|
+
for (const part of message.content) {
|
|
1480
|
+
if (part.type === "text") {
|
|
1481
|
+
const text = part.text.trim();
|
|
1482
|
+
if (text.length > 0) {
|
|
1483
|
+
textChunks.push(text);
|
|
1484
|
+
}
|
|
1485
|
+
} else if (part.type === "file") {
|
|
1486
|
+
const mapped = await this.resolveFilePart(part);
|
|
1487
|
+
if (mapped) {
|
|
1488
|
+
if (mapped.type === "text") {
|
|
1489
|
+
textChunks.push(mapped.text);
|
|
1490
|
+
} else {
|
|
1491
|
+
flushText();
|
|
1492
|
+
items.push(mapped);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1317
1495
|
}
|
|
1318
1496
|
}
|
|
1319
1497
|
}
|
|
1320
1498
|
}
|
|
1499
|
+
flushText();
|
|
1500
|
+
return items;
|
|
1321
1501
|
}
|
|
1322
|
-
|
|
1323
|
-
}
|
|
1502
|
+
};
|
|
1324
1503
|
|
|
1325
1504
|
// src/model.ts
|
|
1326
1505
|
function createEmptyUsage() {
|
|
@@ -1555,7 +1734,7 @@ var CodexLanguageModel = class {
|
|
|
1555
1734
|
});
|
|
1556
1735
|
}
|
|
1557
1736
|
doStream(options) {
|
|
1558
|
-
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);
|
|
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);
|
|
1559
1738
|
const packetLogger = this.config.providerSettings.debug?.logPackets === true ? this.config.providerSettings.debug.logger ?? ((packet) => {
|
|
1560
1739
|
if (packet.direction === "inbound") {
|
|
1561
1740
|
console.debug("[codex packet]", packet.message);
|
|
@@ -1587,6 +1766,7 @@ var CodexLanguageModel = class {
|
|
|
1587
1766
|
debugLog?.("outbound", "turn/interrupt", interruptParams);
|
|
1588
1767
|
await client.request("turn/interrupt", interruptParams, interruptTimeoutMs);
|
|
1589
1768
|
};
|
|
1769
|
+
const fileResolver = new PromptFileResolver();
|
|
1590
1770
|
const stream = new ReadableStream({
|
|
1591
1771
|
start: (controller) => {
|
|
1592
1772
|
let closed = false;
|
|
@@ -1599,6 +1779,7 @@ var CodexLanguageModel = class {
|
|
|
1599
1779
|
try {
|
|
1600
1780
|
controller.close();
|
|
1601
1781
|
} finally {
|
|
1782
|
+
await fileResolver.cleanup();
|
|
1602
1783
|
await client.disconnect();
|
|
1603
1784
|
}
|
|
1604
1785
|
};
|
|
@@ -1610,6 +1791,7 @@ var CodexLanguageModel = class {
|
|
|
1610
1791
|
try {
|
|
1611
1792
|
controller.close();
|
|
1612
1793
|
} finally {
|
|
1794
|
+
await fileResolver.cleanup();
|
|
1613
1795
|
await client.disconnect();
|
|
1614
1796
|
}
|
|
1615
1797
|
};
|
|
@@ -1834,7 +2016,7 @@ var CodexLanguageModel = class {
|
|
|
1834
2016
|
closeSuccessfully
|
|
1835
2017
|
);
|
|
1836
2018
|
}
|
|
1837
|
-
const turnInput =
|
|
2019
|
+
const turnInput = await fileResolver.resolve(options.prompt, !!resumeThreadId);
|
|
1838
2020
|
const turnStartParams = stripUndefined({
|
|
1839
2021
|
threadId,
|
|
1840
2022
|
input: turnInput,
|
|
@@ -1858,6 +2040,7 @@ var CodexLanguageModel = class {
|
|
|
1858
2040
|
await interruptTurnIfPossible();
|
|
1859
2041
|
} catch {
|
|
1860
2042
|
}
|
|
2043
|
+
await fileResolver.cleanup();
|
|
1861
2044
|
await client.disconnect();
|
|
1862
2045
|
}
|
|
1863
2046
|
});
|
|
@@ -1969,7 +2152,7 @@ function createCodexAppServer(settings = {}) {
|
|
|
1969
2152
|
});
|
|
1970
2153
|
}
|
|
1971
2154
|
const persistentPool = persistentPoolHandle?.pool ?? null;
|
|
1972
|
-
const effectiveTransportFactory = persistentPool ? () => new PersistentTransport({ pool: persistentPool }) : baseTransportFactory;
|
|
2155
|
+
const effectiveTransportFactory = persistentPool ? (signal) => new PersistentTransport(stripUndefined({ pool: persistentPool, signal })) : baseTransportFactory;
|
|
1973
2156
|
const resolvedSettings = Object.freeze(stripUndefined({
|
|
1974
2157
|
defaultModel: settings.defaultModel,
|
|
1975
2158
|
experimentalApi: settings.experimentalApi,
|
|
@@ -2039,16 +2222,17 @@ exports.CodexWorker = CodexWorker;
|
|
|
2039
2222
|
exports.CodexWorkerPool = CodexWorkerPool;
|
|
2040
2223
|
exports.DynamicToolsDispatcher = DynamicToolsDispatcher;
|
|
2041
2224
|
exports.JsonRpcError = JsonRpcError;
|
|
2225
|
+
exports.LocalFileWriter = LocalFileWriter;
|
|
2042
2226
|
exports.PACKAGE_NAME = PACKAGE_NAME;
|
|
2043
2227
|
exports.PACKAGE_VERSION = PACKAGE_VERSION;
|
|
2044
2228
|
exports.PersistentTransport = PersistentTransport;
|
|
2229
|
+
exports.PromptFileResolver = PromptFileResolver;
|
|
2045
2230
|
exports.StdioTransport = StdioTransport;
|
|
2046
2231
|
exports.WebSocketTransport = WebSocketTransport;
|
|
2047
2232
|
exports.codexAppServer = codexAppServer;
|
|
2048
2233
|
exports.codexProviderMetadata = codexProviderMetadata;
|
|
2049
2234
|
exports.createCodexAppServer = createCodexAppServer;
|
|
2050
2235
|
exports.createCodexProvider = createCodexProvider;
|
|
2051
|
-
exports.mapPromptToTurnInput = mapPromptToTurnInput;
|
|
2052
2236
|
exports.mapSystemPrompt = mapSystemPrompt;
|
|
2053
2237
|
exports.withProviderMetadata = withProviderMetadata;
|
|
2054
2238
|
//# sourceMappingURL=index.cjs.map
|