@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.
@@ -2524,7 +2524,7 @@ var QueueState = class {
2524
2524
  });
2525
2525
  this.processed++;
2526
2526
  if (response.ok) {
2527
- const body = await parseResponseBody(response, entry.request.responseType ?? "json");
2527
+ const body = entry.request.responseType === "stream" ? response.body : await parseResponseBody(response, entry.request.responseType ?? "json");
2528
2528
  this.semaphore.release();
2529
2529
  entry.resolve({ status: response.status, headers: resHeaders, body });
2530
2530
  return;
@@ -22427,6 +22427,160 @@ async function* wrapParallel(raw, interval, moderate2) {
22427
22427
  }
22428
22428
  }
22429
22429
 
22430
+ // src/llm/files/retrieve.ts
22431
+ var DEFAULT_BASE = {
22432
+ anthropic: "https://api.anthropic.com",
22433
+ openai: "https://api.openai.com",
22434
+ xai: "https://api.x.ai",
22435
+ google: "https://generativelanguage.googleapis.com",
22436
+ openrouter: "https://openrouter.ai/api"
22437
+ };
22438
+ var OCTET_STREAM = "application/octet-stream";
22439
+ function contentRequest(ctx, file) {
22440
+ const base = ctx.baseURL ?? DEFAULT_BASE[ctx.provider];
22441
+ const id = file.id;
22442
+ if (ctx.provider === "anthropic") {
22443
+ return {
22444
+ url: `${base}/v1/files/${id}/content?beta=true`,
22445
+ headers: {
22446
+ "x-api-key": ctx.apiKey,
22447
+ "anthropic-version": "2023-06-01",
22448
+ "anthropic-beta": "files-api-2025-04-14",
22449
+ accept: "application/binary"
22450
+ }
22451
+ };
22452
+ }
22453
+ if (ctx.provider === "openai" || ctx.provider === "xai" || ctx.provider === "openrouter") {
22454
+ const containerId = file.ref?.containerId;
22455
+ const path = containerId ? `/v1/containers/${containerId}/files/${id}/content` : `/v1/files/${id}/content`;
22456
+ return { url: `${base}${path}`, headers: { authorization: `Bearer ${ctx.apiKey}` } };
22457
+ }
22458
+ if (ctx.provider === "google") {
22459
+ return {
22460
+ url: `${base}/v1beta/files/${id}:download?alt=media`,
22461
+ headers: { "x-goog-api-key": ctx.apiKey }
22462
+ };
22463
+ }
22464
+ throw new Error(`retrieveFile: no file-content endpoint for provider "${ctx.provider}"`);
22465
+ }
22466
+ function providerAuth(ctx, url) {
22467
+ const base = ctx.baseURL ?? DEFAULT_BASE[ctx.provider];
22468
+ if (!url.startsWith(base)) return {};
22469
+ if (ctx.provider === "anthropic") {
22470
+ return { "x-api-key": ctx.apiKey, "anthropic-version": "2023-06-01" };
22471
+ }
22472
+ if (ctx.provider === "google") return { "x-goog-api-key": ctx.apiKey };
22473
+ return { authorization: `Bearer ${ctx.apiKey}` };
22474
+ }
22475
+ async function fetchFileResponse(ctx, file, responseType) {
22476
+ if (file.url) {
22477
+ return ctx.fetch({
22478
+ url: file.url,
22479
+ method: "GET",
22480
+ headers: providerAuth(ctx, file.url),
22481
+ body: void 0,
22482
+ provider: ctx.provider,
22483
+ model: "files",
22484
+ responseType
22485
+ });
22486
+ }
22487
+ if (file.id) {
22488
+ const { url, headers } = contentRequest(ctx, file);
22489
+ return ctx.fetch({
22490
+ url,
22491
+ method: "GET",
22492
+ headers,
22493
+ body: void 0,
22494
+ provider: ctx.provider,
22495
+ model: "files",
22496
+ responseType
22497
+ });
22498
+ }
22499
+ throw new Error("retrieveFile: FileOutput has neither `data`, `url`, nor `id`");
22500
+ }
22501
+ function bytesToBlob(bytes, type) {
22502
+ const ab = new ArrayBuffer(bytes.byteLength);
22503
+ new Uint8Array(ab).set(bytes);
22504
+ return new Blob([ab], { type });
22505
+ }
22506
+ function singleChunkStream(bytes) {
22507
+ return new ReadableStream({
22508
+ start(controller) {
22509
+ controller.enqueue(bytes);
22510
+ controller.close();
22511
+ }
22512
+ });
22513
+ }
22514
+ var EXT_MIME = {
22515
+ png: "image/png",
22516
+ jpg: "image/jpeg",
22517
+ jpeg: "image/jpeg",
22518
+ gif: "image/gif",
22519
+ webp: "image/webp",
22520
+ svg: "image/svg+xml",
22521
+ bmp: "image/bmp",
22522
+ csv: "text/csv",
22523
+ tsv: "text/tab-separated-values",
22524
+ txt: "text/plain",
22525
+ json: "application/json",
22526
+ xml: "application/xml",
22527
+ html: "text/html",
22528
+ md: "text/markdown",
22529
+ pdf: "application/pdf",
22530
+ zip: "application/zip",
22531
+ parquet: "application/vnd.apache.parquet",
22532
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
22533
+ };
22534
+ function mimeFromName(name) {
22535
+ const ext = name?.includes(".") ? name.split(".").pop()?.toLowerCase() : void 0;
22536
+ return ext ? EXT_MIME[ext] : void 0;
22537
+ }
22538
+ function header(headers, name) {
22539
+ const lower = name.toLowerCase();
22540
+ for (const k in headers) if (k.toLowerCase() === lower) return headers[k];
22541
+ return void 0;
22542
+ }
22543
+ function filenameFromDisposition(cd) {
22544
+ if (!cd) return void 0;
22545
+ const ext = cd.match(/filename\*=[^']*''([^;]+)/i);
22546
+ if (ext) {
22547
+ try {
22548
+ return decodeURIComponent(ext[1].trim());
22549
+ } catch {
22550
+ }
22551
+ }
22552
+ const plain = cd.match(/filename="?([^";]+)"?/i);
22553
+ return plain ? plain[1].trim() : void 0;
22554
+ }
22555
+ async function retrieveFile(file, ctx) {
22556
+ if (file.data) {
22557
+ const blob2 = bytesToBlob(base64ToBytes(file.data), file.mimeType ?? OCTET_STREAM);
22558
+ return { blob: blob2, name: file.name, mimeType: blob2.type, size: blob2.size };
22559
+ }
22560
+ const res = await fetchFileResponse(ctx, file, "arraybuffer");
22561
+ const headers = res.headers ?? {};
22562
+ const name = filenameFromDisposition(header(headers, "content-disposition")) ?? file.name;
22563
+ const mimeType = header(headers, "content-type")?.split(";")[0].trim() || file.mimeType || mimeFromName(name) || OCTET_STREAM;
22564
+ const blob = new Blob([res.body], { type: mimeType });
22565
+ return { blob, name, mimeType, size: blob.size };
22566
+ }
22567
+ async function streamFile(file, ctx) {
22568
+ if (file.data) {
22569
+ const bytes = base64ToBytes(file.data);
22570
+ return { stream: singleChunkStream(bytes), name: file.name, mimeType: file.mimeType, size: bytes.byteLength };
22571
+ }
22572
+ const res = await fetchFileResponse(ctx, file, "stream");
22573
+ const headers = res.headers ?? {};
22574
+ const len = header(headers, "content-length");
22575
+ const name = filenameFromDisposition(header(headers, "content-disposition")) ?? file.name;
22576
+ return {
22577
+ stream: res.body,
22578
+ name,
22579
+ mimeType: header(headers, "content-type")?.split(";")[0].trim() || file.mimeType || mimeFromName(name),
22580
+ size: len ? Number(len) : void 0
22581
+ };
22582
+ }
22583
+
22430
22584
  // src/util/duration.ts
22431
22585
  var UNIT_MS = {
22432
22586
  s: 1e3,
@@ -22669,6 +22823,28 @@ var LLMClient = class {
22669
22823
  else next.output = result;
22670
22824
  return next;
22671
22825
  }
22826
+ // ─── File retrieval (hosted-tool output files) ─────────────────────────
22827
+ retrieveContext() {
22828
+ return {
22829
+ provider: this.provider,
22830
+ apiKey: this.apiKey,
22831
+ fetch: this.fetchFn,
22832
+ baseURL: this.adapter.baseURL()
22833
+ };
22834
+ }
22835
+ /** Fetch a hosted-tool output file (e.g. a code-execution file from
22836
+ * `response.files`): its bytes as a `Blob` plus `name` / `mimeType` / `size`.
22837
+ * Resolves inline `data`, a `url`, or a provider file `id` — all through this
22838
+ * client's provider + auth + engine. */
22839
+ retrieveFile(file) {
22840
+ return retrieveFile(file, this.retrieveContext());
22841
+ }
22842
+ /** Stream a hosted-tool output file: a `ReadableStream<Uint8Array>` plus
22843
+ * best-effort `name` / `mimeType` / `size` (from the response headers). For large
22844
+ * files piped straight to a file / GridFS / HTTP response without buffering. */
22845
+ streamFile(file) {
22846
+ return streamFile(file, this.retrieveContext());
22847
+ }
22672
22848
  /** Submit a request. Returns the parsed CompletionResponse. */
22673
22849
  async complete(input, options = {}) {
22674
22850
  const rawMessages = normalizeInput(input);
@@ -22880,17 +23056,18 @@ var LLMClient = class {
22880
23056
  let usage = emptyUsage();
22881
23057
  let finishReason = "stop";
22882
23058
  let moderationReport;
23059
+ const files = [];
22883
23060
  const fetchStream = this.fetchStreamFn;
22884
- const adapter = this.adapter;
22885
23061
  const queueName = this.queueName;
22886
23062
  const priority = this.priority;
23063
+ const parseStream = this.adapter.createStreamParser();
22887
23064
  async function* rawEvents() {
22888
23065
  for await (const sseEvent of fetchStream(httpReq, {
22889
23066
  queueName,
22890
23067
  priority,
22891
23068
  ctx
22892
23069
  })) {
22893
- for (const ev of adapter.parseStreamEvent(sseEvent)) yield ev;
23070
+ for (const ev of parseStream(sseEvent)) yield ev;
22894
23071
  }
22895
23072
  }
22896
23073
  const mod = options.moderation;
@@ -22928,6 +23105,9 @@ var LLMClient = class {
22928
23105
  case "done":
22929
23106
  finishReason = event.finishReason;
22930
23107
  break;
23108
+ case "file":
23109
+ files.push(event.file);
23110
+ break;
22931
23111
  case "moderation":
22932
23112
  moderationReport = this.mergeModeration(
22933
23113
  moderationReport,
@@ -22950,6 +23130,7 @@ var LLMClient = class {
22950
23130
  toolCalls: [],
22951
23131
  thinking: thinking || null,
22952
23132
  media: [],
23133
+ ...files.length ? { files } : {},
22953
23134
  ...moderationReport ? { moderation: moderationReport } : {},
22954
23135
  latencyMs: performance.now() - start,
22955
23136
  raw: null
@@ -23210,6 +23391,22 @@ function anthropicRequestTier(t) {
23210
23391
  function anthropicBilledTier(raw) {
23211
23392
  return typeof raw === "string" && raw ? { serviceTier: raw, pricingTier: raw } : {};
23212
23393
  }
23394
+ function filesFromCodeExecBlock(block) {
23395
+ if (block.type !== "bash_code_execution_tool_result" && block.type !== "code_execution_tool_result") {
23396
+ return [];
23397
+ }
23398
+ const result = block.content;
23399
+ if (!result || result.type !== "bash_code_execution_result" && result.type !== "code_execution_result" || !Array.isArray(result.content)) {
23400
+ return [];
23401
+ }
23402
+ const files = [];
23403
+ for (const out of result.content) {
23404
+ if ((out.type === "bash_code_execution_output" || out.type === "code_execution_output") && typeof out.file_id === "string") {
23405
+ files.push({ id: out.file_id, source: "code_execution" });
23406
+ }
23407
+ }
23408
+ return files;
23409
+ }
23213
23410
  var AnthropicAdapter = class {
23214
23411
  name = "anthropic";
23215
23412
  apiKey;
@@ -23305,7 +23502,10 @@ var AnthropicAdapter = class {
23305
23502
  });
23306
23503
  const headers = {};
23307
23504
  if (hasFileRef) headers["anthropic-beta"] = "files-api-2025-04-14";
23308
- return { body, headers };
23505
+ const usesCodeExec = req.tools?.some(
23506
+ (t) => !isFunctionTool(t) && t.type === "code_interpreter"
23507
+ );
23508
+ return { body, headers, ...usesCodeExec ? { path: "/v1/messages?beta=true" } : {} };
23309
23509
  }
23310
23510
  enableStreaming(providerReq, _req) {
23311
23511
  providerReq.body.stream = true;
@@ -23387,15 +23587,8 @@ var AnthropicAdapter = class {
23387
23587
  };
23388
23588
  content.push(tc);
23389
23589
  toolCalls.push(tc);
23390
- } else if (block.type === "code_execution_tool_result") {
23391
- const result = block.content;
23392
- if (result?.type === "code_execution_result" && Array.isArray(result.content)) {
23393
- for (const out of result.content) {
23394
- if (out.type === "code_execution_output" && typeof out.file_id === "string") {
23395
- files.push({ id: out.file_id, source: "code_execution" });
23396
- }
23397
- }
23398
- }
23590
+ } else {
23591
+ files.push(...filesFromCodeExecBlock(block));
23399
23592
  }
23400
23593
  }
23401
23594
  const finishReason = extractFinishReason(toolCalls.length > 0, r.stop_reason, {
@@ -23434,6 +23627,8 @@ var AnthropicAdapter = class {
23434
23627
  if (block.type === "tool_use") {
23435
23628
  return [{ type: "tool_call_start", id: block.id, name: block.name }];
23436
23629
  }
23630
+ const files = filesFromCodeExecBlock(block);
23631
+ if (files.length) return files.map((file) => ({ type: "file", file }));
23437
23632
  }
23438
23633
  if (type === "content_block_stop") {
23439
23634
  }
@@ -23457,6 +23652,11 @@ var AnthropicAdapter = class {
23457
23652
  }
23458
23653
  return [];
23459
23654
  }
23655
+ /** Stateless — every event is self-contained (code-execution result blocks
23656
+ * arrive complete in a single content_block_start). */
23657
+ createStreamParser() {
23658
+ return (event) => this.parseStreamEvent(event);
23659
+ }
23460
23660
  parseUsage(u) {
23461
23661
  if (!u) return emptyUsage();
23462
23662
  const inputTokens = u.input_tokens ?? 0;
@@ -23899,7 +24099,9 @@ var GoogleAdapter = class {
23899
24099
  const content = [];
23900
24100
  const toolCalls = [];
23901
24101
  const media = [];
24102
+ const files = [];
23902
24103
  let thinking = null;
24104
+ const hasCodeExec = parts.some((p) => p.executableCode || p.codeExecutionResult);
23903
24105
  for (const part of parts) {
23904
24106
  if (part.text !== void 0 && !part.thought) {
23905
24107
  content.push({ type: "text", text: part.text });
@@ -23910,7 +24112,9 @@ var GoogleAdapter = class {
23910
24112
  if (part.inlineData) {
23911
24113
  const inline = part.inlineData;
23912
24114
  const mime = inline.mimeType;
23913
- if (mime.startsWith("image/")) {
24115
+ if (hasCodeExec) {
24116
+ files.push({ data: inline.data, mimeType: mime, source: "code_execution" });
24117
+ } else if (mime.startsWith("image/")) {
23914
24118
  const p = {
23915
24119
  type: "image_output",
23916
24120
  mediaId: "",
@@ -23971,11 +24175,24 @@ var GoogleAdapter = class {
23971
24175
  toolCalls,
23972
24176
  thinking,
23973
24177
  media,
24178
+ ...files.length ? { files } : {},
23974
24179
  latencyMs,
23975
24180
  raw
23976
24181
  };
23977
24182
  }
23978
24183
  parseStreamEvent(event) {
24184
+ return this.streamEvents(event, { codeExec: false });
24185
+ }
24186
+ /** Stateful — Google splits the code-execution marker (`executableCode` /
24187
+ * `codeExecutionResult`) and the produced file (`inlineData`) across parts and
24188
+ * often across SSE events. The closure remembers "code execution began in this
24189
+ * stream" so a later `inlineData` blob is routed to `files` (a code-exec
24190
+ * artifact) rather than `media` (conversational output). */
24191
+ createStreamParser() {
24192
+ const state = { codeExec: false };
24193
+ return (event) => this.streamEvents(event, state);
24194
+ }
24195
+ streamEvents(event, state) {
23979
24196
  const data = JSON.parse(event.data);
23980
24197
  const candidates = data.candidates ?? [];
23981
24198
  const candidate = candidates[0];
@@ -23989,6 +24206,9 @@ var GoogleAdapter = class {
23989
24206
  const rawContent = candidate.content ?? {};
23990
24207
  const parts = rawContent.parts ?? [];
23991
24208
  const events = [];
24209
+ for (const part of parts) {
24210
+ if (part.executableCode || part.codeExecutionResult) state.codeExec = true;
24211
+ }
23992
24212
  for (const part of parts) {
23993
24213
  if (part.text !== void 0 && !part.thought)
23994
24214
  events.push({ type: "text", text: part.text });
@@ -23996,10 +24216,17 @@ var GoogleAdapter = class {
23996
24216
  if (part.inlineData) {
23997
24217
  const inline = part.inlineData;
23998
24218
  const mime = inline.mimeType;
23999
- const mediaType = mime.startsWith("image/") ? "image" : mime.startsWith("audio/") ? "audio" : "video";
24000
- events.push({ type: "media_start", mediaType, mimeType: mime });
24001
- events.push({ type: "media_chunk", data: inline.data });
24002
- events.push({ type: "media_end" });
24219
+ if (state.codeExec) {
24220
+ events.push({
24221
+ type: "file",
24222
+ file: { data: inline.data, mimeType: mime, source: "code_execution" }
24223
+ });
24224
+ } else {
24225
+ const mediaType = mime.startsWith("image/") ? "image" : mime.startsWith("audio/") ? "audio" : "video";
24226
+ events.push({ type: "media_start", mediaType, mimeType: mime });
24227
+ events.push({ type: "media_chunk", data: inline.data });
24228
+ events.push({ type: "media_end" });
24229
+ }
24003
24230
  }
24004
24231
  if (part.functionCall) {
24005
24232
  const fc = part.functionCall;
@@ -24294,6 +24521,10 @@ var GoogleInteractionsAdapter = class {
24294
24521
  }
24295
24522
  return events;
24296
24523
  }
24524
+ /** Stateless — the Interactions adapter surfaces no code-execution file outputs. */
24525
+ createStreamParser() {
24526
+ return (event) => this.parseStreamEvent(event);
24527
+ }
24297
24528
  parseUsage(u) {
24298
24529
  if (!u) return emptyUsage();
24299
24530
  const input = u.total_input_tokens ?? u.prompt_tokens ?? 0;
@@ -25259,6 +25490,10 @@ var OpenAIAdapter = class {
25259
25490
  }
25260
25491
  return events;
25261
25492
  }
25493
+ /** Stateless — Chat Completions has no hosted code-execution file outputs. */
25494
+ createStreamParser() {
25495
+ return (event) => this.parseStreamEvent(event);
25496
+ }
25262
25497
  parseUsage(u) {
25263
25498
  if (!u) return emptyUsage();
25264
25499
  const input = u.prompt_tokens ?? u.input_tokens ?? 0;
@@ -25774,6 +26009,33 @@ function filenameForMime(mimeType) {
25774
26009
  if (mimeType.startsWith("image/")) return `file.${mimeType.slice("image/".length)}`;
25775
26010
  return "file.bin";
25776
26011
  }
26012
+ function filesFromResponsesOutputItem(item) {
26013
+ const files = [];
26014
+ const type = item.type;
26015
+ if (type === "message") {
26016
+ for (const c of item.content ?? []) {
26017
+ if (c.type !== "output_text") continue;
26018
+ for (const a of c.annotations ?? []) {
26019
+ if (a.type === "container_file_citation" && typeof a.file_id === "string") {
26020
+ files.push({
26021
+ id: a.file_id,
26022
+ ...typeof a.filename === "string" ? { name: a.filename } : {},
26023
+ ...typeof a.container_id === "string" ? { ref: { containerId: a.container_id } } : {},
26024
+ source: "code_execution"
26025
+ });
26026
+ }
26027
+ }
26028
+ }
26029
+ }
26030
+ if (type === "code_interpreter_call") {
26031
+ for (const out of item.outputs ?? []) {
26032
+ if (out.type === "image" && typeof out.url === "string") {
26033
+ files.push({ url: out.url, source: "code_execution" });
26034
+ }
26035
+ }
26036
+ }
26037
+ return files;
26038
+ }
25777
26039
  var OpenAIResponsesAdapter = class {
25778
26040
  name = "openai";
25779
26041
  apiKey;
@@ -25949,10 +26211,12 @@ var OpenAIResponsesAdapter = class {
25949
26211
  const content = [];
25950
26212
  const toolCalls = [];
25951
26213
  const media = [];
26214
+ const files = [];
25952
26215
  let thinking = null;
25953
26216
  let text = "";
25954
26217
  for (const item of output) {
25955
26218
  const type = item.type;
26219
+ files.push(...filesFromResponsesOutputItem(item));
25956
26220
  if (type === "message") {
25957
26221
  const itemContent = item.content ?? [];
25958
26222
  for (const c of itemContent) {
@@ -26012,6 +26276,7 @@ var OpenAIResponsesAdapter = class {
26012
26276
  toolCalls,
26013
26277
  thinking,
26014
26278
  media,
26279
+ ...files.length ? { files } : {},
26015
26280
  ...moderation ? { moderation } : {},
26016
26281
  latencyMs,
26017
26282
  raw
@@ -26053,6 +26318,7 @@ var OpenAIResponsesAdapter = class {
26053
26318
  }
26054
26319
  if (type === "response.output_item.done") {
26055
26320
  const item = data.item;
26321
+ for (const file of filesFromResponsesOutputItem(item)) events.push({ type: "file", file });
26056
26322
  if (item?.type === "function_call") {
26057
26323
  events.push({ type: "tool_call_end", id: item.call_id ?? "" });
26058
26324
  }
@@ -26083,6 +26349,11 @@ var OpenAIResponsesAdapter = class {
26083
26349
  }
26084
26350
  return events;
26085
26351
  }
26352
+ /** Stateless — each output item finalizes with all its file annotations in a
26353
+ * single response.output_item.done event. */
26354
+ createStreamParser() {
26355
+ return (event) => this.parseStreamEvent(event);
26356
+ }
26086
26357
  parseUsage(u) {
26087
26358
  if (!u) return emptyUsage();
26088
26359
  const input = u.input_tokens ?? 0;
@@ -27738,6 +28009,7 @@ var AgentLoop = class _AgentLoop {
27738
28009
  _toolTimeout;
27739
28010
  _maxSteps;
27740
28011
  _guardrails;
28012
+ _toolInputGuardrails;
27741
28013
  _policy;
27742
28014
  _approve;
27743
28015
  _checkpoint;
@@ -27765,6 +28037,7 @@ var AgentLoop = class _AgentLoop {
27765
28037
  this._toolTimeout = config.toolTimeout ?? DEFAULT_TOOL_TIMEOUT_MS;
27766
28038
  this._maxSteps = config.maxSteps !== void 0 && config.maxSteps > 0 ? config.maxSteps : DEFAULT_MAX_STEPS;
27767
28039
  this._guardrails = config.guardrails ?? [];
28040
+ this._toolInputGuardrails = config.toolInputGuardrails ?? [];
27768
28041
  this._policy = config.policy ?? null;
27769
28042
  this._approve = config.approve ?? null;
27770
28043
  this._checkpoint = config.checkpoint ?? null;
@@ -27802,6 +28075,19 @@ var AgentLoop = class _AgentLoop {
27802
28075
  get model() {
27803
28076
  return this.client.model;
27804
28077
  }
28078
+ // ─── File retrieval (hosted-tool output files) ──────────────────────────
28079
+ /** Fetch a hosted-tool output file from the run's `response.files` — its bytes
28080
+ * as a `Blob` plus `name` / `mimeType` / `size`. Delegates to the underlying
28081
+ * client, so it uses this agent's provider + key (same as `retrieveFile` on a
28082
+ * `complete()` result). */
28083
+ retrieveFile(file) {
28084
+ return this.client.retrieveFile(file);
28085
+ }
28086
+ /** Stream a hosted-tool output file (large files) — a `ReadableStream` plus
28087
+ * best-effort `name` / `mimeType` / `size`. Delegates to the underlying client. */
28088
+ streamFile(file) {
28089
+ return this.client.streamFile(file);
28090
+ }
27805
28091
  get system() {
27806
28092
  return this._system;
27807
28093
  }
@@ -27983,6 +28269,10 @@ var AgentLoop = class _AgentLoop {
27983
28269
  toolCalls: lastResponse?.toolCalls ?? [],
27984
28270
  thinking: lastResponse?.thinking ?? null,
27985
28271
  media: lastResponse?.media ?? [],
28272
+ // Propagate hosted-tool file outputs + inline-moderation result from the final
28273
+ // LLM response (e.g. code-execution files produced during the run).
28274
+ ...lastResponse?.files ? { files: lastResponse.files } : {},
28275
+ ...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
27986
28276
  latencyMs: performance.now() - startPerf,
27987
28277
  raw: lastResponse?.raw ?? null
27988
28278
  };
@@ -28177,6 +28467,8 @@ var AgentLoop = class _AgentLoop {
28177
28467
  toolCalls: lastResponse?.toolCalls ?? [],
28178
28468
  thinking: lastResponse?.thinking ?? null,
28179
28469
  media: [],
28470
+ ...lastResponse?.files ? { files: lastResponse.files } : {},
28471
+ ...lastResponse?.moderation ? { moderation: lastResponse.moderation } : {},
28180
28472
  latencyMs: performance.now() - startPerf,
28181
28473
  raw: null
28182
28474
  };
@@ -28263,6 +28555,18 @@ var AgentLoop = class _AgentLoop {
28263
28555
  runTrace
28264
28556
  );
28265
28557
  if (!lookup.found) return lookup.errorResult;
28558
+ for (const g of this._toolInputGuardrails) {
28559
+ const decision = await g.check({
28560
+ toolName: tc.name,
28561
+ arguments: tc.arguments,
28562
+ callId: tc.id,
28563
+ step,
28564
+ trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id }
28565
+ });
28566
+ if (!decision.pass) {
28567
+ return this.buildDeniedResult(tc, decision.reason, reports);
28568
+ }
28569
+ }
28266
28570
  if (this._policy !== null) {
28267
28571
  const target = {
28268
28572
  kind: "tool",
@@ -28279,7 +28583,7 @@ var AgentLoop = class _AgentLoop {
28279
28583
  try {
28280
28584
  const baseCtx = { step, callId: tc.id, metrics, trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id } };
28281
28585
  const result = await executeWithTimeout(lookup.tool, tc, baseCtx, this._toolTimeout);
28282
- return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace);
28586
+ return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, lookup.tool, baseCtx);
28283
28587
  } catch (e) {
28284
28588
  return handleToolError(e, tc, this.hooks, runId, this.id, step, metrics, reports, toolStart, runTrace);
28285
28589
  }
@@ -28370,7 +28674,7 @@ var AgentLoop = class _AgentLoop {
28370
28674
  try {
28371
28675
  const baseCtx = { step, callId: tc.id, metrics, trace: { sessionId: runTrace.sessionId, requestId: runTrace.requestId, callId: tc.id } };
28372
28676
  const result = await executeWithTimeout(tool, tc, baseCtx, this._toolTimeout);
28373
- return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace);
28677
+ return await this.buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, tool, baseCtx);
28374
28678
  } catch (e) {
28375
28679
  return handleToolError(e, tc, this.hooks, runId, this.id, step, metrics, reports, toolStart, runTrace);
28376
28680
  }
@@ -28382,9 +28686,16 @@ var AgentLoop = class _AgentLoop {
28382
28686
  return this.buildDeniedResult(tc, denyMsg, reports);
28383
28687
  }
28384
28688
  /** Emit onToolCallComplete, push report, and return success content part. */
28385
- async buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace) {
28689
+ async buildSuccessResult(tc, result, runId, step, metrics, reports, toolStart, runTrace, tool, context) {
28386
28690
  const latencyMs = performance.now() - toolStart;
28387
28691
  const resultStr = typeof result === "string" ? result : JSON.stringify(result);
28692
+ let customData;
28693
+ if (tool?.customDataExtractor && context) {
28694
+ try {
28695
+ customData = await tool.customDataExtractor(result, tc.arguments, context);
28696
+ } catch {
28697
+ }
28698
+ }
28388
28699
  await this.hooks.emit("onToolCallComplete", {
28389
28700
  runId,
28390
28701
  agentId: this.id,
@@ -28406,7 +28717,8 @@ var AgentLoop = class _AgentLoop {
28406
28717
  latencyMs,
28407
28718
  skipped: false,
28408
28719
  error: null,
28409
- metrics: Object.fromEntries(metrics)
28720
+ metrics: Object.fromEntries(metrics),
28721
+ ...customData !== void 0 ? { customData } : {}
28410
28722
  });
28411
28723
  return { type: "tool_result", id: tc.id, content: resultStr };
28412
28724
  }
@@ -34410,7 +34722,13 @@ async function complete(opts) {
34410
34722
  serviceTier
34411
34723
  });
34412
34724
  }
34413
- const result = { text: res.text, response: res };
34725
+ const result = {
34726
+ text: res.text,
34727
+ response: res,
34728
+ // Bound to this call's client (same provider/model/key/engine).
34729
+ retrieveFile: (file) => llm.retrieveFile(file),
34730
+ streamFile: (file) => llm.streamFile(file)
34731
+ };
34414
34732
  if (opts.structured?.schema) {
34415
34733
  result.parsed = parseStructured(res.text);
34416
34734
  }
package/dist/index.d.ts CHANGED
@@ -49,6 +49,7 @@ export { isFunctionTool, isBuiltinTool } from './llm/types/tools';
49
49
  export type { FunctionTool, BuiltinTool, McpToolParams, Tool, ToolChoice, JsonSchema } from './llm/types/tools';
50
50
  export { emptyUsage } from './llm/types/response';
51
51
  export type { CompletionResponse, FileOutput, FinishReason, Usage } from './llm/types/response';
52
+ export type { FileStream, RetrievedFile } from './llm/files/retrieve';
52
53
  export type { NormalizedRequest, ReasoningContext, ThinkingConfig, CacheConfig } from './llm/types/request';
53
54
  export type { MediaStreamType, StreamEvent } from './llm/types/stream';
54
55
  export type { ExecuteOptions } from './llm/types/options';
@@ -98,7 +99,7 @@ export type { ConversationHistoryConfig, HistoryEntry, HistorySnapshot } from '.
98
99
  export { AgentLoop } from './agent/loop';
99
100
  export type { AgentLoopConfig } from './agent/loop-config';
100
101
  export type { AgentLoopSnapshot, AgentRunReport, AgentStreamEvent, AgentTool, ContentClass, LearnInput, StepReport, TokenCountContext, TokenCounter, ToolCallReport, ToolExecutionContext } from './agent/types';
101
- export type { Guardrail, GuardrailDecision, GuardrailPass, GuardrailTrip, GuardrailCheckContext, InputGuardrailContext, OutputGuardrailContext, GuardrailTriggeredContext } from './agent/guardrail-types';
102
+ export type { Guardrail, GuardrailDecision, GuardrailPass, GuardrailTrip, GuardrailCheckContext, InputGuardrailContext, OutputGuardrailContext, GuardrailTriggeredContext, ToolInputGuardrail, ToolInputGuardrailContext, ToolInputGuardrailDecision } from './agent/guardrail-types';
102
103
  export { BearerKeyAuth } from './server/auth';
103
104
  export type { AuthPlugin, AuthVerifyResult } from './server/auth';
104
105
  export { dispatch } from './server/dispatch';