@combycode/llm-sdk 1.1.0 → 1.3.0
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/CHANGELOG.md +50 -0
- package/README.md +24 -0
- package/dist/agent/guardrail-types.d.ts +25 -0
- package/dist/agent/loop-config.d.ts +5 -1
- package/dist/agent/loop.d.ts +11 -1
- package/dist/agent/types.d.ts +8 -0
- package/dist/helpers/one-shot.d.ts +8 -1
- package/dist/index.browser.js +341 -23
- package/dist/index.d.ts +2 -1
- package/dist/index.js +341 -23
- package/dist/llm/client.d.ts +12 -1
- package/dist/llm/files/retrieve.d.ts +44 -0
- package/dist/llm/providers/anthropic/messages.d.ts +3 -0
- package/dist/llm/providers/google/generate.d.ts +7 -0
- package/dist/llm/providers/google/interactions.d.ts +2 -0
- package/dist/llm/providers/openai/completions.d.ts +2 -0
- package/dist/llm/providers/openai/responses.d.ts +3 -0
- package/dist/llm/types/provider.d.ts +9 -1
- package/dist/llm/types/response.d.ts +7 -0
- package/dist/llm/types/stream.d.ts +9 -1
- package/dist/network/types.d.ts +4 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2451,7 +2451,7 @@ var QueueState = class {
|
|
|
2451
2451
|
});
|
|
2452
2452
|
this.processed++;
|
|
2453
2453
|
if (response.ok) {
|
|
2454
|
-
const body = await parseResponseBody(response, entry.request.responseType ?? "json");
|
|
2454
|
+
const body = entry.request.responseType === "stream" ? response.body : await parseResponseBody(response, entry.request.responseType ?? "json");
|
|
2455
2455
|
this.semaphore.release();
|
|
2456
2456
|
entry.resolve({ status: response.status, headers: resHeaders, body });
|
|
2457
2457
|
return;
|
|
@@ -22354,6 +22354,160 @@ async function* wrapParallel(raw, interval, moderate2) {
|
|
|
22354
22354
|
}
|
|
22355
22355
|
}
|
|
22356
22356
|
|
|
22357
|
+
// src/llm/files/retrieve.ts
|
|
22358
|
+
var DEFAULT_BASE = {
|
|
22359
|
+
anthropic: "https://api.anthropic.com",
|
|
22360
|
+
openai: "https://api.openai.com",
|
|
22361
|
+
xai: "https://api.x.ai",
|
|
22362
|
+
google: "https://generativelanguage.googleapis.com",
|
|
22363
|
+
openrouter: "https://openrouter.ai/api"
|
|
22364
|
+
};
|
|
22365
|
+
var OCTET_STREAM = "application/octet-stream";
|
|
22366
|
+
function contentRequest(ctx, file) {
|
|
22367
|
+
const base = ctx.baseURL ?? DEFAULT_BASE[ctx.provider];
|
|
22368
|
+
const id = file.id;
|
|
22369
|
+
if (ctx.provider === "anthropic") {
|
|
22370
|
+
return {
|
|
22371
|
+
url: `${base}/v1/files/${id}/content?beta=true`,
|
|
22372
|
+
headers: {
|
|
22373
|
+
"x-api-key": ctx.apiKey,
|
|
22374
|
+
"anthropic-version": "2023-06-01",
|
|
22375
|
+
"anthropic-beta": "files-api-2025-04-14",
|
|
22376
|
+
accept: "application/binary"
|
|
22377
|
+
}
|
|
22378
|
+
};
|
|
22379
|
+
}
|
|
22380
|
+
if (ctx.provider === "openai" || ctx.provider === "xai" || ctx.provider === "openrouter") {
|
|
22381
|
+
const containerId = file.ref?.containerId;
|
|
22382
|
+
const path = containerId ? `/v1/containers/${containerId}/files/${id}/content` : `/v1/files/${id}/content`;
|
|
22383
|
+
return { url: `${base}${path}`, headers: { authorization: `Bearer ${ctx.apiKey}` } };
|
|
22384
|
+
}
|
|
22385
|
+
if (ctx.provider === "google") {
|
|
22386
|
+
return {
|
|
22387
|
+
url: `${base}/v1beta/files/${id}:download?alt=media`,
|
|
22388
|
+
headers: { "x-goog-api-key": ctx.apiKey }
|
|
22389
|
+
};
|
|
22390
|
+
}
|
|
22391
|
+
throw new Error(`retrieveFile: no file-content endpoint for provider "${ctx.provider}"`);
|
|
22392
|
+
}
|
|
22393
|
+
function providerAuth(ctx, url) {
|
|
22394
|
+
const base = ctx.baseURL ?? DEFAULT_BASE[ctx.provider];
|
|
22395
|
+
if (!url.startsWith(base)) return {};
|
|
22396
|
+
if (ctx.provider === "anthropic") {
|
|
22397
|
+
return { "x-api-key": ctx.apiKey, "anthropic-version": "2023-06-01" };
|
|
22398
|
+
}
|
|
22399
|
+
if (ctx.provider === "google") return { "x-goog-api-key": ctx.apiKey };
|
|
22400
|
+
return { authorization: `Bearer ${ctx.apiKey}` };
|
|
22401
|
+
}
|
|
22402
|
+
async function fetchFileResponse(ctx, file, responseType) {
|
|
22403
|
+
if (file.url) {
|
|
22404
|
+
return ctx.fetch({
|
|
22405
|
+
url: file.url,
|
|
22406
|
+
method: "GET",
|
|
22407
|
+
headers: providerAuth(ctx, file.url),
|
|
22408
|
+
body: void 0,
|
|
22409
|
+
provider: ctx.provider,
|
|
22410
|
+
model: "files",
|
|
22411
|
+
responseType
|
|
22412
|
+
});
|
|
22413
|
+
}
|
|
22414
|
+
if (file.id) {
|
|
22415
|
+
const { url, headers } = contentRequest(ctx, file);
|
|
22416
|
+
return ctx.fetch({
|
|
22417
|
+
url,
|
|
22418
|
+
method: "GET",
|
|
22419
|
+
headers,
|
|
22420
|
+
body: void 0,
|
|
22421
|
+
provider: ctx.provider,
|
|
22422
|
+
model: "files",
|
|
22423
|
+
responseType
|
|
22424
|
+
});
|
|
22425
|
+
}
|
|
22426
|
+
throw new Error("retrieveFile: FileOutput has neither `data`, `url`, nor `id`");
|
|
22427
|
+
}
|
|
22428
|
+
function bytesToBlob(bytes, type) {
|
|
22429
|
+
const ab = new ArrayBuffer(bytes.byteLength);
|
|
22430
|
+
new Uint8Array(ab).set(bytes);
|
|
22431
|
+
return new Blob([ab], { type });
|
|
22432
|
+
}
|
|
22433
|
+
function singleChunkStream(bytes) {
|
|
22434
|
+
return new ReadableStream({
|
|
22435
|
+
start(controller) {
|
|
22436
|
+
controller.enqueue(bytes);
|
|
22437
|
+
controller.close();
|
|
22438
|
+
}
|
|
22439
|
+
});
|
|
22440
|
+
}
|
|
22441
|
+
var EXT_MIME = {
|
|
22442
|
+
png: "image/png",
|
|
22443
|
+
jpg: "image/jpeg",
|
|
22444
|
+
jpeg: "image/jpeg",
|
|
22445
|
+
gif: "image/gif",
|
|
22446
|
+
webp: "image/webp",
|
|
22447
|
+
svg: "image/svg+xml",
|
|
22448
|
+
bmp: "image/bmp",
|
|
22449
|
+
csv: "text/csv",
|
|
22450
|
+
tsv: "text/tab-separated-values",
|
|
22451
|
+
txt: "text/plain",
|
|
22452
|
+
json: "application/json",
|
|
22453
|
+
xml: "application/xml",
|
|
22454
|
+
html: "text/html",
|
|
22455
|
+
md: "text/markdown",
|
|
22456
|
+
pdf: "application/pdf",
|
|
22457
|
+
zip: "application/zip",
|
|
22458
|
+
parquet: "application/vnd.apache.parquet",
|
|
22459
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
22460
|
+
};
|
|
22461
|
+
function mimeFromName(name) {
|
|
22462
|
+
const ext = name?.includes(".") ? name.split(".").pop()?.toLowerCase() : void 0;
|
|
22463
|
+
return ext ? EXT_MIME[ext] : void 0;
|
|
22464
|
+
}
|
|
22465
|
+
function header(headers, name) {
|
|
22466
|
+
const lower = name.toLowerCase();
|
|
22467
|
+
for (const k in headers) if (k.toLowerCase() === lower) return headers[k];
|
|
22468
|
+
return void 0;
|
|
22469
|
+
}
|
|
22470
|
+
function filenameFromDisposition(cd) {
|
|
22471
|
+
if (!cd) return void 0;
|
|
22472
|
+
const ext = cd.match(/filename\*=[^']*''([^;]+)/i);
|
|
22473
|
+
if (ext) {
|
|
22474
|
+
try {
|
|
22475
|
+
return decodeURIComponent(ext[1].trim());
|
|
22476
|
+
} catch {
|
|
22477
|
+
}
|
|
22478
|
+
}
|
|
22479
|
+
const plain = cd.match(/filename="?([^";]+)"?/i);
|
|
22480
|
+
return plain ? plain[1].trim() : void 0;
|
|
22481
|
+
}
|
|
22482
|
+
async function retrieveFile(file, ctx) {
|
|
22483
|
+
if (file.data) {
|
|
22484
|
+
const blob2 = bytesToBlob(base64ToBytes(file.data), file.mimeType ?? OCTET_STREAM);
|
|
22485
|
+
return { blob: blob2, name: file.name, mimeType: blob2.type, size: blob2.size };
|
|
22486
|
+
}
|
|
22487
|
+
const res = await fetchFileResponse(ctx, file, "arraybuffer");
|
|
22488
|
+
const headers = res.headers ?? {};
|
|
22489
|
+
const name = filenameFromDisposition(header(headers, "content-disposition")) ?? file.name;
|
|
22490
|
+
const mimeType = header(headers, "content-type")?.split(";")[0].trim() || file.mimeType || mimeFromName(name) || OCTET_STREAM;
|
|
22491
|
+
const blob = new Blob([res.body], { type: mimeType });
|
|
22492
|
+
return { blob, name, mimeType, size: blob.size };
|
|
22493
|
+
}
|
|
22494
|
+
async function streamFile(file, ctx) {
|
|
22495
|
+
if (file.data) {
|
|
22496
|
+
const bytes = base64ToBytes(file.data);
|
|
22497
|
+
return { stream: singleChunkStream(bytes), name: file.name, mimeType: file.mimeType, size: bytes.byteLength };
|
|
22498
|
+
}
|
|
22499
|
+
const res = await fetchFileResponse(ctx, file, "stream");
|
|
22500
|
+
const headers = res.headers ?? {};
|
|
22501
|
+
const len = header(headers, "content-length");
|
|
22502
|
+
const name = filenameFromDisposition(header(headers, "content-disposition")) ?? file.name;
|
|
22503
|
+
return {
|
|
22504
|
+
stream: res.body,
|
|
22505
|
+
name,
|
|
22506
|
+
mimeType: header(headers, "content-type")?.split(";")[0].trim() || file.mimeType || mimeFromName(name),
|
|
22507
|
+
size: len ? Number(len) : void 0
|
|
22508
|
+
};
|
|
22509
|
+
}
|
|
22510
|
+
|
|
22357
22511
|
// src/util/duration.ts
|
|
22358
22512
|
var UNIT_MS = {
|
|
22359
22513
|
s: 1e3,
|
|
@@ -22596,6 +22750,28 @@ var LLMClient = class {
|
|
|
22596
22750
|
else next.output = result;
|
|
22597
22751
|
return next;
|
|
22598
22752
|
}
|
|
22753
|
+
// ─── File retrieval (hosted-tool output files) ─────────────────────────
|
|
22754
|
+
retrieveContext() {
|
|
22755
|
+
return {
|
|
22756
|
+
provider: this.provider,
|
|
22757
|
+
apiKey: this.apiKey,
|
|
22758
|
+
fetch: this.fetchFn,
|
|
22759
|
+
baseURL: this.adapter.baseURL()
|
|
22760
|
+
};
|
|
22761
|
+
}
|
|
22762
|
+
/** Fetch a hosted-tool output file (e.g. a code-execution file from
|
|
22763
|
+
* `response.files`): its bytes as a `Blob` plus `name` / `mimeType` / `size`.
|
|
22764
|
+
* Resolves inline `data`, a `url`, or a provider file `id` — all through this
|
|
22765
|
+
* client's provider + auth + engine. */
|
|
22766
|
+
retrieveFile(file) {
|
|
22767
|
+
return retrieveFile(file, this.retrieveContext());
|
|
22768
|
+
}
|
|
22769
|
+
/** Stream a hosted-tool output file: a `ReadableStream<Uint8Array>` plus
|
|
22770
|
+
* best-effort `name` / `mimeType` / `size` (from the response headers). For large
|
|
22771
|
+
* files piped straight to a file / GridFS / HTTP response without buffering. */
|
|
22772
|
+
streamFile(file) {
|
|
22773
|
+
return streamFile(file, this.retrieveContext());
|
|
22774
|
+
}
|
|
22599
22775
|
/** Submit a request. Returns the parsed CompletionResponse. */
|
|
22600
22776
|
async complete(input, options = {}) {
|
|
22601
22777
|
const rawMessages = normalizeInput(input);
|
|
@@ -22807,17 +22983,18 @@ var LLMClient = class {
|
|
|
22807
22983
|
let usage = emptyUsage();
|
|
22808
22984
|
let finishReason = "stop";
|
|
22809
22985
|
let moderationReport;
|
|
22986
|
+
const files = [];
|
|
22810
22987
|
const fetchStream = this.fetchStreamFn;
|
|
22811
|
-
const adapter = this.adapter;
|
|
22812
22988
|
const queueName = this.queueName;
|
|
22813
22989
|
const priority = this.priority;
|
|
22990
|
+
const parseStream = this.adapter.createStreamParser();
|
|
22814
22991
|
async function* rawEvents() {
|
|
22815
22992
|
for await (const sseEvent of fetchStream(httpReq, {
|
|
22816
22993
|
queueName,
|
|
22817
22994
|
priority,
|
|
22818
22995
|
ctx
|
|
22819
22996
|
})) {
|
|
22820
|
-
for (const ev of
|
|
22997
|
+
for (const ev of parseStream(sseEvent)) yield ev;
|
|
22821
22998
|
}
|
|
22822
22999
|
}
|
|
22823
23000
|
const mod = options.moderation;
|
|
@@ -22855,6 +23032,9 @@ var LLMClient = class {
|
|
|
22855
23032
|
case "done":
|
|
22856
23033
|
finishReason = event.finishReason;
|
|
22857
23034
|
break;
|
|
23035
|
+
case "file":
|
|
23036
|
+
files.push(event.file);
|
|
23037
|
+
break;
|
|
22858
23038
|
case "moderation":
|
|
22859
23039
|
moderationReport = this.mergeModeration(
|
|
22860
23040
|
moderationReport,
|
|
@@ -22877,6 +23057,7 @@ var LLMClient = class {
|
|
|
22877
23057
|
toolCalls: [],
|
|
22878
23058
|
thinking: thinking || null,
|
|
22879
23059
|
media: [],
|
|
23060
|
+
...files.length ? { files } : {},
|
|
22880
23061
|
...moderationReport ? { moderation: moderationReport } : {},
|
|
22881
23062
|
latencyMs: performance.now() - start,
|
|
22882
23063
|
raw: null
|
|
@@ -23137,6 +23318,22 @@ function anthropicRequestTier(t) {
|
|
|
23137
23318
|
function anthropicBilledTier(raw) {
|
|
23138
23319
|
return typeof raw === "string" && raw ? { serviceTier: raw, pricingTier: raw } : {};
|
|
23139
23320
|
}
|
|
23321
|
+
function filesFromCodeExecBlock(block) {
|
|
23322
|
+
if (block.type !== "bash_code_execution_tool_result" && block.type !== "code_execution_tool_result") {
|
|
23323
|
+
return [];
|
|
23324
|
+
}
|
|
23325
|
+
const result = block.content;
|
|
23326
|
+
if (!result || result.type !== "bash_code_execution_result" && result.type !== "code_execution_result" || !Array.isArray(result.content)) {
|
|
23327
|
+
return [];
|
|
23328
|
+
}
|
|
23329
|
+
const files = [];
|
|
23330
|
+
for (const out of result.content) {
|
|
23331
|
+
if ((out.type === "bash_code_execution_output" || out.type === "code_execution_output") && typeof out.file_id === "string") {
|
|
23332
|
+
files.push({ id: out.file_id, source: "code_execution" });
|
|
23333
|
+
}
|
|
23334
|
+
}
|
|
23335
|
+
return files;
|
|
23336
|
+
}
|
|
23140
23337
|
var AnthropicAdapter = class {
|
|
23141
23338
|
name = "anthropic";
|
|
23142
23339
|
apiKey;
|
|
@@ -23232,7 +23429,10 @@ var AnthropicAdapter = class {
|
|
|
23232
23429
|
});
|
|
23233
23430
|
const headers = {};
|
|
23234
23431
|
if (hasFileRef) headers["anthropic-beta"] = "files-api-2025-04-14";
|
|
23235
|
-
|
|
23432
|
+
const usesCodeExec = req.tools?.some(
|
|
23433
|
+
(t) => !isFunctionTool(t) && t.type === "code_interpreter"
|
|
23434
|
+
);
|
|
23435
|
+
return { body, headers, ...usesCodeExec ? { path: "/v1/messages?beta=true" } : {} };
|
|
23236
23436
|
}
|
|
23237
23437
|
enableStreaming(providerReq, _req) {
|
|
23238
23438
|
providerReq.body.stream = true;
|
|
@@ -23314,15 +23514,8 @@ var AnthropicAdapter = class {
|
|
|
23314
23514
|
};
|
|
23315
23515
|
content.push(tc);
|
|
23316
23516
|
toolCalls.push(tc);
|
|
23317
|
-
} else
|
|
23318
|
-
|
|
23319
|
-
if (result?.type === "code_execution_result" && Array.isArray(result.content)) {
|
|
23320
|
-
for (const out of result.content) {
|
|
23321
|
-
if (out.type === "code_execution_output" && typeof out.file_id === "string") {
|
|
23322
|
-
files.push({ id: out.file_id, source: "code_execution" });
|
|
23323
|
-
}
|
|
23324
|
-
}
|
|
23325
|
-
}
|
|
23517
|
+
} else {
|
|
23518
|
+
files.push(...filesFromCodeExecBlock(block));
|
|
23326
23519
|
}
|
|
23327
23520
|
}
|
|
23328
23521
|
const finishReason = extractFinishReason(toolCalls.length > 0, r.stop_reason, {
|
|
@@ -23361,6 +23554,8 @@ var AnthropicAdapter = class {
|
|
|
23361
23554
|
if (block.type === "tool_use") {
|
|
23362
23555
|
return [{ type: "tool_call_start", id: block.id, name: block.name }];
|
|
23363
23556
|
}
|
|
23557
|
+
const files = filesFromCodeExecBlock(block);
|
|
23558
|
+
if (files.length) return files.map((file) => ({ type: "file", file }));
|
|
23364
23559
|
}
|
|
23365
23560
|
if (type === "content_block_stop") {
|
|
23366
23561
|
}
|
|
@@ -23384,6 +23579,11 @@ var AnthropicAdapter = class {
|
|
|
23384
23579
|
}
|
|
23385
23580
|
return [];
|
|
23386
23581
|
}
|
|
23582
|
+
/** Stateless — every event is self-contained (code-execution result blocks
|
|
23583
|
+
* arrive complete in a single content_block_start). */
|
|
23584
|
+
createStreamParser() {
|
|
23585
|
+
return (event) => this.parseStreamEvent(event);
|
|
23586
|
+
}
|
|
23387
23587
|
parseUsage(u) {
|
|
23388
23588
|
if (!u) return emptyUsage();
|
|
23389
23589
|
const inputTokens = u.input_tokens ?? 0;
|
|
@@ -23826,7 +24026,9 @@ var GoogleAdapter = class {
|
|
|
23826
24026
|
const content = [];
|
|
23827
24027
|
const toolCalls = [];
|
|
23828
24028
|
const media = [];
|
|
24029
|
+
const files = [];
|
|
23829
24030
|
let thinking = null;
|
|
24031
|
+
const hasCodeExec = parts.some((p) => p.executableCode || p.codeExecutionResult);
|
|
23830
24032
|
for (const part of parts) {
|
|
23831
24033
|
if (part.text !== void 0 && !part.thought) {
|
|
23832
24034
|
content.push({ type: "text", text: part.text });
|
|
@@ -23837,7 +24039,9 @@ var GoogleAdapter = class {
|
|
|
23837
24039
|
if (part.inlineData) {
|
|
23838
24040
|
const inline = part.inlineData;
|
|
23839
24041
|
const mime = inline.mimeType;
|
|
23840
|
-
if (
|
|
24042
|
+
if (hasCodeExec) {
|
|
24043
|
+
files.push({ data: inline.data, mimeType: mime, source: "code_execution" });
|
|
24044
|
+
} else if (mime.startsWith("image/")) {
|
|
23841
24045
|
const p = {
|
|
23842
24046
|
type: "image_output",
|
|
23843
24047
|
mediaId: "",
|
|
@@ -23898,11 +24102,24 @@ var GoogleAdapter = class {
|
|
|
23898
24102
|
toolCalls,
|
|
23899
24103
|
thinking,
|
|
23900
24104
|
media,
|
|
24105
|
+
...files.length ? { files } : {},
|
|
23901
24106
|
latencyMs,
|
|
23902
24107
|
raw
|
|
23903
24108
|
};
|
|
23904
24109
|
}
|
|
23905
24110
|
parseStreamEvent(event) {
|
|
24111
|
+
return this.streamEvents(event, { codeExec: false });
|
|
24112
|
+
}
|
|
24113
|
+
/** Stateful — Google splits the code-execution marker (`executableCode` /
|
|
24114
|
+
* `codeExecutionResult`) and the produced file (`inlineData`) across parts and
|
|
24115
|
+
* often across SSE events. The closure remembers "code execution began in this
|
|
24116
|
+
* stream" so a later `inlineData` blob is routed to `files` (a code-exec
|
|
24117
|
+
* artifact) rather than `media` (conversational output). */
|
|
24118
|
+
createStreamParser() {
|
|
24119
|
+
const state = { codeExec: false };
|
|
24120
|
+
return (event) => this.streamEvents(event, state);
|
|
24121
|
+
}
|
|
24122
|
+
streamEvents(event, state) {
|
|
23906
24123
|
const data = JSON.parse(event.data);
|
|
23907
24124
|
const candidates = data.candidates ?? [];
|
|
23908
24125
|
const candidate = candidates[0];
|
|
@@ -23916,6 +24133,9 @@ var GoogleAdapter = class {
|
|
|
23916
24133
|
const rawContent = candidate.content ?? {};
|
|
23917
24134
|
const parts = rawContent.parts ?? [];
|
|
23918
24135
|
const events = [];
|
|
24136
|
+
for (const part of parts) {
|
|
24137
|
+
if (part.executableCode || part.codeExecutionResult) state.codeExec = true;
|
|
24138
|
+
}
|
|
23919
24139
|
for (const part of parts) {
|
|
23920
24140
|
if (part.text !== void 0 && !part.thought)
|
|
23921
24141
|
events.push({ type: "text", text: part.text });
|
|
@@ -23923,10 +24143,17 @@ var GoogleAdapter = class {
|
|
|
23923
24143
|
if (part.inlineData) {
|
|
23924
24144
|
const inline = part.inlineData;
|
|
23925
24145
|
const mime = inline.mimeType;
|
|
23926
|
-
|
|
23927
|
-
|
|
23928
|
-
|
|
23929
|
-
|
|
24146
|
+
if (state.codeExec) {
|
|
24147
|
+
events.push({
|
|
24148
|
+
type: "file",
|
|
24149
|
+
file: { data: inline.data, mimeType: mime, source: "code_execution" }
|
|
24150
|
+
});
|
|
24151
|
+
} else {
|
|
24152
|
+
const mediaType = mime.startsWith("image/") ? "image" : mime.startsWith("audio/") ? "audio" : "video";
|
|
24153
|
+
events.push({ type: "media_start", mediaType, mimeType: mime });
|
|
24154
|
+
events.push({ type: "media_chunk", data: inline.data });
|
|
24155
|
+
events.push({ type: "media_end" });
|
|
24156
|
+
}
|
|
23930
24157
|
}
|
|
23931
24158
|
if (part.functionCall) {
|
|
23932
24159
|
const fc = part.functionCall;
|
|
@@ -24221,6 +24448,10 @@ var GoogleInteractionsAdapter = class {
|
|
|
24221
24448
|
}
|
|
24222
24449
|
return events;
|
|
24223
24450
|
}
|
|
24451
|
+
/** Stateless — the Interactions adapter surfaces no code-execution file outputs. */
|
|
24452
|
+
createStreamParser() {
|
|
24453
|
+
return (event) => this.parseStreamEvent(event);
|
|
24454
|
+
}
|
|
24224
24455
|
parseUsage(u) {
|
|
24225
24456
|
if (!u) return emptyUsage();
|
|
24226
24457
|
const input = u.total_input_tokens ?? u.prompt_tokens ?? 0;
|
|
@@ -25186,6 +25417,10 @@ var OpenAIAdapter = class {
|
|
|
25186
25417
|
}
|
|
25187
25418
|
return events;
|
|
25188
25419
|
}
|
|
25420
|
+
/** Stateless — Chat Completions has no hosted code-execution file outputs. */
|
|
25421
|
+
createStreamParser() {
|
|
25422
|
+
return (event) => this.parseStreamEvent(event);
|
|
25423
|
+
}
|
|
25189
25424
|
parseUsage(u) {
|
|
25190
25425
|
if (!u) return emptyUsage();
|
|
25191
25426
|
const input = u.prompt_tokens ?? u.input_tokens ?? 0;
|
|
@@ -25701,6 +25936,33 @@ function filenameForMime(mimeType) {
|
|
|
25701
25936
|
if (mimeType.startsWith("image/")) return `file.${mimeType.slice("image/".length)}`;
|
|
25702
25937
|
return "file.bin";
|
|
25703
25938
|
}
|
|
25939
|
+
function filesFromResponsesOutputItem(item) {
|
|
25940
|
+
const files = [];
|
|
25941
|
+
const type = item.type;
|
|
25942
|
+
if (type === "message") {
|
|
25943
|
+
for (const c of item.content ?? []) {
|
|
25944
|
+
if (c.type !== "output_text") continue;
|
|
25945
|
+
for (const a of c.annotations ?? []) {
|
|
25946
|
+
if (a.type === "container_file_citation" && typeof a.file_id === "string") {
|
|
25947
|
+
files.push({
|
|
25948
|
+
id: a.file_id,
|
|
25949
|
+
...typeof a.filename === "string" ? { name: a.filename } : {},
|
|
25950
|
+
...typeof a.container_id === "string" ? { ref: { containerId: a.container_id } } : {},
|
|
25951
|
+
source: "code_execution"
|
|
25952
|
+
});
|
|
25953
|
+
}
|
|
25954
|
+
}
|
|
25955
|
+
}
|
|
25956
|
+
}
|
|
25957
|
+
if (type === "code_interpreter_call") {
|
|
25958
|
+
for (const out of item.outputs ?? []) {
|
|
25959
|
+
if (out.type === "image" && typeof out.url === "string") {
|
|
25960
|
+
files.push({ url: out.url, source: "code_execution" });
|
|
25961
|
+
}
|
|
25962
|
+
}
|
|
25963
|
+
}
|
|
25964
|
+
return files;
|
|
25965
|
+
}
|
|
25704
25966
|
var OpenAIResponsesAdapter = class {
|
|
25705
25967
|
name = "openai";
|
|
25706
25968
|
apiKey;
|
|
@@ -25876,10 +26138,12 @@ var OpenAIResponsesAdapter = class {
|
|
|
25876
26138
|
const content = [];
|
|
25877
26139
|
const toolCalls = [];
|
|
25878
26140
|
const media = [];
|
|
26141
|
+
const files = [];
|
|
25879
26142
|
let thinking = null;
|
|
25880
26143
|
let text = "";
|
|
25881
26144
|
for (const item of output) {
|
|
25882
26145
|
const type = item.type;
|
|
26146
|
+
files.push(...filesFromResponsesOutputItem(item));
|
|
25883
26147
|
if (type === "message") {
|
|
25884
26148
|
const itemContent = item.content ?? [];
|
|
25885
26149
|
for (const c of itemContent) {
|
|
@@ -25939,6 +26203,7 @@ var OpenAIResponsesAdapter = class {
|
|
|
25939
26203
|
toolCalls,
|
|
25940
26204
|
thinking,
|
|
25941
26205
|
media,
|
|
26206
|
+
...files.length ? { files } : {},
|
|
25942
26207
|
...moderation ? { moderation } : {},
|
|
25943
26208
|
latencyMs,
|
|
25944
26209
|
raw
|
|
@@ -25980,6 +26245,7 @@ var OpenAIResponsesAdapter = class {
|
|
|
25980
26245
|
}
|
|
25981
26246
|
if (type === "response.output_item.done") {
|
|
25982
26247
|
const item = data.item;
|
|
26248
|
+
for (const file of filesFromResponsesOutputItem(item)) events.push({ type: "file", file });
|
|
25983
26249
|
if (item?.type === "function_call") {
|
|
25984
26250
|
events.push({ type: "tool_call_end", id: item.call_id ?? "" });
|
|
25985
26251
|
}
|
|
@@ -26010,6 +26276,11 @@ var OpenAIResponsesAdapter = class {
|
|
|
26010
26276
|
}
|
|
26011
26277
|
return events;
|
|
26012
26278
|
}
|
|
26279
|
+
/** Stateless — each output item finalizes with all its file annotations in a
|
|
26280
|
+
* single response.output_item.done event. */
|
|
26281
|
+
createStreamParser() {
|
|
26282
|
+
return (event) => this.parseStreamEvent(event);
|
|
26283
|
+
}
|
|
26013
26284
|
parseUsage(u) {
|
|
26014
26285
|
if (!u) return emptyUsage();
|
|
26015
26286
|
const input = u.input_tokens ?? 0;
|
|
@@ -27665,6 +27936,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
27665
27936
|
_toolTimeout;
|
|
27666
27937
|
_maxSteps;
|
|
27667
27938
|
_guardrails;
|
|
27939
|
+
_toolInputGuardrails;
|
|
27668
27940
|
_policy;
|
|
27669
27941
|
_approve;
|
|
27670
27942
|
_checkpoint;
|
|
@@ -27692,6 +27964,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
27692
27964
|
this._toolTimeout = config.toolTimeout ?? DEFAULT_TOOL_TIMEOUT_MS;
|
|
27693
27965
|
this._maxSteps = config.maxSteps !== void 0 && config.maxSteps > 0 ? config.maxSteps : DEFAULT_MAX_STEPS;
|
|
27694
27966
|
this._guardrails = config.guardrails ?? [];
|
|
27967
|
+
this._toolInputGuardrails = config.toolInputGuardrails ?? [];
|
|
27695
27968
|
this._policy = config.policy ?? null;
|
|
27696
27969
|
this._approve = config.approve ?? null;
|
|
27697
27970
|
this._checkpoint = config.checkpoint ?? null;
|
|
@@ -27729,6 +28002,19 @@ var AgentLoop = class _AgentLoop {
|
|
|
27729
28002
|
get model() {
|
|
27730
28003
|
return this.client.model;
|
|
27731
28004
|
}
|
|
28005
|
+
// ─── File retrieval (hosted-tool output files) ──────────────────────────
|
|
28006
|
+
/** Fetch a hosted-tool output file from the run's `response.files` — its bytes
|
|
28007
|
+
* as a `Blob` plus `name` / `mimeType` / `size`. Delegates to the underlying
|
|
28008
|
+
* client, so it uses this agent's provider + key (same as `retrieveFile` on a
|
|
28009
|
+
* `complete()` result). */
|
|
28010
|
+
retrieveFile(file) {
|
|
28011
|
+
return this.client.retrieveFile(file);
|
|
28012
|
+
}
|
|
28013
|
+
/** Stream a hosted-tool output file (large files) — a `ReadableStream` plus
|
|
28014
|
+
* best-effort `name` / `mimeType` / `size`. Delegates to the underlying client. */
|
|
28015
|
+
streamFile(file) {
|
|
28016
|
+
return this.client.streamFile(file);
|
|
28017
|
+
}
|
|
27732
28018
|
get system() {
|
|
27733
28019
|
return this._system;
|
|
27734
28020
|
}
|
|
@@ -27910,6 +28196,10 @@ var AgentLoop = class _AgentLoop {
|
|
|
27910
28196
|
toolCalls: lastResponse?.toolCalls ?? [],
|
|
27911
28197
|
thinking: lastResponse?.thinking ?? null,
|
|
27912
28198
|
media: lastResponse?.media ?? [],
|
|
28199
|
+
// Propagate hosted-tool file outputs + inline-moderation result from the final
|
|
28200
|
+
// LLM response (e.g. code-execution files produced during the run).
|
|
28201
|
+
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28202
|
+
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
27913
28203
|
latencyMs: performance.now() - startPerf,
|
|
27914
28204
|
raw: lastResponse?.raw ?? null
|
|
27915
28205
|
};
|
|
@@ -28104,6 +28394,8 @@ var AgentLoop = class _AgentLoop {
|
|
|
28104
28394
|
toolCalls: lastResponse?.toolCalls ?? [],
|
|
28105
28395
|
thinking: lastResponse?.thinking ?? null,
|
|
28106
28396
|
media: [],
|
|
28397
|
+
...lastResponse?.files ? { files: lastResponse.files } : {},
|
|
28398
|
+
...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
|
|
28107
28399
|
latencyMs: performance.now() - startPerf,
|
|
28108
28400
|
raw: null
|
|
28109
28401
|
};
|
|
@@ -28190,6 +28482,18 @@ var AgentLoop = class _AgentLoop {
|
|
|
28190
28482
|
runTrace
|
|
28191
28483
|
);
|
|
28192
28484
|
if (!lookup.found) return lookup.errorResult;
|
|
28485
|
+
for (const g of this._toolInputGuardrails) {
|
|
28486
|
+
const decision = await g.check({
|
|
28487
|
+
toolName: tc.name,
|
|
28488
|
+
arguments: tc.arguments,
|
|
28489
|
+
callId: tc.id,
|
|
28490
|
+
step,
|
|
28491
|
+
trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id }
|
|
28492
|
+
});
|
|
28493
|
+
if (!decision.pass) {
|
|
28494
|
+
return this.buildDeniedResult(tc, decision.reason, reports);
|
|
28495
|
+
}
|
|
28496
|
+
}
|
|
28193
28497
|
if (this._policy !== null) {
|
|
28194
28498
|
const target = {
|
|
28195
28499
|
kind: "tool",
|
|
@@ -28206,7 +28510,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28206
28510
|
try {
|
|
28207
28511
|
const baseCtx = { step, callId: tc.id, metrics, trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id } };
|
|
28208
28512
|
const result = await executeWithTimeout(lookup.tool, tc, baseCtx, this._toolTimeout);
|
|
28209
|
-
return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace);
|
|
28513
|
+
return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, lookup.tool, baseCtx);
|
|
28210
28514
|
} catch (e) {
|
|
28211
28515
|
return handleToolError(e, tc, this.hooks, runId, this.id, step, metrics, reports, toolStart, runTrace);
|
|
28212
28516
|
}
|
|
@@ -28297,7 +28601,7 @@ var AgentLoop = class _AgentLoop {
|
|
|
28297
28601
|
try {
|
|
28298
28602
|
const baseCtx = { step, callId: tc.id, metrics, trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id } };
|
|
28299
28603
|
const result = await executeWithTimeout(tool, tc, baseCtx, this._toolTimeout);
|
|
28300
|
-
return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace);
|
|
28604
|
+
return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, tool, baseCtx);
|
|
28301
28605
|
} catch (e) {
|
|
28302
28606
|
return handleToolError(e, tc, this.hooks, runId, this.id, step, metrics, reports, toolStart, runTrace);
|
|
28303
28607
|
}
|
|
@@ -28309,9 +28613,16 @@ var AgentLoop = class _AgentLoop {
|
|
|
28309
28613
|
return this.buildDeniedResult(tc, denyMsg, reports);
|
|
28310
28614
|
}
|
|
28311
28615
|
/** Emit onToolCallComplete, push report, and return success content part. */
|
|
28312
|
-
async buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace) {
|
|
28616
|
+
async buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, tool, context) {
|
|
28313
28617
|
const latencyMs = performance.now() - toolStart;
|
|
28314
28618
|
const resultStr = typeof result === "string" ? result : JSON.stringify(result);
|
|
28619
|
+
let customData;
|
|
28620
|
+
if (tool?.customDataExtractor && context) {
|
|
28621
|
+
try {
|
|
28622
|
+
customData = await tool.customDataExtractor(result, tc.arguments, context);
|
|
28623
|
+
} catch {
|
|
28624
|
+
}
|
|
28625
|
+
}
|
|
28315
28626
|
await this.hooks.emit("onToolCallComplete", {
|
|
28316
28627
|
runId,
|
|
28317
28628
|
agentId: this.id,
|
|
@@ -28333,7 +28644,8 @@ var AgentLoop = class _AgentLoop {
|
|
|
28333
28644
|
latencyMs,
|
|
28334
28645
|
skipped: false,
|
|
28335
28646
|
error: null,
|
|
28336
|
-
metrics: Object.fromEntries(metrics)
|
|
28647
|
+
metrics: Object.fromEntries(metrics),
|
|
28648
|
+
...customData !== void 0 ? { customData } : {}
|
|
28337
28649
|
});
|
|
28338
28650
|
return { type: "tool_result", id: tc.id, content: resultStr };
|
|
28339
28651
|
}
|
|
@@ -34337,7 +34649,13 @@ async function complete(opts) {
|
|
|
34337
34649
|
serviceTier
|
|
34338
34650
|
});
|
|
34339
34651
|
}
|
|
34340
|
-
const result = {
|
|
34652
|
+
const result = {
|
|
34653
|
+
text: res.text,
|
|
34654
|
+
response: res,
|
|
34655
|
+
// Bound to this call's client (same provider/model/key/engine).
|
|
34656
|
+
retrieveFile: (file) => llm.retrieveFile(file),
|
|
34657
|
+
streamFile: (file) => llm.streamFile(file)
|
|
34658
|
+
};
|
|
34341
34659
|
if (opts.structured?.schema) {
|
|
34342
34660
|
result.parsed = parseStructured(res.text);
|
|
34343
34661
|
}
|
package/dist/llm/client.d.ts
CHANGED
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
* Hooks emitted: onClientCreate (in ctor), onMessageResolve, onBeforeSubmit,
|
|
19
19
|
* onCompletion, onClientDestroy. */
|
|
20
20
|
import type { HookBus } from '../bus/hook-bus';
|
|
21
|
+
import type { FileStream, RetrievedFile } from './files/retrieve';
|
|
21
22
|
import type { ContentPart, Message } from './types/messages';
|
|
22
23
|
import type { ExecuteOptions } from './types/options';
|
|
23
24
|
import type { ApiType, ProviderName } from './types/provider';
|
|
24
|
-
import type { CompletionResponse } from './types/response';
|
|
25
|
+
import type { CompletionResponse, FileOutput } from './types/response';
|
|
25
26
|
import type { StreamEvent } from './types/stream';
|
|
26
27
|
import type { LLMClientConfig } from './client-config';
|
|
27
28
|
export declare class LLMClient {
|
|
@@ -67,6 +68,16 @@ export declare class LLMClient {
|
|
|
67
68
|
private moderationConfig;
|
|
68
69
|
/** Fold a moderation stream event into the accumulating report. */
|
|
69
70
|
private mergeModeration;
|
|
71
|
+
private retrieveContext;
|
|
72
|
+
/** Fetch a hosted-tool output file (e.g. a code-execution file from
|
|
73
|
+
* `response.files`): its bytes as a `Blob` plus `name` / `mimeType` / `size`.
|
|
74
|
+
* Resolves inline `data`, a `url`, or a provider file `id` — all through this
|
|
75
|
+
* client's provider + auth + engine. */
|
|
76
|
+
retrieveFile(file: FileOutput): Promise<RetrievedFile>;
|
|
77
|
+
/** Stream a hosted-tool output file: a `ReadableStream<Uint8Array>` plus
|
|
78
|
+
* best-effort `name` / `mimeType` / `size` (from the response headers). For large
|
|
79
|
+
* files piped straight to a file / GridFS / HTTP response without buffering. */
|
|
80
|
+
streamFile(file: FileOutput): Promise<FileStream>;
|
|
70
81
|
/** Submit a request. Returns the parsed CompletionResponse. */
|
|
71
82
|
complete(input: string | ContentPart[] | Message[], options?: ExecuteOptions): Promise<CompletionResponse>;
|
|
72
83
|
/** Run `complete` with a JSON Schema enforced via `structured`. Strips any
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Retrieve the bytes of a hosted-tool output file (e.g. code-execution files).
|
|
2
|
+
*
|
|
3
|
+
* A `FileOutput` may carry inline base64 `data` (Google), a `url` (OpenAI
|
|
4
|
+
* code-interpreter images), or an `id` fetched from the provider's files API
|
|
5
|
+
* (Anthropic, OpenAI container files). These helpers resolve all three into
|
|
6
|
+
* bytes — buffered (`retrieveFile` → Blob) or streamed (`streamFile` →
|
|
7
|
+
* ReadableStream, for large files piped straight to a sink).
|
|
8
|
+
*
|
|
9
|
+
* All HTTP flows through the injected `EngineFetch` (auth, queue, cost, traces). */
|
|
10
|
+
import type { EngineFetch } from '../../network/types';
|
|
11
|
+
import type { ProviderName } from '../types/provider';
|
|
12
|
+
import type { FileOutput } from '../types/response';
|
|
13
|
+
export interface RetrieveContext {
|
|
14
|
+
provider: ProviderName;
|
|
15
|
+
apiKey: string;
|
|
16
|
+
fetch: EngineFetch;
|
|
17
|
+
/** Provider API base URL; defaults per provider when omitted. */
|
|
18
|
+
baseURL?: string;
|
|
19
|
+
}
|
|
20
|
+
/** A retrieved file's bytes plus the metadata an end user needs to display and
|
|
21
|
+
* open it as an attachment (correct name + type). */
|
|
22
|
+
export interface RetrievedFile {
|
|
23
|
+
blob: Blob;
|
|
24
|
+
/** Filename (from Content-Disposition, else the FileOutput name). */
|
|
25
|
+
name?: string;
|
|
26
|
+
/** MIME type (from Content-Type, else the FileOutput mime). */
|
|
27
|
+
mimeType: string;
|
|
28
|
+
/** Size in bytes. */
|
|
29
|
+
size: number;
|
|
30
|
+
}
|
|
31
|
+
/** A streamed file: its byte stream plus best-effort metadata (from the response
|
|
32
|
+
* headers). The stream itself carries only bytes — name/type/size come from here. */
|
|
33
|
+
export interface FileStream {
|
|
34
|
+
stream: ReadableStream<Uint8Array>;
|
|
35
|
+
name?: string;
|
|
36
|
+
mimeType?: string;
|
|
37
|
+
/** From Content-Length, when the provider sends it. */
|
|
38
|
+
size?: number;
|
|
39
|
+
}
|
|
40
|
+
/** Fetch the whole file as bytes (buffered) plus its name/type/size. */
|
|
41
|
+
export declare function retrieveFile(file: FileOutput, ctx: RetrieveContext): Promise<RetrievedFile>;
|
|
42
|
+
/** Stream the file's bytes (un-buffered) plus best-effort name/type/size. Pipe the
|
|
43
|
+
* stream straight to a file, GridFS, or an HTTP response — nothing is buffered. */
|
|
44
|
+
export declare function streamFile(file: FileOutput, ctx: RetrieveContext): Promise<FileStream>;
|