@ljoukov/llm 4.0.1 → 4.0.3

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,6 +1,6 @@
1
1
  // src/llm.ts
2
- import { Buffer as Buffer3 } from "buffer";
3
- import { AsyncLocalStorage } from "async_hooks";
2
+ import { Buffer as Buffer4 } from "buffer";
3
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
4
4
  import { randomBytes } from "crypto";
5
5
  import {
6
6
  FinishReason,
@@ -205,6 +205,16 @@ var OPENAI_GPT_52_PRICING = {
205
205
  cachedRate: 0.175 / 1e6,
206
206
  outputRate: 14 / 1e6
207
207
  };
208
+ var OPENAI_GPT_54_PRICING = {
209
+ inputRate: 2.5 / 1e6,
210
+ cachedRate: 0.25 / 1e6,
211
+ outputRate: 15 / 1e6
212
+ };
213
+ var OPENAI_GPT_54_PRIORITY_PRICING = {
214
+ inputRate: 5 / 1e6,
215
+ cachedRate: 0.5 / 1e6,
216
+ outputRate: 30 / 1e6
217
+ };
208
218
  var OPENAI_GPT_53_CODEX_PRICING = {
209
219
  inputRate: 1.25 / 1e6,
210
220
  cachedRate: 0.125 / 1e6,
@@ -216,6 +226,12 @@ var OPENAI_GPT_5_MINI_PRICING = {
216
226
  outputRate: 2 / 1e6
217
227
  };
218
228
  function getOpenAiPricing(modelId) {
229
+ if (modelId.includes("gpt-5.4-fast")) {
230
+ return OPENAI_GPT_54_PRIORITY_PRICING;
231
+ }
232
+ if (modelId.includes("gpt-5.4")) {
233
+ return OPENAI_GPT_54_PRICING;
234
+ }
219
235
  if (modelId.includes("gpt-5.3-codex-spark")) {
220
236
  return OPENAI_GPT_5_MINI_PRICING;
221
237
  }
@@ -2626,11 +2642,18 @@ async function runOpenAiCall(fn, modelId, runOptions) {
2626
2642
  }
2627
2643
 
2628
2644
  // src/openai/models.ts
2629
- var OPENAI_MODEL_IDS = ["gpt-5.3-codex", "gpt-5.2", "gpt-5.1-codex-mini"];
2645
+ var OPENAI_MODEL_IDS = [
2646
+ "gpt-5.4",
2647
+ "gpt-5.3-codex",
2648
+ "gpt-5.2",
2649
+ "gpt-5.1-codex-mini"
2650
+ ];
2630
2651
  function isOpenAiModelId(value) {
2631
2652
  return OPENAI_MODEL_IDS.includes(value);
2632
2653
  }
2633
2654
  var CHATGPT_MODEL_IDS = [
2655
+ "chatgpt-gpt-5.4",
2656
+ "chatgpt-gpt-5.4-fast",
2634
2657
  "chatgpt-gpt-5.3-codex",
2635
2658
  "chatgpt-gpt-5.3-codex-spark",
2636
2659
  "chatgpt-gpt-5.2",
@@ -2642,9 +2665,387 @@ function isChatGptModelId(value) {
2642
2665
  function stripChatGptPrefix(model) {
2643
2666
  return model.slice("chatgpt-".length);
2644
2667
  }
2668
+ function resolveChatGptProviderModel(model) {
2669
+ switch (model) {
2670
+ case "chatgpt-gpt-5.4-fast":
2671
+ return "gpt-5.4";
2672
+ default:
2673
+ return stripChatGptPrefix(model);
2674
+ }
2675
+ }
2676
+ function resolveChatGptServiceTier(model) {
2677
+ return model === "chatgpt-gpt-5.4-fast" ? "priority" : void 0;
2678
+ }
2679
+
2680
+ // src/agentLogging.ts
2681
+ import { AsyncLocalStorage } from "async_hooks";
2682
+ import { Buffer as Buffer3 } from "buffer";
2683
+ import { appendFile, mkdir, writeFile } from "fs/promises";
2684
+ import path3 from "path";
2685
+ function toIsoNow() {
2686
+ return (/* @__PURE__ */ new Date()).toISOString();
2687
+ }
2688
+ function toErrorMessage(error) {
2689
+ if (error instanceof Error && error.message) {
2690
+ return error.message;
2691
+ }
2692
+ if (typeof error === "string") {
2693
+ return error;
2694
+ }
2695
+ return String(error);
2696
+ }
2697
+ function isPromiseLike(value) {
2698
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
2699
+ }
2700
+ function normalisePathSegment(value) {
2701
+ const cleaned = value.trim().replace(/[^a-z0-9._-]+/giu, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
2702
+ return cleaned.length > 0 ? cleaned : "segment";
2703
+ }
2704
+ function ensureTrailingNewline(value) {
2705
+ return value.endsWith("\n") ? value : `${value}
2706
+ `;
2707
+ }
2708
+ function redactDataUrlPayload(value) {
2709
+ if (!value.toLowerCase().startsWith("data:")) {
2710
+ return value;
2711
+ }
2712
+ const commaIndex = value.indexOf(",");
2713
+ if (commaIndex < 0) {
2714
+ return value;
2715
+ }
2716
+ return `${value.slice(0, commaIndex + 1)}...`;
2717
+ }
2718
+ function sanitiseLogValue(value, seen = /* @__PURE__ */ new WeakSet()) {
2719
+ if (typeof value === "string") {
2720
+ return redactDataUrlPayload(value);
2721
+ }
2722
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) {
2723
+ return value;
2724
+ }
2725
+ if (Array.isArray(value)) {
2726
+ return value.map((entry) => sanitiseLogValue(entry, seen));
2727
+ }
2728
+ if (typeof value !== "object") {
2729
+ return String(value);
2730
+ }
2731
+ if (seen.has(value)) {
2732
+ return "[circular]";
2733
+ }
2734
+ seen.add(value);
2735
+ const record = value;
2736
+ const output = {};
2737
+ const hasInlineMime = typeof record.mimeType === "string" && record.mimeType.trim().length > 0 || typeof record.mime_type === "string" && record.mime_type.trim().length > 0;
2738
+ for (const [key, entryValue] of Object.entries(record)) {
2739
+ if (key === "image_url") {
2740
+ if (typeof entryValue === "string") {
2741
+ output[key] = redactDataUrlPayload(entryValue);
2742
+ continue;
2743
+ }
2744
+ if (entryValue && typeof entryValue === "object") {
2745
+ const nested = entryValue;
2746
+ if (typeof nested.url === "string") {
2747
+ output[key] = {
2748
+ ...nested,
2749
+ url: redactDataUrlPayload(nested.url)
2750
+ };
2751
+ continue;
2752
+ }
2753
+ }
2754
+ }
2755
+ if (key === "data" && hasInlineMime && typeof entryValue === "string") {
2756
+ output[key] = `[omitted:${Buffer3.byteLength(entryValue, "utf8")}b]`;
2757
+ continue;
2758
+ }
2759
+ output[key] = sanitiseLogValue(entryValue, seen);
2760
+ }
2761
+ return output;
2762
+ }
2763
+ function serialiseForSnippet(value) {
2764
+ if (typeof value === "string") {
2765
+ return value;
2766
+ }
2767
+ try {
2768
+ return JSON.stringify(sanitiseLogValue(value));
2769
+ } catch {
2770
+ return String(value);
2771
+ }
2772
+ }
2773
+ function formatToolLogSnippet(value) {
2774
+ const compact = serialiseForSnippet(value).replace(/\s+/gu, " ").trim();
2775
+ if (compact.length === 0) {
2776
+ return "<empty>";
2777
+ }
2778
+ const max = 600;
2779
+ if (compact.length <= max) {
2780
+ return compact;
2781
+ }
2782
+ return `${compact.slice(0, max)}...`;
2783
+ }
2784
+ function formatUsd(value) {
2785
+ const amount = typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
2786
+ return amount.toFixed(6);
2787
+ }
2788
+ function appendToolCallStreamLog(options) {
2789
+ const event = options.event;
2790
+ if (event.type !== "tool_call") {
2791
+ return;
2792
+ }
2793
+ const callIdSegment = typeof event.callId === "string" && event.callId.trim().length > 0 ? ` callId=${event.callId}` : "";
2794
+ const prefix = [
2795
+ `tool_call_${event.phase}:`,
2796
+ `turn=${event.turn.toString()}`,
2797
+ `index=${event.toolIndex.toString()}`,
2798
+ `tool=${event.toolName}${callIdSegment}`
2799
+ ].join(" ");
2800
+ if (event.phase === "started") {
2801
+ options.append(prefix);
2802
+ options.append(`tool_call_input: ${formatToolLogSnippet(sanitiseLogValue(event.input))}`);
2803
+ return;
2804
+ }
2805
+ const durationSegment = typeof event.durationMs === "number" && Number.isFinite(event.durationMs) ? ` durationMs=${Math.max(0, Math.round(event.durationMs)).toString()}` : "";
2806
+ options.append(`${prefix} status=${event.error ? "error" : "ok"}${durationSegment}`);
2807
+ options.append(`tool_call_output: ${formatToolLogSnippet(sanitiseLogValue(event.output))}`);
2808
+ if (typeof event.error === "string" && event.error.trim().length > 0) {
2809
+ options.append(`tool_call_error: ${event.error.trim()}`);
2810
+ }
2811
+ }
2812
+ function appendAgentStreamEventLog(options) {
2813
+ const event = options.event;
2814
+ switch (event.type) {
2815
+ case "delta": {
2816
+ const channelPrefix = event.channel === "thought" ? "thought_delta" : "response_delta";
2817
+ options.append(`${channelPrefix}: ${event.text}`);
2818
+ return;
2819
+ }
2820
+ case "model": {
2821
+ options.append(`model: ${event.modelVersion}`);
2822
+ return;
2823
+ }
2824
+ case "usage": {
2825
+ options.append(
2826
+ [
2827
+ "usage:",
2828
+ `modelVersion=${event.modelVersion}`,
2829
+ `costUsd=${formatUsd(event.costUsd)}`,
2830
+ `tokens=${formatToolLogSnippet(sanitiseLogValue(event.usage))}`
2831
+ ].join(" ")
2832
+ );
2833
+ return;
2834
+ }
2835
+ case "blocked": {
2836
+ options.append("blocked");
2837
+ return;
2838
+ }
2839
+ case "tool_call": {
2840
+ appendToolCallStreamLog({
2841
+ event,
2842
+ append: options.append
2843
+ });
2844
+ return;
2845
+ }
2846
+ }
2847
+ }
2848
+ var AgentLoggingSessionImpl = class {
2849
+ workspaceDir;
2850
+ logsRootDir;
2851
+ mirrorToConsole;
2852
+ sink;
2853
+ agentLogPath;
2854
+ ensureReady;
2855
+ pending = /* @__PURE__ */ new Set();
2856
+ lineChain = Promise.resolve();
2857
+ callCounter = 0;
2858
+ constructor(config) {
2859
+ this.workspaceDir = path3.resolve(config.workspaceDir ?? process.cwd());
2860
+ this.logsRootDir = path3.join(path3.dirname(this.workspaceDir), "logs");
2861
+ this.mirrorToConsole = config.mirrorToConsole !== false;
2862
+ this.sink = config.sink;
2863
+ this.agentLogPath = path3.join(this.workspaceDir, "agent.log");
2864
+ this.ensureReady = this.prepare();
2865
+ this.track(this.ensureReady);
2866
+ }
2867
+ async prepare() {
2868
+ await mkdir(this.workspaceDir, { recursive: true });
2869
+ await mkdir(this.logsRootDir, { recursive: true });
2870
+ }
2871
+ track(task) {
2872
+ this.pending.add(task);
2873
+ task.finally(() => {
2874
+ this.pending.delete(task);
2875
+ });
2876
+ }
2877
+ enqueueLineWrite(line) {
2878
+ const next = this.lineChain.then(async () => {
2879
+ await this.ensureReady;
2880
+ await appendFile(this.agentLogPath, `${line}
2881
+ `, "utf8");
2882
+ const sinkResult = this.sink?.append(line);
2883
+ if (isPromiseLike(sinkResult)) {
2884
+ await sinkResult;
2885
+ }
2886
+ });
2887
+ const tracked = next.catch(() => void 0);
2888
+ this.lineChain = tracked;
2889
+ this.track(tracked);
2890
+ }
2891
+ logLine(line) {
2892
+ const timestamped = `${toIsoNow()} ${line}`;
2893
+ if (this.mirrorToConsole) {
2894
+ console.log(timestamped);
2895
+ }
2896
+ this.enqueueLineWrite(timestamped);
2897
+ }
2898
+ startLlmCall(input) {
2899
+ const callNumber = this.callCounter + 1;
2900
+ this.callCounter = callNumber;
2901
+ const timestampSegment = toIsoNow().replace(/[:]/g, "-");
2902
+ const modelSegment = normalisePathSegment(input.modelId);
2903
+ const baseDir = path3.join(
2904
+ this.logsRootDir,
2905
+ `${timestampSegment}-${callNumber.toString().padStart(4, "0")}`,
2906
+ modelSegment
2907
+ );
2908
+ const responsePath = path3.join(baseDir, "response.txt");
2909
+ const thoughtsPath = path3.join(baseDir, "thoughts.txt");
2910
+ const responseMetadataPath = path3.join(baseDir, "response.metadata.json");
2911
+ let chain = this.ensureReady.then(async () => {
2912
+ await mkdir(baseDir, { recursive: true });
2913
+ const requestText = input.requestText.trim().length > 0 ? input.requestText : "<empty request>";
2914
+ await writeFile(
2915
+ path3.join(baseDir, "request.txt"),
2916
+ ensureTrailingNewline(requestText),
2917
+ "utf8"
2918
+ );
2919
+ const requestMetadata = {
2920
+ capturedAt: toIsoNow(),
2921
+ provider: input.provider,
2922
+ modelId: input.modelId,
2923
+ ...input.requestMetadata ? { request: sanitiseLogValue(input.requestMetadata) } : {}
2924
+ };
2925
+ await writeFile(
2926
+ path3.join(baseDir, "request.metadata.json"),
2927
+ `${JSON.stringify(requestMetadata, null, 2)}
2928
+ `,
2929
+ "utf8"
2930
+ );
2931
+ const usedNames = /* @__PURE__ */ new Set();
2932
+ for (const attachment of input.attachments ?? []) {
2933
+ let filename = normalisePathSegment(attachment.filename);
2934
+ if (!filename.includes(".")) {
2935
+ filename = `${filename}.bin`;
2936
+ }
2937
+ const ext = path3.extname(filename);
2938
+ const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
2939
+ let candidate = filename;
2940
+ let duplicateIndex = 2;
2941
+ while (usedNames.has(candidate)) {
2942
+ candidate = `${base}-${duplicateIndex.toString()}${ext}`;
2943
+ duplicateIndex += 1;
2944
+ }
2945
+ usedNames.add(candidate);
2946
+ await writeFile(path3.join(baseDir, candidate), attachment.bytes);
2947
+ }
2948
+ }).catch(() => void 0);
2949
+ this.track(chain);
2950
+ let closed = false;
2951
+ const enqueue = (operation) => {
2952
+ const next = chain.then(operation);
2953
+ const tracked = next.catch(() => void 0);
2954
+ chain = tracked;
2955
+ this.track(tracked);
2956
+ };
2957
+ return {
2958
+ appendThoughtDelta: (text) => {
2959
+ if (closed || text.length === 0) {
2960
+ return;
2961
+ }
2962
+ enqueue(async () => {
2963
+ await appendFile(thoughtsPath, text, "utf8");
2964
+ });
2965
+ },
2966
+ appendResponseDelta: (text) => {
2967
+ if (closed || text.length === 0) {
2968
+ return;
2969
+ }
2970
+ enqueue(async () => {
2971
+ await appendFile(responsePath, text, "utf8");
2972
+ });
2973
+ },
2974
+ complete: (metadata) => {
2975
+ if (closed) {
2976
+ return;
2977
+ }
2978
+ closed = true;
2979
+ enqueue(async () => {
2980
+ const payload = {
2981
+ capturedAt: toIsoNow(),
2982
+ status: "completed"
2983
+ };
2984
+ if (metadata) {
2985
+ const sanitised = sanitiseLogValue(metadata);
2986
+ if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
2987
+ Object.assign(payload, sanitised);
2988
+ } else if (sanitised !== void 0) {
2989
+ payload.metadata = sanitised;
2990
+ }
2991
+ }
2992
+ await writeFile(responseMetadataPath, `${JSON.stringify(payload, null, 2)}
2993
+ `, "utf8");
2994
+ });
2995
+ },
2996
+ fail: (error, metadata) => {
2997
+ if (closed) {
2998
+ return;
2999
+ }
3000
+ closed = true;
3001
+ enqueue(async () => {
3002
+ const payload = {
3003
+ capturedAt: toIsoNow(),
3004
+ status: "failed",
3005
+ error: toErrorMessage(error)
3006
+ };
3007
+ if (metadata) {
3008
+ const sanitised = sanitiseLogValue(metadata);
3009
+ if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3010
+ Object.assign(payload, sanitised);
3011
+ } else if (sanitised !== void 0) {
3012
+ payload.metadata = sanitised;
3013
+ }
3014
+ }
3015
+ await writeFile(responseMetadataPath, `${JSON.stringify(payload, null, 2)}
3016
+ `, "utf8");
3017
+ });
3018
+ }
3019
+ };
3020
+ }
3021
+ async flush() {
3022
+ while (this.pending.size > 0) {
3023
+ await Promise.allSettled([...this.pending]);
3024
+ }
3025
+ if (typeof this.sink?.flush === "function") {
3026
+ try {
3027
+ await this.sink.flush();
3028
+ } catch {
3029
+ }
3030
+ }
3031
+ }
3032
+ };
3033
+ var loggingSessionStorage = new AsyncLocalStorage();
3034
+ function createAgentLoggingSession(config) {
3035
+ return new AgentLoggingSessionImpl(config);
3036
+ }
3037
+ function runWithAgentLoggingSession(session, fn) {
3038
+ if (!session) {
3039
+ return fn();
3040
+ }
3041
+ return loggingSessionStorage.run(session, fn);
3042
+ }
3043
+ function getCurrentAgentLoggingSession() {
3044
+ return loggingSessionStorage.getStore();
3045
+ }
2645
3046
 
2646
3047
  // src/llm.ts
2647
- var toolCallContextStorage = new AsyncLocalStorage();
3048
+ var toolCallContextStorage = new AsyncLocalStorage2();
2648
3049
  function getCurrentToolCallContext() {
2649
3050
  return toolCallContextStorage.getStore() ?? null;
2650
3051
  }
@@ -2936,9 +3337,9 @@ function sanitisePartForLogging(part) {
2936
3337
  case "inlineData": {
2937
3338
  let omittedBytes;
2938
3339
  try {
2939
- omittedBytes = Buffer3.from(part.data, "base64").byteLength;
3340
+ omittedBytes = Buffer4.from(part.data, "base64").byteLength;
2940
3341
  } catch {
2941
- omittedBytes = Buffer3.byteLength(part.data, "utf8");
3342
+ omittedBytes = Buffer4.byteLength(part.data, "utf8");
2942
3343
  }
2943
3344
  return {
2944
3345
  type: "inlineData",
@@ -3023,7 +3424,11 @@ function convertLlmContentToGeminiContent(content) {
3023
3424
  }
3024
3425
  function resolveProvider(model) {
3025
3426
  if (isChatGptModelId(model)) {
3026
- return { provider: "chatgpt", model: stripChatGptPrefix(model) };
3427
+ return {
3428
+ provider: "chatgpt",
3429
+ model: resolveChatGptProviderModel(model),
3430
+ serviceTier: resolveChatGptServiceTier(model)
3431
+ };
3027
3432
  }
3028
3433
  if (isGeminiTextModelId(model) || isGeminiImageModelId(model)) {
3029
3434
  return { provider: "gemini", model };
@@ -4001,8 +4406,8 @@ function parseOpenAiToolArguments(raw) {
4001
4406
  function formatZodIssues(issues) {
4002
4407
  const messages = [];
4003
4408
  for (const issue of issues) {
4004
- const path6 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
4005
- messages.push(`${path6}: ${issue.message}`);
4409
+ const path8 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
4410
+ messages.push(`${path8}: ${issue.message}`);
4006
4411
  }
4007
4412
  return messages.join("; ");
4008
4413
  }
@@ -4388,9 +4793,9 @@ function resolveGeminiThinkingConfig(modelId, thinkingLevel) {
4388
4793
  }
4389
4794
  function decodeInlineDataBuffer(base64) {
4390
4795
  try {
4391
- return Buffer3.from(base64, "base64");
4796
+ return Buffer4.from(base64, "base64");
4392
4797
  } catch {
4393
- return Buffer3.from(base64, "base64url");
4798
+ return Buffer4.from(base64, "base64url");
4394
4799
  }
4395
4800
  }
4396
4801
  function extractImages(content) {
@@ -4407,6 +4812,195 @@ function extractImages(content) {
4407
4812
  }
4408
4813
  return images;
4409
4814
  }
4815
+ function resolveAttachmentExtension(mimeType) {
4816
+ const normalized = (mimeType ?? "").trim().toLowerCase();
4817
+ switch (normalized) {
4818
+ case "image/jpeg":
4819
+ return "jpg";
4820
+ case "image/png":
4821
+ return "png";
4822
+ case "image/webp":
4823
+ return "webp";
4824
+ case "image/gif":
4825
+ return "gif";
4826
+ case "image/heic":
4827
+ return "heic";
4828
+ case "image/heif":
4829
+ return "heif";
4830
+ case "application/pdf":
4831
+ return "pdf";
4832
+ case "application/json":
4833
+ return "json";
4834
+ case "text/plain":
4835
+ return "txt";
4836
+ case "text/markdown":
4837
+ return "md";
4838
+ default: {
4839
+ const slashIndex = normalized.indexOf("/");
4840
+ if (slashIndex >= 0) {
4841
+ const subtype = normalized.slice(slashIndex + 1).split("+")[0] ?? "";
4842
+ const cleaned = subtype.replace(/[^a-z0-9]+/giu, "");
4843
+ if (cleaned.length > 0) {
4844
+ return cleaned;
4845
+ }
4846
+ }
4847
+ return "bin";
4848
+ }
4849
+ }
4850
+ }
4851
+ function decodeDataUrlAttachment(value, basename) {
4852
+ const trimmed = value.trim();
4853
+ if (!trimmed.toLowerCase().startsWith("data:")) {
4854
+ return null;
4855
+ }
4856
+ const commaIndex = trimmed.indexOf(",");
4857
+ if (commaIndex < 0) {
4858
+ return null;
4859
+ }
4860
+ const header = trimmed.slice(5, commaIndex);
4861
+ const payload = trimmed.slice(commaIndex + 1);
4862
+ const isBase64 = /;base64(?:;|$)/iu.test(header);
4863
+ const mimeType = (header.split(";")[0] ?? "application/octet-stream").trim().toLowerCase();
4864
+ try {
4865
+ const bytes = isBase64 ? Buffer4.from(payload, "base64") : Buffer4.from(decodeURIComponent(payload), "utf8");
4866
+ return {
4867
+ filename: `${basename}.${resolveAttachmentExtension(mimeType)}`,
4868
+ bytes
4869
+ };
4870
+ } catch {
4871
+ return null;
4872
+ }
4873
+ }
4874
+ function collectPayloadAttachments(value, options) {
4875
+ if (typeof value === "string") {
4876
+ const attachment = decodeDataUrlAttachment(
4877
+ value,
4878
+ `${options.prefix}-${options.counter.toString()}`
4879
+ );
4880
+ if (attachment) {
4881
+ options.attachments.push(attachment);
4882
+ options.counter += 1;
4883
+ }
4884
+ return;
4885
+ }
4886
+ if (!value || typeof value !== "object") {
4887
+ return;
4888
+ }
4889
+ if (options.seen.has(value)) {
4890
+ return;
4891
+ }
4892
+ options.seen.add(value);
4893
+ if (Array.isArray(value)) {
4894
+ for (const entry of value) {
4895
+ collectPayloadAttachments(entry, options);
4896
+ }
4897
+ return;
4898
+ }
4899
+ const record = value;
4900
+ const mimeType = typeof record.mimeType === "string" ? record.mimeType : void 0;
4901
+ if (typeof record.data === "string" && mimeType) {
4902
+ try {
4903
+ options.attachments.push({
4904
+ filename: `${options.prefix}-${options.counter.toString()}.${resolveAttachmentExtension(mimeType)}`,
4905
+ bytes: decodeInlineDataBuffer(record.data)
4906
+ });
4907
+ options.counter += 1;
4908
+ } catch {
4909
+ }
4910
+ }
4911
+ for (const entry of Object.values(record)) {
4912
+ collectPayloadAttachments(entry, options);
4913
+ }
4914
+ }
4915
+ function serialiseRequestPayloadForLogging(value) {
4916
+ try {
4917
+ return `${JSON.stringify(sanitiseLogValue(value), null, 2)}
4918
+ `;
4919
+ } catch {
4920
+ return `${String(value)}
4921
+ `;
4922
+ }
4923
+ }
4924
+ function startLlmCallLoggerFromContents(options) {
4925
+ const session = getCurrentAgentLoggingSession();
4926
+ if (!session) {
4927
+ return void 0;
4928
+ }
4929
+ const attachments = [];
4930
+ const sections = [];
4931
+ for (const [messageIndex, message] of options.contents.entries()) {
4932
+ sections.push(`### message_${(messageIndex + 1).toString()} role=${message.role}`);
4933
+ for (const [partIndex, part] of message.parts.entries()) {
4934
+ if (part.type === "text") {
4935
+ const channel = part.thought === true ? "thought" : "response";
4936
+ sections.push(`[text:${channel}]`);
4937
+ sections.push(part.text);
4938
+ continue;
4939
+ }
4940
+ const filename = `message-${(messageIndex + 1).toString()}-part-${(partIndex + 1).toString()}.${resolveAttachmentExtension(part.mimeType)}`;
4941
+ attachments.push({
4942
+ filename,
4943
+ bytes: decodeInlineDataBuffer(part.data)
4944
+ });
4945
+ sections.push(
4946
+ `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
4947
+ );
4948
+ }
4949
+ sections.push("");
4950
+ }
4951
+ return session.startLlmCall({
4952
+ provider: options.provider,
4953
+ modelId: options.request.model,
4954
+ requestText: sections.join("\n").trim(),
4955
+ requestMetadata: {
4956
+ model: options.request.model,
4957
+ input: options.contents.map((content) => ({
4958
+ role: content.role,
4959
+ parts: content.parts.map((part) => sanitisePartForLogging(part))
4960
+ })),
4961
+ ...options.request.instructions ? {
4962
+ instructions: options.request.instructions
4963
+ } : {},
4964
+ ...options.request.tools ? { tools: options.request.tools } : {},
4965
+ ...options.request.responseMimeType ? {
4966
+ responseMimeType: options.request.responseMimeType
4967
+ } : {},
4968
+ ...options.request.responseJsonSchema ? {
4969
+ responseJsonSchema: sanitiseLogValue(options.request.responseJsonSchema)
4970
+ } : {},
4971
+ ...options.request.responseModalities ? { responseModalities: options.request.responseModalities } : {},
4972
+ ...options.request.imageAspectRatio ? { imageAspectRatio: options.request.imageAspectRatio } : {},
4973
+ ...options.request.imageSize ? { imageSize: options.request.imageSize } : {},
4974
+ ...options.request.thinkingLevel ? { thinkingLevel: options.request.thinkingLevel } : {},
4975
+ ...options.request.openAiTextFormat ? { openAiTextFormat: sanitiseLogValue(options.request.openAiTextFormat) } : {},
4976
+ ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
4977
+ },
4978
+ attachments
4979
+ });
4980
+ }
4981
+ function startLlmCallLoggerFromPayload(options) {
4982
+ const session = getCurrentAgentLoggingSession();
4983
+ if (!session) {
4984
+ return void 0;
4985
+ }
4986
+ const attachments = [];
4987
+ collectPayloadAttachments(options.requestPayload, {
4988
+ prefix: `step-${options.step.toString()}`,
4989
+ attachments,
4990
+ seen: /* @__PURE__ */ new WeakSet(),
4991
+ counter: 1
4992
+ });
4993
+ return session.startLlmCall({
4994
+ provider: options.provider,
4995
+ modelId: options.modelId,
4996
+ requestText: serialiseRequestPayloadForLogging(options.requestPayload),
4997
+ requestMetadata: {
4998
+ step: options.step,
4999
+ ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
5000
+ },
5001
+ attachments
5002
+ });
5003
+ }
4410
5004
  async function runTextCall(params) {
4411
5005
  const { request, queue, abortController } = params;
4412
5006
  const providerInfo = resolveProvider(request.model);
@@ -4416,6 +5010,11 @@ async function runTextCall(params) {
4416
5010
  if (contents.length === 0) {
4417
5011
  throw new Error("LLM call received an empty prompt.");
4418
5012
  }
5013
+ const callLogger = startLlmCallLoggerFromContents({
5014
+ provider,
5015
+ request,
5016
+ contents
5017
+ });
4419
5018
  let modelVersion = request.model;
4420
5019
  let blocked = false;
4421
5020
  let grounding;
@@ -4423,12 +5022,17 @@ async function runTextCall(params) {
4423
5022
  let responseRole;
4424
5023
  let latestUsage;
4425
5024
  let responseImages = 0;
4426
- const pushDelta = (channel, text2) => {
4427
- if (!text2) {
5025
+ const pushDelta = (channel, text) => {
5026
+ if (!text) {
4428
5027
  return;
4429
5028
  }
4430
- responseParts.push({ type: "text", text: text2, ...channel === "thought" ? { thought: true } : {} });
4431
- queue.push({ type: "delta", channel, text: text2 });
5029
+ responseParts.push({ type: "text", text, ...channel === "thought" ? { thought: true } : {} });
5030
+ if (channel === "thought") {
5031
+ callLogger?.appendThoughtDelta(text);
5032
+ } else {
5033
+ callLogger?.appendResponseDelta(text);
5034
+ }
5035
+ queue.push({ type: "delta", channel, text });
4432
5036
  };
4433
5037
  const pushInline = (data, mimeType) => {
4434
5038
  if (!data) {
@@ -4455,263 +5059,295 @@ async function runTextCall(params) {
4455
5059
  return abortController.signal;
4456
5060
  };
4457
5061
  const signal = resolveAbortSignal();
4458
- if (provider === "openai") {
4459
- const openAiInput = toOpenAiInput(contents);
4460
- const openAiTools = toOpenAiTools(request.tools);
4461
- const reasoningEffort = resolveOpenAiReasoningEffort(modelForProvider, request.thinkingLevel);
4462
- const openAiTextConfig = {
4463
- format: request.openAiTextFormat ?? { type: "text" },
4464
- verbosity: resolveOpenAiVerbosity(modelForProvider)
4465
- };
4466
- const reasoning = {
4467
- effort: toOpenAiReasoningEffort(reasoningEffort),
4468
- summary: "detailed"
4469
- };
4470
- await runOpenAiCall(async (client) => {
4471
- const stream = client.responses.stream(
4472
- {
4473
- model: modelForProvider,
4474
- input: openAiInput,
4475
- reasoning,
4476
- text: openAiTextConfig,
4477
- ...openAiTools ? { tools: openAiTools } : {},
4478
- include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
4479
- },
4480
- { signal }
4481
- );
4482
- for await (const event of stream) {
4483
- switch (event.type) {
4484
- case "response.output_text.delta": {
4485
- const delta = event.delta ?? "";
4486
- pushDelta("response", typeof delta === "string" ? delta : "");
4487
- break;
4488
- }
4489
- case "response.reasoning_summary_text.delta": {
4490
- const delta = event.delta ?? "";
4491
- pushDelta("thought", typeof delta === "string" ? delta : "");
4492
- break;
4493
- }
4494
- case "response.refusal.delta": {
4495
- blocked = true;
4496
- queue.push({ type: "blocked" });
4497
- break;
4498
- }
4499
- default:
4500
- break;
4501
- }
4502
- }
4503
- const finalResponse = await stream.finalResponse();
4504
- modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
4505
- queue.push({ type: "model", modelVersion });
4506
- if (finalResponse.error) {
4507
- const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
4508
- throw new Error(message);
4509
- }
4510
- if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
4511
- const detail = finalResponse.incomplete_details?.reason;
4512
- throw new Error(
4513
- `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
5062
+ try {
5063
+ if (provider === "openai") {
5064
+ const openAiInput = toOpenAiInput(contents);
5065
+ const openAiTools = toOpenAiTools(request.tools);
5066
+ const reasoningEffort = resolveOpenAiReasoningEffort(modelForProvider, request.thinkingLevel);
5067
+ const openAiTextConfig = {
5068
+ format: request.openAiTextFormat ?? { type: "text" },
5069
+ verbosity: resolveOpenAiVerbosity(modelForProvider)
5070
+ };
5071
+ const reasoning = {
5072
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5073
+ summary: "detailed"
5074
+ };
5075
+ await runOpenAiCall(async (client) => {
5076
+ const stream = client.responses.stream(
5077
+ {
5078
+ model: modelForProvider,
5079
+ input: openAiInput,
5080
+ reasoning,
5081
+ text: openAiTextConfig,
5082
+ ...openAiTools ? { tools: openAiTools } : {},
5083
+ include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
5084
+ },
5085
+ { signal }
4514
5086
  );
4515
- }
4516
- latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
4517
- if (responseParts.length === 0) {
4518
- const fallback = extractOpenAiResponseParts(finalResponse);
4519
- blocked = blocked || fallback.blocked;
4520
- for (const part of fallback.parts) {
4521
- if (part.type === "text") {
4522
- pushDelta(part.thought === true ? "thought" : "response", part.text);
4523
- } else {
4524
- pushInline(part.data, part.mimeType);
5087
+ for await (const event of stream) {
5088
+ switch (event.type) {
5089
+ case "response.output_text.delta": {
5090
+ const delta = event.delta ?? "";
5091
+ pushDelta("response", typeof delta === "string" ? delta : "");
5092
+ break;
5093
+ }
5094
+ case "response.reasoning_summary_text.delta": {
5095
+ const delta = event.delta ?? "";
5096
+ pushDelta("thought", typeof delta === "string" ? delta : "");
5097
+ break;
5098
+ }
5099
+ case "response.refusal.delta": {
5100
+ blocked = true;
5101
+ queue.push({ type: "blocked" });
5102
+ break;
5103
+ }
5104
+ default:
5105
+ break;
4525
5106
  }
4526
5107
  }
4527
- }
4528
- }, modelForProvider);
4529
- } else if (provider === "chatgpt") {
4530
- const chatGptInput = toChatGptInput(contents);
4531
- const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
4532
- const openAiTools = toOpenAiTools(request.tools);
4533
- const requestPayload = {
4534
- model: modelForProvider,
4535
- store: false,
4536
- stream: true,
4537
- instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
4538
- input: chatGptInput.input,
4539
- include: ["reasoning.encrypted_content"],
4540
- reasoning: { effort: toOpenAiReasoningEffort(reasoningEffort), summary: "detailed" },
4541
- text: {
4542
- format: request.openAiTextFormat ?? { type: "text" },
4543
- verbosity: resolveOpenAiVerbosity(request.model)
4544
- },
4545
- ...openAiTools ? { tools: openAiTools } : {}
4546
- };
4547
- let sawResponseDelta = false;
4548
- let sawThoughtDelta = false;
4549
- const result = await collectChatGptCodexResponseWithRetry({
4550
- request: requestPayload,
4551
- signal,
4552
- onDelta: (delta) => {
4553
- if (delta.thoughtDelta) {
4554
- sawThoughtDelta = true;
4555
- pushDelta("thought", delta.thoughtDelta);
5108
+ const finalResponse = await stream.finalResponse();
5109
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5110
+ queue.push({ type: "model", modelVersion });
5111
+ if (finalResponse.error) {
5112
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5113
+ throw new Error(message);
4556
5114
  }
4557
- if (delta.textDelta) {
4558
- sawResponseDelta = true;
4559
- pushDelta("response", delta.textDelta);
5115
+ if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
5116
+ const detail = finalResponse.incomplete_details?.reason;
5117
+ throw new Error(
5118
+ `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
5119
+ );
4560
5120
  }
4561
- }
4562
- });
4563
- blocked = blocked || result.blocked;
4564
- if (blocked) {
4565
- queue.push({ type: "blocked" });
4566
- }
4567
- if (result.model) {
4568
- modelVersion = `chatgpt-${result.model}`;
4569
- queue.push({ type: "model", modelVersion });
4570
- }
4571
- latestUsage = extractChatGptUsageTokens(result.usage);
4572
- const fallbackText = typeof result.text === "string" ? result.text : "";
4573
- const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
4574
- if (!sawThoughtDelta && fallbackThoughts.length > 0) {
4575
- pushDelta("thought", fallbackThoughts);
4576
- }
4577
- if (!sawResponseDelta && fallbackText.length > 0) {
4578
- pushDelta("response", fallbackText);
4579
- }
4580
- } else if (provider === "fireworks") {
4581
- if (request.tools && request.tools.length > 0) {
4582
- throw new Error(
4583
- "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
4584
- );
4585
- }
4586
- const fireworksMessages = toFireworksMessages(contents, {
4587
- responseMimeType: request.responseMimeType,
4588
- responseJsonSchema: request.responseJsonSchema
4589
- });
4590
- await runFireworksCall(async (client) => {
4591
- const responseFormat = request.responseJsonSchema ? {
4592
- type: "json_schema",
4593
- json_schema: {
4594
- name: "llm-response",
4595
- schema: request.responseJsonSchema
5121
+ latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
5122
+ if (responseParts.length === 0) {
5123
+ const fallback = extractOpenAiResponseParts(finalResponse);
5124
+ blocked = blocked || fallback.blocked;
5125
+ for (const part of fallback.parts) {
5126
+ if (part.type === "text") {
5127
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
5128
+ } else {
5129
+ pushInline(part.data, part.mimeType);
5130
+ }
5131
+ }
4596
5132
  }
4597
- } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
4598
- const response = await client.chat.completions.create(
4599
- {
4600
- model: modelForProvider,
4601
- messages: fireworksMessages,
4602
- ...responseFormat ? { response_format: responseFormat } : {}
5133
+ }, modelForProvider);
5134
+ } else if (provider === "chatgpt") {
5135
+ const chatGptInput = toChatGptInput(contents);
5136
+ const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
5137
+ const openAiTools = toOpenAiTools(request.tools);
5138
+ const requestPayload = {
5139
+ model: modelForProvider,
5140
+ store: false,
5141
+ stream: true,
5142
+ ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
5143
+ instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
5144
+ input: chatGptInput.input,
5145
+ include: ["reasoning.encrypted_content"],
5146
+ reasoning: {
5147
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5148
+ summary: "detailed"
4603
5149
  },
4604
- { signal }
4605
- );
4606
- modelVersion = typeof response.model === "string" ? response.model : request.model;
4607
- queue.push({ type: "model", modelVersion });
4608
- const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
4609
- if (choice?.finish_reason === "content_filter") {
4610
- blocked = true;
5150
+ text: {
5151
+ format: request.openAiTextFormat ?? { type: "text" },
5152
+ verbosity: resolveOpenAiVerbosity(request.model)
5153
+ },
5154
+ ...openAiTools ? { tools: openAiTools } : {}
5155
+ };
5156
+ let sawResponseDelta = false;
5157
+ let sawThoughtDelta = false;
5158
+ const result = await collectChatGptCodexResponseWithRetry({
5159
+ request: requestPayload,
5160
+ signal,
5161
+ onDelta: (delta) => {
5162
+ if (delta.thoughtDelta) {
5163
+ sawThoughtDelta = true;
5164
+ pushDelta("thought", delta.thoughtDelta);
5165
+ }
5166
+ if (delta.textDelta) {
5167
+ sawResponseDelta = true;
5168
+ pushDelta("response", delta.textDelta);
5169
+ }
5170
+ }
5171
+ });
5172
+ blocked = blocked || result.blocked;
5173
+ if (blocked) {
4611
5174
  queue.push({ type: "blocked" });
4612
5175
  }
4613
- const textOutput = extractFireworksMessageText(
4614
- choice?.message
4615
- );
4616
- if (textOutput.length > 0) {
4617
- pushDelta("response", textOutput);
5176
+ if (result.model) {
5177
+ modelVersion = providerInfo.serviceTier ? request.model : `chatgpt-${result.model}`;
5178
+ queue.push({ type: "model", modelVersion });
4618
5179
  }
4619
- latestUsage = extractFireworksUsageTokens(response.usage);
4620
- }, modelForProvider);
4621
- } else {
4622
- const geminiContents = contents.map(convertLlmContentToGeminiContent);
4623
- const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
4624
- const config = {
4625
- maxOutputTokens: 32e3,
4626
- ...thinkingConfig ? { thinkingConfig } : {},
4627
- ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
4628
- ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
4629
- ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
4630
- ...request.imageAspectRatio || request.imageSize ? {
4631
- imageConfig: {
4632
- ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
4633
- ...request.imageSize ? { imageSize: request.imageSize } : {}
4634
- }
4635
- } : {}
4636
- };
4637
- const geminiTools = toGeminiTools(request.tools);
4638
- if (geminiTools) {
4639
- config.tools = geminiTools;
4640
- }
4641
- await runGeminiCall(async (client) => {
4642
- const stream = await client.models.generateContentStream({
4643
- model: modelForProvider,
4644
- contents: geminiContents,
4645
- config
5180
+ latestUsage = extractChatGptUsageTokens(result.usage);
5181
+ const fallbackText = typeof result.text === "string" ? result.text : "";
5182
+ const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
5183
+ if (!sawThoughtDelta && fallbackThoughts.length > 0) {
5184
+ pushDelta("thought", fallbackThoughts);
5185
+ }
5186
+ if (!sawResponseDelta && fallbackText.length > 0) {
5187
+ pushDelta("response", fallbackText);
5188
+ }
5189
+ } else if (provider === "fireworks") {
5190
+ if (request.tools && request.tools.length > 0) {
5191
+ throw new Error(
5192
+ "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
5193
+ );
5194
+ }
5195
+ const fireworksMessages = toFireworksMessages(contents, {
5196
+ responseMimeType: request.responseMimeType,
5197
+ responseJsonSchema: request.responseJsonSchema
4646
5198
  });
4647
- let latestGrounding;
4648
- for await (const chunk of stream) {
4649
- if (chunk.modelVersion) {
4650
- modelVersion = chunk.modelVersion;
4651
- queue.push({ type: "model", modelVersion });
4652
- }
4653
- if (chunk.promptFeedback?.blockReason) {
5199
+ await runFireworksCall(async (client) => {
5200
+ const responseFormat = request.responseJsonSchema ? {
5201
+ type: "json_schema",
5202
+ json_schema: {
5203
+ name: "llm-response",
5204
+ schema: request.responseJsonSchema
5205
+ }
5206
+ } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
5207
+ const response = await client.chat.completions.create(
5208
+ {
5209
+ model: modelForProvider,
5210
+ messages: fireworksMessages,
5211
+ ...responseFormat ? { response_format: responseFormat } : {}
5212
+ },
5213
+ { signal }
5214
+ );
5215
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
5216
+ queue.push({ type: "model", modelVersion });
5217
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5218
+ if (choice?.finish_reason === "content_filter") {
4654
5219
  blocked = true;
4655
5220
  queue.push({ type: "blocked" });
4656
5221
  }
4657
- latestUsage = mergeTokenUpdates(latestUsage, extractGeminiUsageTokens(chunk.usageMetadata));
4658
- const candidates = chunk.candidates;
4659
- if (!candidates || candidates.length === 0) {
4660
- continue;
4661
- }
4662
- const primary = candidates[0];
4663
- if (primary && isModerationFinish(primary.finishReason)) {
4664
- blocked = true;
4665
- queue.push({ type: "blocked" });
5222
+ const textOutput = extractFireworksMessageText(
5223
+ choice?.message
5224
+ );
5225
+ if (textOutput.length > 0) {
5226
+ pushDelta("response", textOutput);
4666
5227
  }
4667
- for (const candidate of candidates) {
4668
- const candidateContent = candidate.content;
4669
- if (!candidateContent) {
4670
- continue;
5228
+ latestUsage = extractFireworksUsageTokens(response.usage);
5229
+ }, modelForProvider);
5230
+ } else {
5231
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
5232
+ const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
5233
+ const config = {
5234
+ maxOutputTokens: 32e3,
5235
+ ...thinkingConfig ? { thinkingConfig } : {},
5236
+ ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
5237
+ ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
5238
+ ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
5239
+ ...request.imageAspectRatio || request.imageSize ? {
5240
+ imageConfig: {
5241
+ ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
5242
+ ...request.imageSize ? { imageSize: request.imageSize } : {}
4671
5243
  }
4672
- if (candidate.groundingMetadata) {
4673
- latestGrounding = candidate.groundingMetadata;
5244
+ } : {}
5245
+ };
5246
+ const geminiTools = toGeminiTools(request.tools);
5247
+ if (geminiTools) {
5248
+ config.tools = geminiTools;
5249
+ }
5250
+ await runGeminiCall(async (client) => {
5251
+ const stream = await client.models.generateContentStream({
5252
+ model: modelForProvider,
5253
+ contents: geminiContents,
5254
+ config
5255
+ });
5256
+ let latestGrounding;
5257
+ for await (const chunk of stream) {
5258
+ if (chunk.modelVersion) {
5259
+ modelVersion = chunk.modelVersion;
5260
+ queue.push({ type: "model", modelVersion });
4674
5261
  }
4675
- const content2 = convertGeminiContentToLlmContent(candidateContent);
4676
- if (!responseRole) {
4677
- responseRole = content2.role;
5262
+ if (chunk.promptFeedback?.blockReason) {
5263
+ blocked = true;
5264
+ queue.push({ type: "blocked" });
4678
5265
  }
4679
- for (const part of content2.parts) {
4680
- if (part.type === "text") {
4681
- pushDelta(part.thought === true ? "thought" : "response", part.text);
4682
- } else {
4683
- pushInline(part.data, part.mimeType);
5266
+ latestUsage = mergeTokenUpdates(
5267
+ latestUsage,
5268
+ extractGeminiUsageTokens(chunk.usageMetadata)
5269
+ );
5270
+ const candidates = chunk.candidates;
5271
+ if (!candidates || candidates.length === 0) {
5272
+ continue;
5273
+ }
5274
+ const primary = candidates[0];
5275
+ if (primary && isModerationFinish(primary.finishReason)) {
5276
+ blocked = true;
5277
+ queue.push({ type: "blocked" });
5278
+ }
5279
+ for (const candidate of candidates) {
5280
+ const candidateContent = candidate.content;
5281
+ if (!candidateContent) {
5282
+ continue;
5283
+ }
5284
+ if (candidate.groundingMetadata) {
5285
+ latestGrounding = candidate.groundingMetadata;
5286
+ }
5287
+ const content2 = convertGeminiContentToLlmContent(candidateContent);
5288
+ if (!responseRole) {
5289
+ responseRole = content2.role;
5290
+ }
5291
+ for (const part of content2.parts) {
5292
+ if (part.type === "text") {
5293
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
5294
+ } else {
5295
+ pushInline(part.data, part.mimeType);
5296
+ }
4684
5297
  }
4685
5298
  }
4686
5299
  }
4687
- }
4688
- grounding = latestGrounding;
4689
- }, modelForProvider);
4690
- }
4691
- const mergedParts = mergeConsecutiveTextParts(responseParts);
4692
- const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
4693
- const { text, thoughts } = extractTextByChannel(content);
4694
- const costUsd = estimateCallCostUsd({
4695
- modelId: modelVersion,
4696
- tokens: latestUsage,
4697
- responseImages,
4698
- imageSize: request.imageSize
4699
- });
4700
- if (latestUsage) {
4701
- queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5300
+ grounding = latestGrounding;
5301
+ }, modelForProvider);
5302
+ }
5303
+ const mergedParts = mergeConsecutiveTextParts(responseParts);
5304
+ const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
5305
+ const { text, thoughts } = extractTextByChannel(content);
5306
+ const costUsd = estimateCallCostUsd({
5307
+ modelId: modelVersion,
5308
+ tokens: latestUsage,
5309
+ responseImages,
5310
+ imageSize: request.imageSize
5311
+ });
5312
+ if (latestUsage) {
5313
+ queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5314
+ }
5315
+ callLogger?.complete({
5316
+ provider,
5317
+ model: request.model,
5318
+ modelVersion,
5319
+ blocked,
5320
+ costUsd,
5321
+ usage: latestUsage,
5322
+ grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5323
+ responseChars: text.length,
5324
+ thoughtChars: thoughts.length,
5325
+ responseImages
5326
+ });
5327
+ return {
5328
+ provider,
5329
+ model: request.model,
5330
+ modelVersion,
5331
+ content,
5332
+ text,
5333
+ thoughts,
5334
+ blocked,
5335
+ usage: latestUsage,
5336
+ costUsd,
5337
+ grounding
5338
+ };
5339
+ } catch (error) {
5340
+ callLogger?.fail(error, {
5341
+ provider,
5342
+ model: request.model,
5343
+ modelVersion,
5344
+ blocked,
5345
+ usage: latestUsage,
5346
+ partialResponseParts: responseParts.length,
5347
+ responseImages
5348
+ });
5349
+ throw error;
4702
5350
  }
4703
- return {
4704
- provider,
4705
- model: request.model,
4706
- modelVersion,
4707
- content,
4708
- text,
4709
- thoughts,
4710
- blocked,
4711
- usage: latestUsage,
4712
- costUsd,
4713
- grounding
4714
- };
4715
5351
  }
4716
5352
  function streamText(request) {
4717
5353
  const queue = createAsyncQueue();
@@ -5174,6 +5810,23 @@ async function runToolLoop(request) {
5174
5810
  let modelVersion = request.model;
5175
5811
  let usageTokens;
5176
5812
  let thoughtDeltaEmitted = false;
5813
+ let blocked = false;
5814
+ const stepRequestPayload = {
5815
+ model: providerInfo.model,
5816
+ input,
5817
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5818
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5819
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5820
+ reasoning,
5821
+ text: textConfig,
5822
+ include: ["reasoning.encrypted_content"]
5823
+ };
5824
+ const stepCallLogger = startLlmCallLoggerFromPayload({
5825
+ provider: "openai",
5826
+ modelId: request.model,
5827
+ requestPayload: stepRequestPayload,
5828
+ step: turn
5829
+ });
5177
5830
  const emitEvent = (ev) => {
5178
5831
  onEvent?.(ev);
5179
5832
  };
@@ -5182,226 +5835,276 @@ async function runToolLoop(request) {
5182
5835
  firstModelEventAtMs = Date.now();
5183
5836
  }
5184
5837
  };
5185
- const finalResponse = await runOpenAiCall(
5186
- async (client) => {
5187
- const stream = client.responses.stream(
5188
- {
5189
- model: providerInfo.model,
5190
- input,
5191
- ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5192
- ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5193
- ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5194
- reasoning,
5195
- text: textConfig,
5196
- include: ["reasoning.encrypted_content"]
5197
- },
5198
- { signal: abortController.signal }
5199
- );
5200
- for await (const event of stream) {
5201
- markFirstModelEvent();
5202
- switch (event.type) {
5203
- case "response.output_text.delta":
5204
- emitEvent({
5205
- type: "delta",
5206
- channel: "response",
5207
- text: typeof event.delta === "string" ? event.delta : ""
5208
- });
5209
- break;
5210
- case "response.reasoning_summary_text.delta":
5211
- thoughtDeltaEmitted = true;
5212
- emitEvent({
5213
- type: "delta",
5214
- channel: "thought",
5215
- text: typeof event.delta === "string" ? event.delta : ""
5216
- });
5217
- break;
5218
- case "response.refusal.delta":
5219
- emitEvent({ type: "blocked" });
5220
- break;
5221
- default:
5222
- break;
5838
+ try {
5839
+ const finalResponse = await runOpenAiCall(
5840
+ async (client) => {
5841
+ const stream = client.responses.stream(
5842
+ {
5843
+ model: providerInfo.model,
5844
+ input,
5845
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5846
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5847
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5848
+ reasoning,
5849
+ text: textConfig,
5850
+ include: ["reasoning.encrypted_content"]
5851
+ },
5852
+ { signal: abortController.signal }
5853
+ );
5854
+ for await (const event of stream) {
5855
+ markFirstModelEvent();
5856
+ switch (event.type) {
5857
+ case "response.output_text.delta": {
5858
+ const text = typeof event.delta === "string" ? event.delta : "";
5859
+ if (text.length > 0) {
5860
+ stepCallLogger?.appendResponseDelta(text);
5861
+ }
5862
+ emitEvent({
5863
+ type: "delta",
5864
+ channel: "response",
5865
+ text
5866
+ });
5867
+ break;
5868
+ }
5869
+ case "response.reasoning_summary_text.delta": {
5870
+ thoughtDeltaEmitted = true;
5871
+ const text = typeof event.delta === "string" ? event.delta : "";
5872
+ if (text.length > 0) {
5873
+ stepCallLogger?.appendThoughtDelta(text);
5874
+ }
5875
+ emitEvent({
5876
+ type: "delta",
5877
+ channel: "thought",
5878
+ text
5879
+ });
5880
+ break;
5881
+ }
5882
+ case "response.refusal.delta":
5883
+ blocked = true;
5884
+ emitEvent({ type: "blocked" });
5885
+ break;
5886
+ default:
5887
+ break;
5888
+ }
5889
+ }
5890
+ return await stream.finalResponse();
5891
+ },
5892
+ providerInfo.model,
5893
+ {
5894
+ onSettled: (metrics) => {
5895
+ schedulerMetrics = metrics;
5223
5896
  }
5224
5897
  }
5225
- return await stream.finalResponse();
5226
- },
5227
- providerInfo.model,
5228
- {
5229
- onSettled: (metrics) => {
5230
- schedulerMetrics = metrics;
5898
+ );
5899
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5900
+ emitEvent({ type: "model", modelVersion });
5901
+ if (finalResponse.error) {
5902
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5903
+ throw new Error(message);
5904
+ }
5905
+ usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
5906
+ const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
5907
+ const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
5908
+ if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
5909
+ stepCallLogger?.appendThoughtDelta(reasoningSummary);
5910
+ emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
5911
+ }
5912
+ const modelCompletedAtMs = Date.now();
5913
+ const stepCostUsd = estimateCallCostUsd({
5914
+ modelId: modelVersion,
5915
+ tokens: usageTokens,
5916
+ responseImages: 0
5917
+ });
5918
+ totalCostUsd += stepCostUsd;
5919
+ if (usageTokens) {
5920
+ emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
5921
+ }
5922
+ const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
5923
+ const stepToolCalls = [];
5924
+ if (responseToolCalls.length === 0) {
5925
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5926
+ const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2) : [];
5927
+ finalText = responseText;
5928
+ finalThoughts = reasoningSummary;
5929
+ const stepCompletedAtMs2 = Date.now();
5930
+ const timing2 = buildStepTiming({
5931
+ stepStartedAtMs,
5932
+ stepCompletedAtMs: stepCompletedAtMs2,
5933
+ modelCompletedAtMs,
5934
+ firstModelEventAtMs,
5935
+ schedulerMetrics,
5936
+ toolExecutionMs: 0,
5937
+ waitToolMs: 0
5938
+ });
5939
+ steps.push({
5940
+ step: steps.length + 1,
5941
+ modelVersion,
5942
+ text: responseText || void 0,
5943
+ thoughts: reasoningSummary || void 0,
5944
+ toolCalls: [],
5945
+ usage: usageTokens,
5946
+ costUsd: stepCostUsd,
5947
+ timing: timing2
5948
+ });
5949
+ stepCallLogger?.complete({
5950
+ provider: "openai",
5951
+ model: request.model,
5952
+ modelVersion,
5953
+ step: turn,
5954
+ usage: usageTokens,
5955
+ costUsd: stepCostUsd,
5956
+ blocked,
5957
+ responseChars: responseText.length,
5958
+ thoughtChars: reasoningSummary.length,
5959
+ toolCalls: 0,
5960
+ finalStep: steeringItems2.length === 0
5961
+ });
5962
+ if (steeringItems2.length === 0) {
5963
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5231
5964
  }
5965
+ previousResponseId = finalResponse.id;
5966
+ input = steeringItems2;
5967
+ continue;
5232
5968
  }
5233
- );
5234
- modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5235
- emitEvent({ type: "model", modelVersion });
5236
- if (finalResponse.error) {
5237
- const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5238
- throw new Error(message);
5239
- }
5240
- usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
5241
- const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
5242
- const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
5243
- if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
5244
- emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
5245
- }
5246
- const modelCompletedAtMs = Date.now();
5247
- const stepCostUsd = estimateCallCostUsd({
5248
- modelId: modelVersion,
5249
- tokens: usageTokens,
5250
- responseImages: 0
5251
- });
5252
- totalCostUsd += stepCostUsd;
5253
- if (usageTokens) {
5254
- emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
5255
- }
5256
- const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
5257
- const stepToolCalls = [];
5258
- if (responseToolCalls.length === 0) {
5259
- const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5260
- const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2) : [];
5261
- finalText = responseText;
5262
- finalThoughts = reasoningSummary;
5263
- const stepCompletedAtMs2 = Date.now();
5264
- const timing2 = buildStepTiming({
5969
+ const callInputs = responseToolCalls.map((call, index) => {
5970
+ const toolIndex = index + 1;
5971
+ const toolId = buildToolLogId(turn, toolIndex);
5972
+ const toolName = call.name;
5973
+ if (call.kind === "custom") {
5974
+ return {
5975
+ call,
5976
+ toolName,
5977
+ value: call.input,
5978
+ parseError: void 0,
5979
+ toolId,
5980
+ turn,
5981
+ toolIndex
5982
+ };
5983
+ }
5984
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5985
+ return { call, toolName, value, parseError, toolId, turn, toolIndex };
5986
+ });
5987
+ for (const entry of callInputs) {
5988
+ emitEvent({
5989
+ type: "tool_call",
5990
+ phase: "started",
5991
+ turn: entry.turn,
5992
+ toolIndex: entry.toolIndex,
5993
+ toolName: entry.toolName,
5994
+ toolId: entry.toolId,
5995
+ callKind: entry.call.kind,
5996
+ callId: entry.call.call_id,
5997
+ input: entry.value
5998
+ });
5999
+ }
6000
+ const callResults = await Promise.all(
6001
+ callInputs.map(async (entry) => {
6002
+ return await toolCallContextStorage.run(
6003
+ {
6004
+ toolName: entry.toolName,
6005
+ toolId: entry.toolId,
6006
+ turn: entry.turn,
6007
+ toolIndex: entry.toolIndex
6008
+ },
6009
+ async () => {
6010
+ const { result, outputPayload } = await executeToolCall({
6011
+ callKind: entry.call.kind,
6012
+ toolName: entry.toolName,
6013
+ tool: request.tools[entry.toolName],
6014
+ rawInput: entry.value,
6015
+ parseError: entry.parseError
6016
+ });
6017
+ return { entry, result, outputPayload };
6018
+ }
6019
+ );
6020
+ })
6021
+ );
6022
+ const toolOutputs = [];
6023
+ let toolExecutionMs = 0;
6024
+ let waitToolMs = 0;
6025
+ for (const { entry, result, outputPayload } of callResults) {
6026
+ stepToolCalls.push({ ...result, callId: entry.call.call_id });
6027
+ const callDurationMs = toToolResultDuration(result);
6028
+ toolExecutionMs += callDurationMs;
6029
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6030
+ waitToolMs += callDurationMs;
6031
+ }
6032
+ emitEvent({
6033
+ type: "tool_call",
6034
+ phase: "completed",
6035
+ turn: entry.turn,
6036
+ toolIndex: entry.toolIndex,
6037
+ toolName: entry.toolName,
6038
+ toolId: entry.toolId,
6039
+ callKind: entry.call.kind,
6040
+ callId: entry.call.call_id,
6041
+ input: entry.value,
6042
+ output: result.output,
6043
+ error: result.error,
6044
+ durationMs: result.durationMs
6045
+ });
6046
+ if (entry.call.kind === "custom") {
6047
+ toolOutputs.push({
6048
+ type: "custom_tool_call_output",
6049
+ call_id: entry.call.call_id,
6050
+ output: toOpenAiToolOutput(outputPayload)
6051
+ });
6052
+ } else {
6053
+ toolOutputs.push({
6054
+ type: "function_call_output",
6055
+ call_id: entry.call.call_id,
6056
+ output: toOpenAiToolOutput(outputPayload)
6057
+ });
6058
+ }
6059
+ }
6060
+ const stepCompletedAtMs = Date.now();
6061
+ const timing = buildStepTiming({
5265
6062
  stepStartedAtMs,
5266
- stepCompletedAtMs: stepCompletedAtMs2,
6063
+ stepCompletedAtMs,
5267
6064
  modelCompletedAtMs,
5268
6065
  firstModelEventAtMs,
5269
6066
  schedulerMetrics,
5270
- toolExecutionMs: 0,
5271
- waitToolMs: 0
6067
+ toolExecutionMs,
6068
+ waitToolMs
5272
6069
  });
5273
6070
  steps.push({
5274
6071
  step: steps.length + 1,
5275
6072
  modelVersion,
5276
6073
  text: responseText || void 0,
5277
6074
  thoughts: reasoningSummary || void 0,
5278
- toolCalls: [],
6075
+ toolCalls: stepToolCalls,
5279
6076
  usage: usageTokens,
5280
6077
  costUsd: stepCostUsd,
5281
- timing: timing2
6078
+ timing
5282
6079
  });
5283
- if (steeringItems2.length === 0) {
5284
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5285
- }
5286
- previousResponseId = finalResponse.id;
5287
- input = steeringItems2;
5288
- continue;
5289
- }
5290
- const callInputs = responseToolCalls.map((call, index) => {
5291
- const toolIndex = index + 1;
5292
- const toolId = buildToolLogId(turn, toolIndex);
5293
- const toolName = call.name;
5294
- if (call.kind === "custom") {
5295
- return {
5296
- call,
5297
- toolName,
5298
- value: call.input,
5299
- parseError: void 0,
5300
- toolId,
5301
- turn,
5302
- toolIndex
5303
- };
5304
- }
5305
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5306
- return { call, toolName, value, parseError, toolId, turn, toolIndex };
5307
- });
5308
- for (const entry of callInputs) {
5309
- emitEvent({
5310
- type: "tool_call",
5311
- phase: "started",
5312
- turn: entry.turn,
5313
- toolIndex: entry.toolIndex,
5314
- toolName: entry.toolName,
5315
- toolId: entry.toolId,
5316
- callKind: entry.call.kind,
5317
- callId: entry.call.call_id,
5318
- input: entry.value
6080
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6081
+ const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
6082
+ stepCallLogger?.complete({
6083
+ provider: "openai",
6084
+ model: request.model,
6085
+ modelVersion,
6086
+ step: turn,
6087
+ usage: usageTokens,
6088
+ costUsd: stepCostUsd,
6089
+ blocked,
6090
+ responseChars: responseText.length,
6091
+ thoughtChars: reasoningSummary.length,
6092
+ toolCalls: stepToolCalls.length,
6093
+ finalStep: false
5319
6094
  });
5320
- }
5321
- const callResults = await Promise.all(
5322
- callInputs.map(async (entry) => {
5323
- return await toolCallContextStorage.run(
5324
- {
5325
- toolName: entry.toolName,
5326
- toolId: entry.toolId,
5327
- turn: entry.turn,
5328
- toolIndex: entry.toolIndex
5329
- },
5330
- async () => {
5331
- const { result, outputPayload } = await executeToolCall({
5332
- callKind: entry.call.kind,
5333
- toolName: entry.toolName,
5334
- tool: request.tools[entry.toolName],
5335
- rawInput: entry.value,
5336
- parseError: entry.parseError
5337
- });
5338
- return { entry, result, outputPayload };
5339
- }
5340
- );
5341
- })
5342
- );
5343
- const toolOutputs = [];
5344
- let toolExecutionMs = 0;
5345
- let waitToolMs = 0;
5346
- for (const { entry, result, outputPayload } of callResults) {
5347
- stepToolCalls.push({ ...result, callId: entry.call.call_id });
5348
- const callDurationMs = toToolResultDuration(result);
5349
- toolExecutionMs += callDurationMs;
5350
- if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5351
- waitToolMs += callDurationMs;
5352
- }
5353
- emitEvent({
5354
- type: "tool_call",
5355
- phase: "completed",
5356
- turn: entry.turn,
5357
- toolIndex: entry.toolIndex,
5358
- toolName: entry.toolName,
5359
- toolId: entry.toolId,
5360
- callKind: entry.call.kind,
5361
- callId: entry.call.call_id,
5362
- input: entry.value,
5363
- output: result.output,
5364
- error: result.error,
5365
- durationMs: result.durationMs
6095
+ previousResponseId = finalResponse.id;
6096
+ input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
6097
+ } catch (error) {
6098
+ stepCallLogger?.fail(error, {
6099
+ provider: "openai",
6100
+ model: request.model,
6101
+ modelVersion,
6102
+ step: turn,
6103
+ usage: usageTokens,
6104
+ blocked
5366
6105
  });
5367
- if (entry.call.kind === "custom") {
5368
- toolOutputs.push({
5369
- type: "custom_tool_call_output",
5370
- call_id: entry.call.call_id,
5371
- output: toOpenAiToolOutput(outputPayload)
5372
- });
5373
- } else {
5374
- toolOutputs.push({
5375
- type: "function_call_output",
5376
- call_id: entry.call.call_id,
5377
- output: toOpenAiToolOutput(outputPayload)
5378
- });
5379
- }
6106
+ throw error;
5380
6107
  }
5381
- const stepCompletedAtMs = Date.now();
5382
- const timing = buildStepTiming({
5383
- stepStartedAtMs,
5384
- stepCompletedAtMs,
5385
- modelCompletedAtMs,
5386
- firstModelEventAtMs,
5387
- schedulerMetrics,
5388
- toolExecutionMs,
5389
- waitToolMs
5390
- });
5391
- steps.push({
5392
- step: steps.length + 1,
5393
- modelVersion,
5394
- text: responseText || void 0,
5395
- thoughts: reasoningSummary || void 0,
5396
- toolCalls: stepToolCalls,
5397
- usage: usageTokens,
5398
- costUsd: stepCostUsd,
5399
- timing
5400
- });
5401
- const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5402
- const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
5403
- previousResponseId = finalResponse.id;
5404
- input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
5405
6108
  }
5406
6109
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5407
6110
  }
@@ -5419,242 +6122,656 @@ async function runToolLoop(request) {
5419
6122
  const stepStartedAtMs = Date.now();
5420
6123
  let firstModelEventAtMs;
5421
6124
  let thoughtDeltaEmitted = false;
6125
+ let sawResponseDelta = false;
6126
+ let modelVersion = request.model;
6127
+ let usageTokens;
6128
+ let responseText = "";
6129
+ let reasoningSummaryText = "";
5422
6130
  const markFirstModelEvent = () => {
5423
6131
  if (firstModelEventAtMs === void 0) {
5424
6132
  firstModelEventAtMs = Date.now();
5425
6133
  }
5426
6134
  };
5427
- const response = await collectChatGptCodexResponseWithRetry({
5428
- sessionId: conversationId,
5429
- request: {
5430
- model: providerInfo.model,
5431
- store: false,
5432
- stream: true,
5433
- instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
5434
- input,
5435
- prompt_cache_key: promptCacheKey,
5436
- include: ["reasoning.encrypted_content"],
5437
- tools: openAiTools,
5438
- tool_choice: "auto",
5439
- parallel_tool_calls: true,
5440
- reasoning: {
5441
- effort: toOpenAiReasoningEffort(reasoningEffort),
5442
- summary: "detailed"
5443
- },
5444
- text: { verbosity: resolveOpenAiVerbosity(request.model) }
6135
+ const stepRequestPayload = {
6136
+ model: providerInfo.model,
6137
+ store: false,
6138
+ stream: true,
6139
+ ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
6140
+ instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
6141
+ input,
6142
+ prompt_cache_key: promptCacheKey,
6143
+ include: ["reasoning.encrypted_content"],
6144
+ tools: openAiTools,
6145
+ tool_choice: "auto",
6146
+ parallel_tool_calls: true,
6147
+ reasoning: {
6148
+ effort: toOpenAiReasoningEffort(reasoningEffort),
6149
+ summary: "detailed"
5445
6150
  },
5446
- signal: request.signal,
5447
- onDelta: (delta) => {
5448
- if (delta.thoughtDelta) {
5449
- markFirstModelEvent();
5450
- thoughtDeltaEmitted = true;
5451
- request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
6151
+ text: { verbosity: resolveOpenAiVerbosity(request.model) }
6152
+ };
6153
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6154
+ provider: "chatgpt",
6155
+ modelId: request.model,
6156
+ requestPayload: stepRequestPayload,
6157
+ step: turn
6158
+ });
6159
+ try {
6160
+ const response = await collectChatGptCodexResponseWithRetry({
6161
+ sessionId: conversationId,
6162
+ request: stepRequestPayload,
6163
+ signal: request.signal,
6164
+ onDelta: (delta) => {
6165
+ if (delta.thoughtDelta) {
6166
+ markFirstModelEvent();
6167
+ thoughtDeltaEmitted = true;
6168
+ stepCallLogger?.appendThoughtDelta(delta.thoughtDelta);
6169
+ request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
6170
+ }
6171
+ if (delta.textDelta) {
6172
+ markFirstModelEvent();
6173
+ sawResponseDelta = true;
6174
+ stepCallLogger?.appendResponseDelta(delta.textDelta);
6175
+ request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
6176
+ }
5452
6177
  }
5453
- if (delta.textDelta) {
5454
- markFirstModelEvent();
5455
- request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
6178
+ });
6179
+ const modelCompletedAtMs = Date.now();
6180
+ modelVersion = response.model && !providerInfo.serviceTier ? `chatgpt-${response.model}` : request.model;
6181
+ usageTokens = extractChatGptUsageTokens(response.usage);
6182
+ const stepCostUsd = estimateCallCostUsd({
6183
+ modelId: modelVersion,
6184
+ tokens: usageTokens,
6185
+ responseImages: 0
6186
+ });
6187
+ totalCostUsd += stepCostUsd;
6188
+ responseText = (response.text ?? "").trim();
6189
+ reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
6190
+ if (!thoughtDeltaEmitted && reasoningSummaryText.length > 0) {
6191
+ stepCallLogger?.appendThoughtDelta(reasoningSummaryText);
6192
+ request.onEvent?.({ type: "delta", channel: "thought", text: reasoningSummaryText });
6193
+ }
6194
+ if (!sawResponseDelta && responseText.length > 0) {
6195
+ stepCallLogger?.appendResponseDelta(responseText);
6196
+ }
6197
+ const responseToolCalls = response.toolCalls ?? [];
6198
+ if (responseToolCalls.length === 0) {
6199
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6200
+ const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
6201
+ finalText = responseText;
6202
+ finalThoughts = reasoningSummaryText;
6203
+ const stepCompletedAtMs2 = Date.now();
6204
+ const timing2 = buildStepTiming({
6205
+ stepStartedAtMs,
6206
+ stepCompletedAtMs: stepCompletedAtMs2,
6207
+ modelCompletedAtMs,
6208
+ firstModelEventAtMs,
6209
+ toolExecutionMs: 0,
6210
+ waitToolMs: 0
6211
+ });
6212
+ steps.push({
6213
+ step: steps.length + 1,
6214
+ modelVersion,
6215
+ text: responseText || void 0,
6216
+ thoughts: reasoningSummaryText || void 0,
6217
+ toolCalls: [],
6218
+ usage: usageTokens,
6219
+ costUsd: stepCostUsd,
6220
+ timing: timing2
6221
+ });
6222
+ stepCallLogger?.complete({
6223
+ provider: "chatgpt",
6224
+ model: request.model,
6225
+ modelVersion,
6226
+ step: turn,
6227
+ usage: usageTokens,
6228
+ costUsd: stepCostUsd,
6229
+ responseChars: responseText.length,
6230
+ thoughtChars: reasoningSummaryText.length,
6231
+ toolCalls: 0,
6232
+ finalStep: steeringItems2.length === 0
6233
+ });
6234
+ if (steeringItems2.length === 0) {
6235
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5456
6236
  }
6237
+ const assistantItem = toChatGptAssistantMessage(responseText);
6238
+ input = assistantItem ? input.concat(assistantItem, steeringItems2) : input.concat(steeringItems2);
6239
+ continue;
5457
6240
  }
5458
- });
5459
- const modelCompletedAtMs = Date.now();
5460
- const modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
5461
- const usageTokens = extractChatGptUsageTokens(response.usage);
5462
- const stepCostUsd = estimateCallCostUsd({
5463
- modelId: modelVersion,
5464
- tokens: usageTokens,
5465
- responseImages: 0
5466
- });
5467
- totalCostUsd += stepCostUsd;
5468
- const responseText = (response.text ?? "").trim();
5469
- const reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
5470
- if (!thoughtDeltaEmitted && reasoningSummaryText.length > 0) {
5471
- request.onEvent?.({ type: "delta", channel: "thought", text: reasoningSummaryText });
5472
- }
5473
- const responseToolCalls = response.toolCalls ?? [];
5474
- if (responseToolCalls.length === 0) {
5475
- const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5476
- const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
5477
- finalText = responseText;
5478
- finalThoughts = reasoningSummaryText;
5479
- const stepCompletedAtMs2 = Date.now();
5480
- const timing2 = buildStepTiming({
6241
+ const toolCalls = [];
6242
+ const toolOutputs = [];
6243
+ const callInputs = responseToolCalls.map((call, index) => {
6244
+ const toolIndex = index + 1;
6245
+ const toolId = buildToolLogId(turn, toolIndex);
6246
+ const toolName = call.name;
6247
+ const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
6248
+ const ids = normalizeChatGptToolIds({
6249
+ callKind: call.kind,
6250
+ callId: call.callId,
6251
+ itemId: call.id
6252
+ });
6253
+ return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
6254
+ });
6255
+ for (const entry of callInputs) {
6256
+ request.onEvent?.({
6257
+ type: "tool_call",
6258
+ phase: "started",
6259
+ turn: entry.turn,
6260
+ toolIndex: entry.toolIndex,
6261
+ toolName: entry.toolName,
6262
+ toolId: entry.toolId,
6263
+ callKind: entry.call.kind,
6264
+ callId: entry.ids.callId,
6265
+ input: entry.value
6266
+ });
6267
+ }
6268
+ const callResults = await Promise.all(
6269
+ callInputs.map(async (entry) => {
6270
+ return await toolCallContextStorage.run(
6271
+ {
6272
+ toolName: entry.toolName,
6273
+ toolId: entry.toolId,
6274
+ turn: entry.turn,
6275
+ toolIndex: entry.toolIndex
6276
+ },
6277
+ async () => {
6278
+ const { result, outputPayload } = await executeToolCall({
6279
+ callKind: entry.call.kind,
6280
+ toolName: entry.toolName,
6281
+ tool: request.tools[entry.toolName],
6282
+ rawInput: entry.value,
6283
+ parseError: entry.parseError
6284
+ });
6285
+ return { entry, result, outputPayload };
6286
+ }
6287
+ );
6288
+ })
6289
+ );
6290
+ let toolExecutionMs = 0;
6291
+ let waitToolMs = 0;
6292
+ for (const { entry, result, outputPayload } of callResults) {
6293
+ toolCalls.push({ ...result, callId: entry.ids.callId });
6294
+ const callDurationMs = toToolResultDuration(result);
6295
+ toolExecutionMs += callDurationMs;
6296
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6297
+ waitToolMs += callDurationMs;
6298
+ }
6299
+ request.onEvent?.({
6300
+ type: "tool_call",
6301
+ phase: "completed",
6302
+ turn: entry.turn,
6303
+ toolIndex: entry.toolIndex,
6304
+ toolName: entry.toolName,
6305
+ toolId: entry.toolId,
6306
+ callKind: entry.call.kind,
6307
+ callId: entry.ids.callId,
6308
+ input: entry.value,
6309
+ output: result.output,
6310
+ error: result.error,
6311
+ durationMs: result.durationMs
6312
+ });
6313
+ if (entry.call.kind === "custom") {
6314
+ toolOutputs.push({
6315
+ type: "custom_tool_call",
6316
+ id: entry.ids.itemId,
6317
+ call_id: entry.ids.callId,
6318
+ name: entry.toolName,
6319
+ input: entry.call.input,
6320
+ status: "completed"
6321
+ });
6322
+ toolOutputs.push({
6323
+ type: "custom_tool_call_output",
6324
+ call_id: entry.ids.callId,
6325
+ output: toOpenAiToolOutput(outputPayload)
6326
+ });
6327
+ } else {
6328
+ toolOutputs.push({
6329
+ type: "function_call",
6330
+ id: entry.ids.itemId,
6331
+ call_id: entry.ids.callId,
6332
+ name: entry.toolName,
6333
+ arguments: entry.call.arguments,
6334
+ status: "completed"
6335
+ });
6336
+ toolOutputs.push({
6337
+ type: "function_call_output",
6338
+ call_id: entry.ids.callId,
6339
+ output: toOpenAiToolOutput(outputPayload)
6340
+ });
6341
+ }
6342
+ }
6343
+ const stepCompletedAtMs = Date.now();
6344
+ const timing = buildStepTiming({
5481
6345
  stepStartedAtMs,
5482
- stepCompletedAtMs: stepCompletedAtMs2,
6346
+ stepCompletedAtMs,
5483
6347
  modelCompletedAtMs,
5484
6348
  firstModelEventAtMs,
5485
- toolExecutionMs: 0,
5486
- waitToolMs: 0
6349
+ toolExecutionMs,
6350
+ waitToolMs
5487
6351
  });
5488
6352
  steps.push({
5489
6353
  step: steps.length + 1,
5490
6354
  modelVersion,
5491
6355
  text: responseText || void 0,
5492
6356
  thoughts: reasoningSummaryText || void 0,
5493
- toolCalls: [],
6357
+ toolCalls,
5494
6358
  usage: usageTokens,
5495
6359
  costUsd: stepCostUsd,
5496
- timing: timing2
6360
+ timing
5497
6361
  });
5498
- if (steeringItems2.length === 0) {
5499
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5500
- }
5501
- const assistantItem = toChatGptAssistantMessage(responseText);
5502
- input = assistantItem ? input.concat(assistantItem, steeringItems2) : input.concat(steeringItems2);
5503
- continue;
5504
- }
5505
- const toolCalls = [];
5506
- const toolOutputs = [];
5507
- const callInputs = responseToolCalls.map((call, index) => {
5508
- const toolIndex = index + 1;
5509
- const toolId = buildToolLogId(turn, toolIndex);
5510
- const toolName = call.name;
5511
- const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
5512
- const ids = normalizeChatGptToolIds({
5513
- callKind: call.kind,
5514
- callId: call.callId,
5515
- itemId: call.id
6362
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6363
+ const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
6364
+ stepCallLogger?.complete({
6365
+ provider: "chatgpt",
6366
+ model: request.model,
6367
+ modelVersion,
6368
+ step: turn,
6369
+ usage: usageTokens,
6370
+ costUsd: stepCostUsd,
6371
+ responseChars: responseText.length,
6372
+ thoughtChars: reasoningSummaryText.length,
6373
+ toolCalls: toolCalls.length,
6374
+ finalStep: false
5516
6375
  });
5517
- return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
5518
- });
5519
- for (const entry of callInputs) {
5520
- request.onEvent?.({
5521
- type: "tool_call",
5522
- phase: "started",
5523
- turn: entry.turn,
5524
- toolIndex: entry.toolIndex,
5525
- toolName: entry.toolName,
5526
- toolId: entry.toolId,
5527
- callKind: entry.call.kind,
5528
- callId: entry.ids.callId,
5529
- input: entry.value
6376
+ input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
6377
+ } catch (error) {
6378
+ stepCallLogger?.fail(error, {
6379
+ provider: "chatgpt",
6380
+ model: request.model,
6381
+ modelVersion,
6382
+ step: turn,
6383
+ usage: usageTokens
5530
6384
  });
6385
+ throw error;
5531
6386
  }
5532
- const callResults = await Promise.all(
5533
- callInputs.map(async (entry) => {
5534
- return await toolCallContextStorage.run(
5535
- {
5536
- toolName: entry.toolName,
5537
- toolId: entry.toolId,
5538
- turn: entry.turn,
5539
- toolIndex: entry.toolIndex
5540
- },
5541
- async () => {
5542
- const { result, outputPayload } = await executeToolCall({
5543
- callKind: entry.call.kind,
5544
- toolName: entry.toolName,
5545
- tool: request.tools[entry.toolName],
5546
- rawInput: entry.value,
5547
- parseError: entry.parseError
5548
- });
5549
- return { entry, result, outputPayload };
5550
- }
5551
- );
5552
- })
6387
+ }
6388
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
6389
+ }
6390
+ if (providerInfo.provider === "fireworks") {
6391
+ if (request.modelTools && request.modelTools.length > 0) {
6392
+ throw new Error(
6393
+ "Fireworks provider does not support provider-native modelTools in runToolLoop."
5553
6394
  );
5554
- let toolExecutionMs = 0;
5555
- let waitToolMs = 0;
5556
- for (const { entry, result, outputPayload } of callResults) {
5557
- toolCalls.push({ ...result, callId: entry.ids.callId });
5558
- const callDurationMs = toToolResultDuration(result);
5559
- toolExecutionMs += callDurationMs;
5560
- if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5561
- waitToolMs += callDurationMs;
6395
+ }
6396
+ const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
6397
+ const messages = toFireworksMessages(contents);
6398
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
6399
+ const turn = stepIndex + 1;
6400
+ const stepStartedAtMs = Date.now();
6401
+ let schedulerMetrics;
6402
+ let modelVersion = request.model;
6403
+ let usageTokens;
6404
+ let responseText = "";
6405
+ let blocked = false;
6406
+ const stepRequestPayload = {
6407
+ model: providerInfo.model,
6408
+ messages,
6409
+ tools: fireworksTools,
6410
+ tool_choice: "auto",
6411
+ parallel_tool_calls: true
6412
+ };
6413
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6414
+ provider: "fireworks",
6415
+ modelId: request.model,
6416
+ requestPayload: stepRequestPayload,
6417
+ step: turn
6418
+ });
6419
+ try {
6420
+ const response = await runFireworksCall(
6421
+ async (client) => {
6422
+ return await client.chat.completions.create(
6423
+ {
6424
+ model: providerInfo.model,
6425
+ messages,
6426
+ tools: fireworksTools,
6427
+ tool_choice: "auto",
6428
+ parallel_tool_calls: true
6429
+ },
6430
+ { signal: request.signal }
6431
+ );
6432
+ },
6433
+ providerInfo.model,
6434
+ {
6435
+ onSettled: (metrics) => {
6436
+ schedulerMetrics = metrics;
6437
+ }
6438
+ }
6439
+ );
6440
+ const modelCompletedAtMs = Date.now();
6441
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
6442
+ request.onEvent?.({ type: "model", modelVersion });
6443
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
6444
+ if (choice?.finish_reason === "content_filter") {
6445
+ blocked = true;
6446
+ request.onEvent?.({ type: "blocked" });
5562
6447
  }
5563
- request.onEvent?.({
5564
- type: "tool_call",
5565
- phase: "completed",
5566
- turn: entry.turn,
5567
- toolIndex: entry.toolIndex,
5568
- toolName: entry.toolName,
5569
- toolId: entry.toolId,
5570
- callKind: entry.call.kind,
5571
- callId: entry.ids.callId,
5572
- input: entry.value,
5573
- output: result.output,
5574
- error: result.error,
5575
- durationMs: result.durationMs
6448
+ const message = choice?.message;
6449
+ responseText = extractFireworksMessageText(message).trim();
6450
+ if (responseText.length > 0) {
6451
+ stepCallLogger?.appendResponseDelta(responseText);
6452
+ request.onEvent?.({ type: "delta", channel: "response", text: responseText });
6453
+ }
6454
+ usageTokens = extractFireworksUsageTokens(response.usage);
6455
+ const stepCostUsd = estimateCallCostUsd({
6456
+ modelId: modelVersion,
6457
+ tokens: usageTokens,
6458
+ responseImages: 0
5576
6459
  });
5577
- if (entry.call.kind === "custom") {
5578
- toolOutputs.push({
5579
- type: "custom_tool_call",
5580
- id: entry.ids.itemId,
5581
- call_id: entry.ids.callId,
5582
- name: entry.toolName,
5583
- input: entry.call.input,
5584
- status: "completed"
6460
+ totalCostUsd += stepCostUsd;
6461
+ if (usageTokens) {
6462
+ request.onEvent?.({
6463
+ type: "usage",
6464
+ usage: usageTokens,
6465
+ costUsd: stepCostUsd,
6466
+ modelVersion
5585
6467
  });
5586
- toolOutputs.push({
5587
- type: "custom_tool_call_output",
5588
- call_id: entry.ids.callId,
5589
- output: toOpenAiToolOutput(outputPayload)
6468
+ }
6469
+ const responseToolCalls = extractFireworksToolCalls(message);
6470
+ if (responseToolCalls.length === 0) {
6471
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6472
+ const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
6473
+ finalText = responseText;
6474
+ finalThoughts = "";
6475
+ const stepCompletedAtMs2 = Date.now();
6476
+ const timing2 = buildStepTiming({
6477
+ stepStartedAtMs,
6478
+ stepCompletedAtMs: stepCompletedAtMs2,
6479
+ modelCompletedAtMs,
6480
+ schedulerMetrics,
6481
+ toolExecutionMs: 0,
6482
+ waitToolMs: 0
5590
6483
  });
5591
- } else {
5592
- toolOutputs.push({
5593
- type: "function_call",
5594
- id: entry.ids.itemId,
5595
- call_id: entry.ids.callId,
5596
- name: entry.toolName,
5597
- arguments: entry.call.arguments,
5598
- status: "completed"
6484
+ steps.push({
6485
+ step: steps.length + 1,
6486
+ modelVersion,
6487
+ text: responseText || void 0,
6488
+ thoughts: void 0,
6489
+ toolCalls: [],
6490
+ usage: usageTokens,
6491
+ costUsd: stepCostUsd,
6492
+ timing: timing2
5599
6493
  });
5600
- toolOutputs.push({
5601
- type: "function_call_output",
5602
- call_id: entry.ids.callId,
5603
- output: toOpenAiToolOutput(outputPayload)
6494
+ stepCallLogger?.complete({
6495
+ provider: "fireworks",
6496
+ model: request.model,
6497
+ modelVersion,
6498
+ step: turn,
6499
+ usage: usageTokens,
6500
+ costUsd: stepCostUsd,
6501
+ blocked,
6502
+ responseChars: responseText.length,
6503
+ thoughtChars: 0,
6504
+ toolCalls: 0,
6505
+ finalStep: steeringMessages.length === 0
5604
6506
  });
6507
+ if (steeringMessages.length === 0) {
6508
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
6509
+ }
6510
+ if (responseText.length > 0) {
6511
+ messages.push({ role: "assistant", content: responseText });
6512
+ }
6513
+ messages.push(...steeringMessages);
6514
+ continue;
5605
6515
  }
5606
- }
5607
- const stepCompletedAtMs = Date.now();
5608
- const timing = buildStepTiming({
5609
- stepStartedAtMs,
5610
- stepCompletedAtMs,
5611
- modelCompletedAtMs,
5612
- firstModelEventAtMs,
5613
- toolExecutionMs,
5614
- waitToolMs
5615
- });
5616
- steps.push({
5617
- step: steps.length + 1,
5618
- modelVersion,
5619
- text: responseText || void 0,
5620
- thoughts: reasoningSummaryText || void 0,
5621
- toolCalls,
5622
- usage: usageTokens,
5623
- costUsd: stepCostUsd,
5624
- timing
5625
- });
5626
- const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5627
- const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
5628
- input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
6516
+ const stepToolCalls = [];
6517
+ const callInputs = responseToolCalls.map((call, index) => {
6518
+ const toolIndex = index + 1;
6519
+ const toolId = buildToolLogId(turn, toolIndex);
6520
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
6521
+ return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
6522
+ });
6523
+ for (const entry of callInputs) {
6524
+ request.onEvent?.({
6525
+ type: "tool_call",
6526
+ phase: "started",
6527
+ turn: entry.turn,
6528
+ toolIndex: entry.toolIndex,
6529
+ toolName: entry.toolName,
6530
+ toolId: entry.toolId,
6531
+ callKind: "function",
6532
+ callId: entry.call.id,
6533
+ input: entry.value
6534
+ });
6535
+ }
6536
+ const callResults = await Promise.all(
6537
+ callInputs.map(async (entry) => {
6538
+ return await toolCallContextStorage.run(
6539
+ {
6540
+ toolName: entry.toolName,
6541
+ toolId: entry.toolId,
6542
+ turn: entry.turn,
6543
+ toolIndex: entry.toolIndex
6544
+ },
6545
+ async () => {
6546
+ const { result, outputPayload } = await executeToolCall({
6547
+ callKind: "function",
6548
+ toolName: entry.toolName,
6549
+ tool: request.tools[entry.toolName],
6550
+ rawInput: entry.value,
6551
+ parseError: entry.parseError
6552
+ });
6553
+ return { entry, result, outputPayload };
6554
+ }
6555
+ );
6556
+ })
6557
+ );
6558
+ const assistantToolCalls = [];
6559
+ const toolMessages = [];
6560
+ let toolExecutionMs = 0;
6561
+ let waitToolMs = 0;
6562
+ for (const { entry, result, outputPayload } of callResults) {
6563
+ stepToolCalls.push({ ...result, callId: entry.call.id });
6564
+ const callDurationMs = toToolResultDuration(result);
6565
+ toolExecutionMs += callDurationMs;
6566
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6567
+ waitToolMs += callDurationMs;
6568
+ }
6569
+ request.onEvent?.({
6570
+ type: "tool_call",
6571
+ phase: "completed",
6572
+ turn: entry.turn,
6573
+ toolIndex: entry.toolIndex,
6574
+ toolName: entry.toolName,
6575
+ toolId: entry.toolId,
6576
+ callKind: "function",
6577
+ callId: entry.call.id,
6578
+ input: entry.value,
6579
+ output: result.output,
6580
+ error: result.error,
6581
+ durationMs: result.durationMs
6582
+ });
6583
+ assistantToolCalls.push({
6584
+ id: entry.call.id,
6585
+ type: "function",
6586
+ function: {
6587
+ name: entry.toolName,
6588
+ arguments: entry.call.arguments
6589
+ }
6590
+ });
6591
+ toolMessages.push({
6592
+ role: "tool",
6593
+ tool_call_id: entry.call.id,
6594
+ content: mergeToolOutput(outputPayload)
6595
+ });
6596
+ }
6597
+ const stepCompletedAtMs = Date.now();
6598
+ const timing = buildStepTiming({
6599
+ stepStartedAtMs,
6600
+ stepCompletedAtMs,
6601
+ modelCompletedAtMs,
6602
+ schedulerMetrics,
6603
+ toolExecutionMs,
6604
+ waitToolMs
6605
+ });
6606
+ steps.push({
6607
+ step: steps.length + 1,
6608
+ modelVersion,
6609
+ text: responseText || void 0,
6610
+ thoughts: void 0,
6611
+ toolCalls: stepToolCalls,
6612
+ usage: usageTokens,
6613
+ costUsd: stepCostUsd,
6614
+ timing
6615
+ });
6616
+ stepCallLogger?.complete({
6617
+ provider: "fireworks",
6618
+ model: request.model,
6619
+ modelVersion,
6620
+ step: turn,
6621
+ usage: usageTokens,
6622
+ costUsd: stepCostUsd,
6623
+ blocked,
6624
+ responseChars: responseText.length,
6625
+ thoughtChars: 0,
6626
+ toolCalls: stepToolCalls.length,
6627
+ finalStep: false
6628
+ });
6629
+ messages.push({
6630
+ role: "assistant",
6631
+ ...responseText.length > 0 ? { content: responseText } : {},
6632
+ tool_calls: assistantToolCalls
6633
+ });
6634
+ messages.push(...toolMessages);
6635
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6636
+ if (steeringInput.length > 0) {
6637
+ messages.push(...toFireworksMessages(steeringInput));
6638
+ }
6639
+ } catch (error) {
6640
+ stepCallLogger?.fail(error, {
6641
+ provider: "fireworks",
6642
+ model: request.model,
6643
+ modelVersion,
6644
+ step: turn,
6645
+ usage: usageTokens,
6646
+ blocked
6647
+ });
6648
+ throw error;
6649
+ }
5629
6650
  }
5630
6651
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5631
6652
  }
5632
- if (providerInfo.provider === "fireworks") {
5633
- if (request.modelTools && request.modelTools.length > 0) {
5634
- throw new Error(
5635
- "Fireworks provider does not support provider-native modelTools in runToolLoop."
5636
- );
5637
- }
5638
- const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
5639
- const messages = toFireworksMessages(contents);
5640
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5641
- const turn = stepIndex + 1;
5642
- const stepStartedAtMs = Date.now();
5643
- let schedulerMetrics;
5644
- const response = await runFireworksCall(
6653
+ const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
6654
+ const geminiNativeTools = toGeminiTools(request.modelTools);
6655
+ const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
6656
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
6657
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
6658
+ const turn = stepIndex + 1;
6659
+ const stepStartedAtMs = Date.now();
6660
+ let firstModelEventAtMs;
6661
+ let schedulerMetrics;
6662
+ let modelVersion = request.model;
6663
+ let usageTokens;
6664
+ let responseText = "";
6665
+ let thoughtsText = "";
6666
+ const markFirstModelEvent = () => {
6667
+ if (firstModelEventAtMs === void 0) {
6668
+ firstModelEventAtMs = Date.now();
6669
+ }
6670
+ };
6671
+ const thinkingConfig = resolveGeminiThinkingConfig(request.model, request.thinkingLevel);
6672
+ const config = {
6673
+ maxOutputTokens: 32e3,
6674
+ tools: geminiTools,
6675
+ toolConfig: {
6676
+ functionCallingConfig: {
6677
+ mode: FunctionCallingConfigMode.VALIDATED
6678
+ }
6679
+ },
6680
+ ...thinkingConfig ? { thinkingConfig } : {}
6681
+ };
6682
+ const onEvent = request.onEvent;
6683
+ const stepRequestPayload = {
6684
+ model: request.model,
6685
+ contents: geminiContents,
6686
+ config
6687
+ };
6688
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6689
+ provider: "gemini",
6690
+ modelId: request.model,
6691
+ requestPayload: stepRequestPayload,
6692
+ step: turn
6693
+ });
6694
+ try {
6695
+ const response = await runGeminiCall(
5645
6696
  async (client) => {
5646
- return await client.chat.completions.create(
5647
- {
5648
- model: providerInfo.model,
5649
- messages,
5650
- tools: fireworksTools,
5651
- tool_choice: "auto",
5652
- parallel_tool_calls: true
5653
- },
5654
- { signal: request.signal }
5655
- );
6697
+ const stream = await client.models.generateContentStream({
6698
+ model: request.model,
6699
+ contents: geminiContents,
6700
+ config
6701
+ });
6702
+ let responseText2 = "";
6703
+ let thoughtsText2 = "";
6704
+ const modelParts = [];
6705
+ const functionCalls = [];
6706
+ const seenFunctionCallIds = /* @__PURE__ */ new Set();
6707
+ const seenFunctionCallKeys = /* @__PURE__ */ new Set();
6708
+ let latestUsageMetadata;
6709
+ let resolvedModelVersion;
6710
+ for await (const chunk of stream) {
6711
+ markFirstModelEvent();
6712
+ if (chunk.modelVersion) {
6713
+ resolvedModelVersion = chunk.modelVersion;
6714
+ onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
6715
+ }
6716
+ if (chunk.usageMetadata) {
6717
+ latestUsageMetadata = chunk.usageMetadata;
6718
+ }
6719
+ const candidates = chunk.candidates;
6720
+ if (!candidates || candidates.length === 0) {
6721
+ continue;
6722
+ }
6723
+ const primary = candidates[0];
6724
+ const parts = primary?.content?.parts;
6725
+ if (!parts || parts.length === 0) {
6726
+ continue;
6727
+ }
6728
+ for (const part of parts) {
6729
+ modelParts.push(part);
6730
+ const call = part.functionCall;
6731
+ if (call) {
6732
+ const id = typeof call.id === "string" ? call.id : "";
6733
+ const shouldAdd = (() => {
6734
+ if (id.length > 0) {
6735
+ if (seenFunctionCallIds.has(id)) {
6736
+ return false;
6737
+ }
6738
+ seenFunctionCallIds.add(id);
6739
+ return true;
6740
+ }
6741
+ const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
6742
+ if (seenFunctionCallKeys.has(key)) {
6743
+ return false;
6744
+ }
6745
+ seenFunctionCallKeys.add(key);
6746
+ return true;
6747
+ })();
6748
+ if (shouldAdd) {
6749
+ functionCalls.push(call);
6750
+ }
6751
+ }
6752
+ if (typeof part.text === "string" && part.text.length > 0) {
6753
+ if (part.thought) {
6754
+ thoughtsText2 += part.text;
6755
+ stepCallLogger?.appendThoughtDelta(part.text);
6756
+ onEvent?.({ type: "delta", channel: "thought", text: part.text });
6757
+ } else {
6758
+ responseText2 += part.text;
6759
+ stepCallLogger?.appendResponseDelta(part.text);
6760
+ onEvent?.({ type: "delta", channel: "response", text: part.text });
6761
+ }
6762
+ }
6763
+ }
6764
+ }
6765
+ return {
6766
+ responseText: responseText2,
6767
+ thoughtsText: thoughtsText2,
6768
+ functionCalls,
6769
+ modelParts,
6770
+ usageMetadata: latestUsageMetadata,
6771
+ modelVersion: resolvedModelVersion ?? request.model
6772
+ };
5656
6773
  },
5657
- providerInfo.model,
6774
+ request.model,
5658
6775
  {
5659
6776
  onSettled: (metrics) => {
5660
6777
  schedulerMetrics = metrics;
@@ -5662,43 +6779,26 @@ async function runToolLoop(request) {
5662
6779
  }
5663
6780
  );
5664
6781
  const modelCompletedAtMs = Date.now();
5665
- const modelVersion = typeof response.model === "string" ? response.model : request.model;
5666
- request.onEvent?.({ type: "model", modelVersion });
5667
- const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5668
- if (choice?.finish_reason === "content_filter") {
5669
- request.onEvent?.({ type: "blocked" });
5670
- }
5671
- const message = choice?.message;
5672
- const responseText = extractFireworksMessageText(message).trim();
5673
- if (responseText.length > 0) {
5674
- request.onEvent?.({ type: "delta", channel: "response", text: responseText });
5675
- }
5676
- const usageTokens = extractFireworksUsageTokens(response.usage);
6782
+ usageTokens = extractGeminiUsageTokens(response.usageMetadata);
6783
+ modelVersion = response.modelVersion ?? request.model;
6784
+ responseText = response.responseText.trim();
6785
+ thoughtsText = response.thoughtsText.trim();
5677
6786
  const stepCostUsd = estimateCallCostUsd({
5678
6787
  modelId: modelVersion,
5679
6788
  tokens: usageTokens,
5680
6789
  responseImages: 0
5681
6790
  });
5682
6791
  totalCostUsd += stepCostUsd;
5683
- if (usageTokens) {
5684
- request.onEvent?.({
5685
- type: "usage",
5686
- usage: usageTokens,
5687
- costUsd: stepCostUsd,
5688
- modelVersion
5689
- });
5690
- }
5691
- const responseToolCalls = extractFireworksToolCalls(message);
5692
- if (responseToolCalls.length === 0) {
6792
+ if (response.functionCalls.length === 0) {
5693
6793
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5694
- const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
5695
6794
  finalText = responseText;
5696
- finalThoughts = "";
6795
+ finalThoughts = thoughtsText;
5697
6796
  const stepCompletedAtMs2 = Date.now();
5698
6797
  const timing2 = buildStepTiming({
5699
6798
  stepStartedAtMs,
5700
6799
  stepCompletedAtMs: stepCompletedAtMs2,
5701
6800
  modelCompletedAtMs,
6801
+ firstModelEventAtMs,
5702
6802
  schedulerMetrics,
5703
6803
  toolExecutionMs: 0,
5704
6804
  waitToolMs: 0
@@ -5706,31 +6806,65 @@ async function runToolLoop(request) {
5706
6806
  steps.push({
5707
6807
  step: steps.length + 1,
5708
6808
  modelVersion,
5709
- text: responseText || void 0,
5710
- thoughts: void 0,
6809
+ text: finalText || void 0,
6810
+ thoughts: finalThoughts || void 0,
5711
6811
  toolCalls: [],
5712
6812
  usage: usageTokens,
5713
6813
  costUsd: stepCostUsd,
5714
6814
  timing: timing2
5715
6815
  });
5716
- if (steeringMessages.length === 0) {
6816
+ stepCallLogger?.complete({
6817
+ provider: "gemini",
6818
+ model: request.model,
6819
+ modelVersion,
6820
+ step: turn,
6821
+ usage: usageTokens,
6822
+ costUsd: stepCostUsd,
6823
+ responseChars: responseText.length,
6824
+ thoughtChars: thoughtsText.length,
6825
+ toolCalls: 0,
6826
+ finalStep: steeringInput2.length === 0
6827
+ });
6828
+ if (steeringInput2.length === 0) {
5717
6829
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5718
6830
  }
5719
- if (responseText.length > 0) {
5720
- messages.push({ role: "assistant", content: responseText });
6831
+ const modelPartsForHistory2 = response.modelParts.filter(
6832
+ (part) => !(typeof part.text === "string" && part.thought === true)
6833
+ );
6834
+ if (modelPartsForHistory2.length > 0) {
6835
+ geminiContents.push({ role: "model", parts: modelPartsForHistory2 });
6836
+ } else if (response.responseText.length > 0) {
6837
+ geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
5721
6838
  }
5722
- messages.push(...steeringMessages);
6839
+ geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
5723
6840
  continue;
5724
6841
  }
5725
- const stepToolCalls = [];
5726
- const callInputs = responseToolCalls.map((call, index) => {
6842
+ const toolCalls = [];
6843
+ const modelPartsForHistory = response.modelParts.filter(
6844
+ (part) => !(typeof part.text === "string" && part.thought === true)
6845
+ );
6846
+ if (modelPartsForHistory.length > 0) {
6847
+ geminiContents.push({ role: "model", parts: modelPartsForHistory });
6848
+ } else {
6849
+ const parts = [];
6850
+ if (response.responseText) {
6851
+ parts.push({ text: response.responseText });
6852
+ }
6853
+ for (const call of response.functionCalls) {
6854
+ parts.push({ functionCall: call });
6855
+ }
6856
+ geminiContents.push({ role: "model", parts });
6857
+ }
6858
+ const responseParts = [];
6859
+ const callInputs = response.functionCalls.map((call, index) => {
5727
6860
  const toolIndex = index + 1;
5728
6861
  const toolId = buildToolLogId(turn, toolIndex);
5729
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5730
- return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
6862
+ const toolName = call.name ?? "unknown";
6863
+ const rawInput = call.args ?? {};
6864
+ return { call, toolName, rawInput, toolId, turn, toolIndex };
5731
6865
  });
5732
6866
  for (const entry of callInputs) {
5733
- request.onEvent?.({
6867
+ onEvent?.({
5734
6868
  type: "tool_call",
5735
6869
  phase: "started",
5736
6870
  turn: entry.turn,
@@ -5739,7 +6873,7 @@ async function runToolLoop(request) {
5739
6873
  toolId: entry.toolId,
5740
6874
  callKind: "function",
5741
6875
  callId: entry.call.id,
5742
- input: entry.value
6876
+ input: entry.rawInput
5743
6877
  });
5744
6878
  }
5745
6879
  const callResults = await Promise.all(
@@ -5756,26 +6890,23 @@ async function runToolLoop(request) {
5756
6890
  callKind: "function",
5757
6891
  toolName: entry.toolName,
5758
6892
  tool: request.tools[entry.toolName],
5759
- rawInput: entry.value,
5760
- parseError: entry.parseError
6893
+ rawInput: entry.rawInput
5761
6894
  });
5762
6895
  return { entry, result, outputPayload };
5763
6896
  }
5764
6897
  );
5765
6898
  })
5766
6899
  );
5767
- const assistantToolCalls = [];
5768
- const toolMessages = [];
5769
6900
  let toolExecutionMs = 0;
5770
6901
  let waitToolMs = 0;
5771
6902
  for (const { entry, result, outputPayload } of callResults) {
5772
- stepToolCalls.push({ ...result, callId: entry.call.id });
6903
+ toolCalls.push({ ...result, callId: entry.call.id });
5773
6904
  const callDurationMs = toToolResultDuration(result);
5774
6905
  toolExecutionMs += callDurationMs;
5775
6906
  if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5776
6907
  waitToolMs += callDurationMs;
5777
6908
  }
5778
- request.onEvent?.({
6909
+ onEvent?.({
5779
6910
  type: "tool_call",
5780
6911
  phase: "completed",
5781
6912
  turn: entry.turn,
@@ -5784,30 +6915,26 @@ async function runToolLoop(request) {
5784
6915
  toolId: entry.toolId,
5785
6916
  callKind: "function",
5786
6917
  callId: entry.call.id,
5787
- input: entry.value,
6918
+ input: entry.rawInput,
5788
6919
  output: result.output,
5789
6920
  error: result.error,
5790
6921
  durationMs: result.durationMs
5791
6922
  });
5792
- assistantToolCalls.push({
5793
- id: entry.call.id,
5794
- type: "function",
5795
- function: {
6923
+ const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
6924
+ responseParts.push({
6925
+ functionResponse: {
5796
6926
  name: entry.toolName,
5797
- arguments: entry.call.arguments
6927
+ response: responsePayload,
6928
+ ...entry.call.id ? { id: entry.call.id } : {}
5798
6929
  }
5799
6930
  });
5800
- toolMessages.push({
5801
- role: "tool",
5802
- tool_call_id: entry.call.id,
5803
- content: mergeToolOutput(outputPayload)
5804
- });
5805
6931
  }
5806
6932
  const stepCompletedAtMs = Date.now();
5807
6933
  const timing = buildStepTiming({
5808
6934
  stepStartedAtMs,
5809
6935
  stepCompletedAtMs,
5810
6936
  modelCompletedAtMs,
6937
+ firstModelEventAtMs,
5811
6938
  schedulerMetrics,
5812
6939
  toolExecutionMs,
5813
6940
  waitToolMs
@@ -5816,296 +6943,40 @@ async function runToolLoop(request) {
5816
6943
  step: steps.length + 1,
5817
6944
  modelVersion,
5818
6945
  text: responseText || void 0,
5819
- thoughts: void 0,
5820
- toolCalls: stepToolCalls,
6946
+ thoughts: thoughtsText || void 0,
6947
+ toolCalls,
5821
6948
  usage: usageTokens,
5822
6949
  costUsd: stepCostUsd,
5823
6950
  timing
5824
6951
  });
5825
- messages.push({
5826
- role: "assistant",
5827
- ...responseText.length > 0 ? { content: responseText } : {},
5828
- tool_calls: assistantToolCalls
6952
+ stepCallLogger?.complete({
6953
+ provider: "gemini",
6954
+ model: request.model,
6955
+ modelVersion,
6956
+ step: turn,
6957
+ usage: usageTokens,
6958
+ costUsd: stepCostUsd,
6959
+ responseChars: responseText.length,
6960
+ thoughtChars: thoughtsText.length,
6961
+ toolCalls: toolCalls.length,
6962
+ finalStep: false
5829
6963
  });
5830
- messages.push(...toolMessages);
6964
+ geminiContents.push({ role: "user", parts: responseParts });
5831
6965
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5832
6966
  if (steeringInput.length > 0) {
5833
- messages.push(...toFireworksMessages(steeringInput));
5834
- }
5835
- }
5836
- throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5837
- }
5838
- const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
5839
- const geminiNativeTools = toGeminiTools(request.modelTools);
5840
- const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
5841
- const geminiContents = contents.map(convertLlmContentToGeminiContent);
5842
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5843
- const stepStartedAtMs = Date.now();
5844
- let firstModelEventAtMs;
5845
- let schedulerMetrics;
5846
- const markFirstModelEvent = () => {
5847
- if (firstModelEventAtMs === void 0) {
5848
- firstModelEventAtMs = Date.now();
5849
- }
5850
- };
5851
- const thinkingConfig = resolveGeminiThinkingConfig(request.model, request.thinkingLevel);
5852
- const config = {
5853
- maxOutputTokens: 32e3,
5854
- tools: geminiTools,
5855
- toolConfig: {
5856
- functionCallingConfig: {
5857
- mode: FunctionCallingConfigMode.VALIDATED
5858
- }
5859
- },
5860
- ...thinkingConfig ? { thinkingConfig } : {}
5861
- };
5862
- const onEvent = request.onEvent;
5863
- const response = await runGeminiCall(
5864
- async (client) => {
5865
- const stream = await client.models.generateContentStream({
5866
- model: request.model,
5867
- contents: geminiContents,
5868
- config
5869
- });
5870
- let responseText = "";
5871
- let thoughtsText = "";
5872
- const modelParts = [];
5873
- const functionCalls = [];
5874
- const seenFunctionCallIds = /* @__PURE__ */ new Set();
5875
- const seenFunctionCallKeys = /* @__PURE__ */ new Set();
5876
- let latestUsageMetadata;
5877
- let resolvedModelVersion;
5878
- for await (const chunk of stream) {
5879
- markFirstModelEvent();
5880
- if (chunk.modelVersion) {
5881
- resolvedModelVersion = chunk.modelVersion;
5882
- onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
5883
- }
5884
- if (chunk.usageMetadata) {
5885
- latestUsageMetadata = chunk.usageMetadata;
5886
- }
5887
- const candidates = chunk.candidates;
5888
- if (!candidates || candidates.length === 0) {
5889
- continue;
5890
- }
5891
- const primary = candidates[0];
5892
- const parts = primary?.content?.parts;
5893
- if (!parts || parts.length === 0) {
5894
- continue;
5895
- }
5896
- for (const part of parts) {
5897
- modelParts.push(part);
5898
- const call = part.functionCall;
5899
- if (call) {
5900
- const id = typeof call.id === "string" ? call.id : "";
5901
- const shouldAdd = (() => {
5902
- if (id.length > 0) {
5903
- if (seenFunctionCallIds.has(id)) {
5904
- return false;
5905
- }
5906
- seenFunctionCallIds.add(id);
5907
- return true;
5908
- }
5909
- const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
5910
- if (seenFunctionCallKeys.has(key)) {
5911
- return false;
5912
- }
5913
- seenFunctionCallKeys.add(key);
5914
- return true;
5915
- })();
5916
- if (shouldAdd) {
5917
- functionCalls.push(call);
5918
- }
5919
- }
5920
- if (typeof part.text === "string" && part.text.length > 0) {
5921
- if (part.thought) {
5922
- thoughtsText += part.text;
5923
- onEvent?.({ type: "delta", channel: "thought", text: part.text });
5924
- } else {
5925
- responseText += part.text;
5926
- onEvent?.({ type: "delta", channel: "response", text: part.text });
5927
- }
5928
- }
5929
- }
5930
- }
5931
- return {
5932
- responseText,
5933
- thoughtsText,
5934
- functionCalls,
5935
- modelParts,
5936
- usageMetadata: latestUsageMetadata,
5937
- modelVersion: resolvedModelVersion ?? request.model
5938
- };
5939
- },
5940
- request.model,
5941
- {
5942
- onSettled: (metrics) => {
5943
- schedulerMetrics = metrics;
5944
- }
6967
+ geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
5945
6968
  }
5946
- );
5947
- const modelCompletedAtMs = Date.now();
5948
- const usageTokens = extractGeminiUsageTokens(response.usageMetadata);
5949
- const modelVersion = response.modelVersion ?? request.model;
5950
- const stepCostUsd = estimateCallCostUsd({
5951
- modelId: modelVersion,
5952
- tokens: usageTokens,
5953
- responseImages: 0
5954
- });
5955
- totalCostUsd += stepCostUsd;
5956
- if (response.functionCalls.length === 0) {
5957
- const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5958
- finalText = response.responseText.trim();
5959
- finalThoughts = response.thoughtsText.trim();
5960
- const stepCompletedAtMs2 = Date.now();
5961
- const timing2 = buildStepTiming({
5962
- stepStartedAtMs,
5963
- stepCompletedAtMs: stepCompletedAtMs2,
5964
- modelCompletedAtMs,
5965
- firstModelEventAtMs,
5966
- schedulerMetrics,
5967
- toolExecutionMs: 0,
5968
- waitToolMs: 0
5969
- });
5970
- steps.push({
5971
- step: steps.length + 1,
6969
+ } catch (error) {
6970
+ stepCallLogger?.fail(error, {
6971
+ provider: "gemini",
6972
+ model: request.model,
5972
6973
  modelVersion,
5973
- text: finalText || void 0,
5974
- thoughts: finalThoughts || void 0,
5975
- toolCalls: [],
6974
+ step: turn,
5976
6975
  usage: usageTokens,
5977
- costUsd: stepCostUsd,
5978
- timing: timing2
5979
- });
5980
- if (steeringInput2.length === 0) {
5981
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5982
- }
5983
- const modelPartsForHistory2 = response.modelParts.filter(
5984
- (part) => !(typeof part.text === "string" && part.thought === true)
5985
- );
5986
- if (modelPartsForHistory2.length > 0) {
5987
- geminiContents.push({ role: "model", parts: modelPartsForHistory2 });
5988
- } else if (response.responseText.length > 0) {
5989
- geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
5990
- }
5991
- geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
5992
- continue;
5993
- }
5994
- const toolCalls = [];
5995
- const modelPartsForHistory = response.modelParts.filter(
5996
- (part) => !(typeof part.text === "string" && part.thought === true)
5997
- );
5998
- if (modelPartsForHistory.length > 0) {
5999
- geminiContents.push({ role: "model", parts: modelPartsForHistory });
6000
- } else {
6001
- const parts = [];
6002
- if (response.responseText) {
6003
- parts.push({ text: response.responseText });
6004
- }
6005
- for (const call of response.functionCalls) {
6006
- parts.push({ functionCall: call });
6007
- }
6008
- geminiContents.push({ role: "model", parts });
6009
- }
6010
- const responseParts = [];
6011
- const callInputs = response.functionCalls.map((call, index) => {
6012
- const turn = stepIndex + 1;
6013
- const toolIndex = index + 1;
6014
- const toolId = buildToolLogId(turn, toolIndex);
6015
- const toolName = call.name ?? "unknown";
6016
- const rawInput = call.args ?? {};
6017
- return { call, toolName, rawInput, toolId, turn, toolIndex };
6018
- });
6019
- for (const entry of callInputs) {
6020
- onEvent?.({
6021
- type: "tool_call",
6022
- phase: "started",
6023
- turn: entry.turn,
6024
- toolIndex: entry.toolIndex,
6025
- toolName: entry.toolName,
6026
- toolId: entry.toolId,
6027
- callKind: "function",
6028
- callId: entry.call.id,
6029
- input: entry.rawInput
6030
- });
6031
- }
6032
- const callResults = await Promise.all(
6033
- callInputs.map(async (entry) => {
6034
- return await toolCallContextStorage.run(
6035
- {
6036
- toolName: entry.toolName,
6037
- toolId: entry.toolId,
6038
- turn: entry.turn,
6039
- toolIndex: entry.toolIndex
6040
- },
6041
- async () => {
6042
- const { result, outputPayload } = await executeToolCall({
6043
- callKind: "function",
6044
- toolName: entry.toolName,
6045
- tool: request.tools[entry.toolName],
6046
- rawInput: entry.rawInput
6047
- });
6048
- return { entry, result, outputPayload };
6049
- }
6050
- );
6051
- })
6052
- );
6053
- let toolExecutionMs = 0;
6054
- let waitToolMs = 0;
6055
- for (const { entry, result, outputPayload } of callResults) {
6056
- toolCalls.push({ ...result, callId: entry.call.id });
6057
- const callDurationMs = toToolResultDuration(result);
6058
- toolExecutionMs += callDurationMs;
6059
- if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6060
- waitToolMs += callDurationMs;
6061
- }
6062
- onEvent?.({
6063
- type: "tool_call",
6064
- phase: "completed",
6065
- turn: entry.turn,
6066
- toolIndex: entry.toolIndex,
6067
- toolName: entry.toolName,
6068
- toolId: entry.toolId,
6069
- callKind: "function",
6070
- callId: entry.call.id,
6071
- input: entry.rawInput,
6072
- output: result.output,
6073
- error: result.error,
6074
- durationMs: result.durationMs
6075
- });
6076
- const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
6077
- responseParts.push({
6078
- functionResponse: {
6079
- name: entry.toolName,
6080
- response: responsePayload,
6081
- ...entry.call.id ? { id: entry.call.id } : {}
6082
- }
6976
+ responseChars: responseText.length,
6977
+ thoughtChars: thoughtsText.length
6083
6978
  });
6084
- }
6085
- const stepCompletedAtMs = Date.now();
6086
- const timing = buildStepTiming({
6087
- stepStartedAtMs,
6088
- stepCompletedAtMs,
6089
- modelCompletedAtMs,
6090
- firstModelEventAtMs,
6091
- schedulerMetrics,
6092
- toolExecutionMs,
6093
- waitToolMs
6094
- });
6095
- steps.push({
6096
- step: steps.length + 1,
6097
- modelVersion,
6098
- text: response.responseText.trim() || void 0,
6099
- thoughts: response.thoughtsText.trim() || void 0,
6100
- toolCalls,
6101
- usage: usageTokens,
6102
- costUsd: stepCostUsd,
6103
- timing
6104
- });
6105
- geminiContents.push({ role: "user", parts: responseParts });
6106
- const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6107
- if (steeringInput.length > 0) {
6108
- geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
6979
+ throw error;
6109
6980
  }
6110
6981
  }
6111
6982
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
@@ -6419,6 +7290,7 @@ ${lines}`;
6419
7290
 
6420
7291
  // src/agent.ts
6421
7292
  import { randomBytes as randomBytes3 } from "crypto";
7293
+ import path7 from "path";
6422
7294
 
6423
7295
  // src/agent/subagents.ts
6424
7296
  import { randomBytes as randomBytes2 } from "crypto";
@@ -6893,26 +7765,26 @@ function resolveInputItemsText(items) {
6893
7765
  }
6894
7766
  const itemType = typeof item.type === "string" ? item.type.trim() : "";
6895
7767
  const name = typeof item.name === "string" ? item.name.trim() : "";
6896
- const path6 = typeof item.path === "string" ? item.path.trim() : "";
7768
+ const path8 = typeof item.path === "string" ? item.path.trim() : "";
6897
7769
  const imageUrl = typeof item.image_url === "string" ? item.image_url.trim() : "";
6898
7770
  if (itemType === "image") {
6899
7771
  lines.push("[image]");
6900
7772
  continue;
6901
7773
  }
6902
- if (itemType === "local_image" && path6) {
6903
- lines.push(`[local_image:${path6}]`);
7774
+ if (itemType === "local_image" && path8) {
7775
+ lines.push(`[local_image:${path8}]`);
6904
7776
  continue;
6905
7777
  }
6906
- if (itemType === "skill" && name && path6) {
6907
- lines.push(`[skill:$${name}](${path6})`);
7778
+ if (itemType === "skill" && name && path8) {
7779
+ lines.push(`[skill:$${name}](${path8})`);
6908
7780
  continue;
6909
7781
  }
6910
- if (itemType === "mention" && name && path6) {
6911
- lines.push(`[mention:$${name}](${path6})`);
7782
+ if (itemType === "mention" && name && path8) {
7783
+ lines.push(`[mention:$${name}](${path8})`);
6912
7784
  continue;
6913
7785
  }
6914
- if (path6 || imageUrl) {
6915
- lines.push(`[${itemType || "input"}:${path6 || imageUrl}]`);
7786
+ if (path8 || imageUrl) {
7787
+ lines.push(`[${itemType || "input"}:${path8 || imageUrl}]`);
6916
7788
  continue;
6917
7789
  }
6918
7790
  if (name) {
@@ -7037,7 +7909,7 @@ function startRun(agent, options) {
7037
7909
  setLifecycle(agent, "idle", "input_queued", `Subagent ${agent.id} run interrupted.`);
7038
7910
  return;
7039
7911
  }
7040
- const message = toErrorMessage(error);
7912
+ const message = toErrorMessage2(error);
7041
7913
  agent.lastError = message;
7042
7914
  setLifecycle(agent, "failed", "run_failed", `Subagent ${agent.id} failed: ${message}`);
7043
7915
  emitBackgroundNotification(agent, options);
@@ -7217,7 +8089,7 @@ function trimToUndefined(value) {
7217
8089
  const trimmed = value?.trim();
7218
8090
  return trimmed && trimmed.length > 0 ? trimmed : void 0;
7219
8091
  }
7220
- function toErrorMessage(error) {
8092
+ function toErrorMessage2(error) {
7221
8093
  if (error instanceof Error) {
7222
8094
  return error.message;
7223
8095
  }
@@ -7230,27 +8102,27 @@ function sleep2(ms) {
7230
8102
  }
7231
8103
 
7232
8104
  // src/tools/filesystemTools.ts
7233
- import path5 from "path";
7234
- import { Buffer as Buffer4 } from "buffer";
8105
+ import path6 from "path";
8106
+ import { Buffer as Buffer5 } from "buffer";
7235
8107
  import { z as z6 } from "zod";
7236
8108
 
7237
8109
  // src/tools/applyPatch.ts
7238
- import path4 from "path";
8110
+ import path5 from "path";
7239
8111
  import { z as z5 } from "zod";
7240
8112
 
7241
8113
  // src/tools/filesystem.ts
7242
8114
  import { promises as fs3 } from "fs";
7243
- import path3 from "path";
8115
+ import path4 from "path";
7244
8116
  var InMemoryAgentFilesystem = class {
7245
8117
  #files = /* @__PURE__ */ new Map();
7246
8118
  #dirs = /* @__PURE__ */ new Map();
7247
8119
  #clock = 0;
7248
8120
  constructor(initialFiles = {}) {
7249
- const root = path3.resolve("/");
8121
+ const root = path4.resolve("/");
7250
8122
  this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
7251
8123
  for (const [filePath, content] of Object.entries(initialFiles)) {
7252
- const absolutePath = path3.resolve(filePath);
7253
- this.#ensureDirSync(path3.dirname(absolutePath));
8124
+ const absolutePath = path4.resolve(filePath);
8125
+ this.#ensureDirSync(path4.dirname(absolutePath));
7254
8126
  this.#files.set(absolutePath, {
7255
8127
  content,
7256
8128
  mtimeMs: this.#nextMtime()
@@ -7258,7 +8130,7 @@ var InMemoryAgentFilesystem = class {
7258
8130
  }
7259
8131
  }
7260
8132
  async readTextFile(filePath) {
7261
- const absolutePath = path3.resolve(filePath);
8133
+ const absolutePath = path4.resolve(filePath);
7262
8134
  const file = this.#files.get(absolutePath);
7263
8135
  if (!file) {
7264
8136
  throw createNoSuchFileError("open", absolutePath);
@@ -7270,24 +8142,24 @@ var InMemoryAgentFilesystem = class {
7270
8142
  return Buffer.from(content, "utf8");
7271
8143
  }
7272
8144
  async writeTextFile(filePath, content) {
7273
- const absolutePath = path3.resolve(filePath);
7274
- const parentPath = path3.dirname(absolutePath);
8145
+ const absolutePath = path4.resolve(filePath);
8146
+ const parentPath = path4.dirname(absolutePath);
7275
8147
  if (!this.#dirs.has(parentPath)) {
7276
8148
  throw createNoSuchFileError("open", parentPath);
7277
8149
  }
7278
8150
  this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
7279
8151
  }
7280
8152
  async deleteFile(filePath) {
7281
- const absolutePath = path3.resolve(filePath);
8153
+ const absolutePath = path4.resolve(filePath);
7282
8154
  if (!this.#files.delete(absolutePath)) {
7283
8155
  throw createNoSuchFileError("unlink", absolutePath);
7284
8156
  }
7285
8157
  }
7286
8158
  async ensureDir(directoryPath) {
7287
- this.#ensureDirSync(path3.resolve(directoryPath));
8159
+ this.#ensureDirSync(path4.resolve(directoryPath));
7288
8160
  }
7289
8161
  async readDir(directoryPath) {
7290
- const absolutePath = path3.resolve(directoryPath);
8162
+ const absolutePath = path4.resolve(directoryPath);
7291
8163
  const directory = this.#dirs.get(absolutePath);
7292
8164
  if (!directory) {
7293
8165
  throw createNoSuchFileError("scandir", absolutePath);
@@ -7298,10 +8170,10 @@ var InMemoryAgentFilesystem = class {
7298
8170
  if (dirPath === absolutePath) {
7299
8171
  continue;
7300
8172
  }
7301
- if (path3.dirname(dirPath) !== absolutePath) {
8173
+ if (path4.dirname(dirPath) !== absolutePath) {
7302
8174
  continue;
7303
8175
  }
7304
- const name = path3.basename(dirPath);
8176
+ const name = path4.basename(dirPath);
7305
8177
  if (seenNames.has(name)) {
7306
8178
  continue;
7307
8179
  }
@@ -7314,10 +8186,10 @@ var InMemoryAgentFilesystem = class {
7314
8186
  });
7315
8187
  }
7316
8188
  for (const [filePath, fileRecord] of this.#files.entries()) {
7317
- if (path3.dirname(filePath) !== absolutePath) {
8189
+ if (path4.dirname(filePath) !== absolutePath) {
7318
8190
  continue;
7319
8191
  }
7320
- const name = path3.basename(filePath);
8192
+ const name = path4.basename(filePath);
7321
8193
  if (seenNames.has(name)) {
7322
8194
  continue;
7323
8195
  }
@@ -7333,7 +8205,7 @@ var InMemoryAgentFilesystem = class {
7333
8205
  return entries;
7334
8206
  }
7335
8207
  async stat(entryPath) {
7336
- const absolutePath = path3.resolve(entryPath);
8208
+ const absolutePath = path4.resolve(entryPath);
7337
8209
  const file = this.#files.get(absolutePath);
7338
8210
  if (file) {
7339
8211
  return { kind: "file", mtimeMs: file.mtimeMs };
@@ -7349,7 +8221,7 @@ var InMemoryAgentFilesystem = class {
7349
8221
  return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
7350
8222
  }
7351
8223
  #ensureDirSync(directoryPath) {
7352
- const absolutePath = path3.resolve(directoryPath);
8224
+ const absolutePath = path4.resolve(directoryPath);
7353
8225
  const parts = [];
7354
8226
  let cursor = absolutePath;
7355
8227
  for (; ; ) {
@@ -7357,7 +8229,7 @@ var InMemoryAgentFilesystem = class {
7357
8229
  break;
7358
8230
  }
7359
8231
  parts.push(cursor);
7360
- const parent = path3.dirname(cursor);
8232
+ const parent = path4.dirname(cursor);
7361
8233
  if (parent === cursor) {
7362
8234
  break;
7363
8235
  }
@@ -7391,7 +8263,7 @@ function createNodeAgentFilesystem() {
7391
8263
  const entries = await fs3.readdir(directoryPath, { withFileTypes: true });
7392
8264
  const result = [];
7393
8265
  for (const entry of entries) {
7394
- const entryPath = path3.resolve(directoryPath, entry.name);
8266
+ const entryPath = path4.resolve(directoryPath, entry.name);
7395
8267
  const stats = await fs3.lstat(entryPath);
7396
8268
  result.push({
7397
8269
  name: entry.name,
@@ -7555,7 +8427,7 @@ function createApplyPatchTool(options = {}) {
7555
8427
  });
7556
8428
  }
7557
8429
  async function applyPatch(request) {
7558
- const cwd = path4.resolve(request.cwd ?? process.cwd());
8430
+ const cwd = path5.resolve(request.cwd ?? process.cwd());
7559
8431
  const adapter = request.fs ?? createNodeAgentFilesystem();
7560
8432
  const allowOutsideCwd = request.allowOutsideCwd === true;
7561
8433
  const patchBytes = Buffer.byteLength(request.patch, "utf8");
@@ -7577,7 +8449,7 @@ async function applyPatch(request) {
7577
8449
  kind: "add",
7578
8450
  path: absolutePath2
7579
8451
  });
7580
- await adapter.ensureDir(path4.dirname(absolutePath2));
8452
+ await adapter.ensureDir(path5.dirname(absolutePath2));
7581
8453
  await adapter.writeTextFile(absolutePath2, operation.content);
7582
8454
  added.push(toDisplayPath(absolutePath2, cwd));
7583
8455
  continue;
@@ -7611,7 +8483,7 @@ async function applyPatch(request) {
7611
8483
  fromPath: absolutePath,
7612
8484
  toPath: destinationPath
7613
8485
  });
7614
- await adapter.ensureDir(path4.dirname(destinationPath));
8486
+ await adapter.ensureDir(path5.dirname(destinationPath));
7615
8487
  await adapter.writeTextFile(destinationPath, next);
7616
8488
  await adapter.deleteFile(absolutePath);
7617
8489
  modified.push(toDisplayPath(destinationPath, cwd));
@@ -7642,22 +8514,22 @@ function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
7642
8514
  if (trimmed.length === 0) {
7643
8515
  throw new Error("apply_patch failed: empty file path");
7644
8516
  }
7645
- const absolutePath = path4.isAbsolute(trimmed) ? path4.resolve(trimmed) : path4.resolve(cwd, trimmed);
8517
+ const absolutePath = path5.isAbsolute(trimmed) ? path5.resolve(trimmed) : path5.resolve(cwd, trimmed);
7646
8518
  if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
7647
8519
  throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
7648
8520
  }
7649
8521
  return absolutePath;
7650
8522
  }
7651
8523
  function isPathInsideCwd(candidatePath, cwd) {
7652
- const relative = path4.relative(cwd, candidatePath);
7653
- return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
8524
+ const relative = path5.relative(cwd, candidatePath);
8525
+ return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
7654
8526
  }
7655
8527
  function toDisplayPath(absolutePath, cwd) {
7656
- const relative = path4.relative(cwd, absolutePath);
8528
+ const relative = path5.relative(cwd, absolutePath);
7657
8529
  if (relative === "") {
7658
8530
  return ".";
7659
8531
  }
7660
- if (!relative.startsWith("..") && !path4.isAbsolute(relative)) {
8532
+ if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
7661
8533
  return relative;
7662
8534
  }
7663
8535
  return absolutePath;
@@ -8424,7 +9296,7 @@ async function readBinaryFile(filesystem, filePath) {
8424
9296
  return await filesystem.readBinaryFile(filePath);
8425
9297
  }
8426
9298
  const text = await filesystem.readTextFile(filePath);
8427
- return Buffer4.from(text, "utf8");
9299
+ return Buffer5.from(text, "utf8");
8428
9300
  }
8429
9301
  function detectImageMimeType(buffer, filePath) {
8430
9302
  if (buffer.length >= 8 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10) {
@@ -8442,7 +9314,7 @@ function detectImageMimeType(buffer, filePath) {
8442
9314
  if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
8443
9315
  return "image/webp";
8444
9316
  }
8445
- const fromExtension = IMAGE_MIME_BY_EXTENSION[path5.extname(filePath).toLowerCase()];
9317
+ const fromExtension = IMAGE_MIME_BY_EXTENSION[path6.extname(filePath).toLowerCase()];
8446
9318
  if (fromExtension && SUPPORTED_IMAGE_MIME_TYPES.has(fromExtension)) {
8447
9319
  return fromExtension;
8448
9320
  }
@@ -8452,13 +9324,13 @@ function isPdfFile(buffer, filePath) {
8452
9324
  if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
8453
9325
  return true;
8454
9326
  }
8455
- return path5.extname(filePath).toLowerCase() === ".pdf";
9327
+ return path6.extname(filePath).toLowerCase() === ".pdf";
8456
9328
  }
8457
9329
  function isValidUtf8(buffer) {
8458
9330
  if (buffer.length === 0) {
8459
9331
  return true;
8460
9332
  }
8461
- return Buffer4.from(buffer.toString("utf8"), "utf8").equals(buffer);
9333
+ return Buffer5.from(buffer.toString("utf8"), "utf8").equals(buffer);
8462
9334
  }
8463
9335
  async function readFileGemini(input, options) {
8464
9336
  const runtime = resolveRuntime(options);
@@ -8490,7 +9362,7 @@ async function writeFileGemini(input, options) {
8490
9362
  action: "write",
8491
9363
  path: filePath
8492
9364
  });
8493
- await runtime.filesystem.ensureDir(path5.dirname(filePath));
9365
+ await runtime.filesystem.ensureDir(path6.dirname(filePath));
8494
9366
  await runtime.filesystem.writeTextFile(filePath, input.content);
8495
9367
  return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
8496
9368
  }
@@ -8511,7 +9383,7 @@ async function replaceFileContentGemini(input, options) {
8511
9383
  originalContent = await runtime.filesystem.readTextFile(filePath);
8512
9384
  } catch (error) {
8513
9385
  if (isNoEntError(error) && oldValue.length === 0) {
8514
- await runtime.filesystem.ensureDir(path5.dirname(filePath));
9386
+ await runtime.filesystem.ensureDir(path6.dirname(filePath));
8515
9387
  await runtime.filesystem.writeTextFile(filePath, newValue);
8516
9388
  return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
8517
9389
  }
@@ -8689,7 +9561,7 @@ async function globFilesGemini(input, options) {
8689
9561
  });
8690
9562
  const matched = [];
8691
9563
  for (const filePath of files) {
8692
- const relativePath = normalizeSlashes(path5.relative(dirPath, filePath));
9564
+ const relativePath = normalizeSlashes(path6.relative(dirPath, filePath));
8693
9565
  if (!matcher(relativePath)) {
8694
9566
  continue;
8695
9567
  }
@@ -8707,7 +9579,7 @@ async function globFilesGemini(input, options) {
8707
9579
  }
8708
9580
  function resolveRuntime(options) {
8709
9581
  return {
8710
- cwd: path5.resolve(options.cwd ?? process.cwd()),
9582
+ cwd: path6.resolve(options.cwd ?? process.cwd()),
8711
9583
  filesystem: options.fs ?? createNodeAgentFilesystem(),
8712
9584
  allowOutsideCwd: options.allowOutsideCwd === true,
8713
9585
  checkAccess: options.checkAccess,
@@ -8738,13 +9610,13 @@ function mapApplyPatchAction(action) {
8738
9610
  return "move";
8739
9611
  }
8740
9612
  function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
8741
- const absolutePath = path5.isAbsolute(inputPath) ? path5.resolve(inputPath) : path5.resolve(cwd, inputPath);
9613
+ const absolutePath = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(cwd, inputPath);
8742
9614
  if (allowOutsideCwd || isPathInsideCwd2(absolutePath, cwd)) {
8743
9615
  return absolutePath;
8744
9616
  }
8745
- if (path5.isAbsolute(inputPath)) {
9617
+ if (path6.isAbsolute(inputPath)) {
8746
9618
  const sandboxRelativePath = inputPath.replace(/^[/\\]+/, "");
8747
- const sandboxRootedPath = path5.resolve(cwd, sandboxRelativePath);
9619
+ const sandboxRootedPath = path6.resolve(cwd, sandboxRelativePath);
8748
9620
  if (isPathInsideCwd2(sandboxRootedPath, cwd)) {
8749
9621
  return sandboxRootedPath;
8750
9622
  }
@@ -8752,25 +9624,25 @@ function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
8752
9624
  throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
8753
9625
  }
8754
9626
  function isPathInsideCwd2(candidatePath, cwd) {
8755
- const relative = path5.relative(cwd, candidatePath);
8756
- return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
9627
+ const relative = path6.relative(cwd, candidatePath);
9628
+ return relative === "" || !relative.startsWith("..") && !path6.isAbsolute(relative);
8757
9629
  }
8758
9630
  function toDisplayPath2(absolutePath, cwd) {
8759
- const relative = path5.relative(cwd, absolutePath);
9631
+ const relative = path6.relative(cwd, absolutePath);
8760
9632
  if (relative === "") {
8761
9633
  return ".";
8762
9634
  }
8763
- if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
9635
+ if (!relative.startsWith("..") && !path6.isAbsolute(relative)) {
8764
9636
  return relative;
8765
9637
  }
8766
9638
  return absolutePath;
8767
9639
  }
8768
9640
  function toSandboxDisplayPath(absolutePath, cwd) {
8769
- const relative = path5.relative(cwd, absolutePath);
9641
+ const relative = path6.relative(cwd, absolutePath);
8770
9642
  if (relative === "") {
8771
9643
  return "/";
8772
9644
  }
8773
- if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
9645
+ if (!relative.startsWith("..") && !path6.isAbsolute(relative)) {
8774
9646
  return `/${normalizeSlashes(relative)}`;
8775
9647
  }
8776
9648
  return normalizeSlashes(absolutePath);
@@ -8886,7 +9758,7 @@ function createGlobMatcher(pattern, caseSensitive = false) {
8886
9758
  }));
8887
9759
  return (candidatePath) => {
8888
9760
  const normalizedPath = normalizeSlashes(candidatePath);
8889
- const basename = path5.posix.basename(normalizedPath);
9761
+ const basename = path6.posix.basename(normalizedPath);
8890
9762
  return compiled.some(
8891
9763
  (entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
8892
9764
  );
@@ -9002,10 +9874,18 @@ function isNoEntError(error) {
9002
9874
  // src/agent.ts
9003
9875
  async function runAgentLoop(request) {
9004
9876
  const telemetry = createAgentTelemetrySession(request.telemetry);
9877
+ const logging = createRootAgentLoggingSession(request);
9005
9878
  try {
9006
- return await runAgentLoopInternal(request, { depth: 0, telemetry });
9879
+ return await runWithAgentLoggingSession(logging, async () => {
9880
+ return await runAgentLoopInternal(request, {
9881
+ depth: 0,
9882
+ telemetry,
9883
+ logging
9884
+ });
9885
+ });
9007
9886
  } finally {
9008
9887
  await telemetry?.flush();
9888
+ await logging?.flush();
9009
9889
  }
9010
9890
  }
9011
9891
  function mergeAbortSignals2(first, second) {
@@ -9076,9 +9956,11 @@ async function runAgentLoopInternal(request, context) {
9076
9956
  subagent_tool,
9077
9957
  subagents,
9078
9958
  telemetry,
9959
+ logging: _logging,
9079
9960
  ...toolLoopRequest
9080
9961
  } = request;
9081
9962
  const telemetrySession = context.telemetry ?? createAgentTelemetrySession(telemetry);
9963
+ const loggingSession = context.logging;
9082
9964
  const runId = randomRunId();
9083
9965
  const startedAtMs = Date.now();
9084
9966
  const steeringChannel = toolLoopRequest.steering ?? createToolLoopSteeringChannel();
@@ -9092,6 +9974,7 @@ async function runAgentLoopInternal(request, context) {
9092
9974
  model: request.model,
9093
9975
  depth: context.depth,
9094
9976
  telemetry: telemetrySession,
9977
+ logging: loggingSession,
9095
9978
  customTools: customTools ?? {},
9096
9979
  filesystemSelection,
9097
9980
  subagentSelection,
@@ -9128,6 +10011,16 @@ async function runAgentLoopInternal(request, context) {
9128
10011
  filesystemToolsEnabled: Object.keys(filesystemTools).length > 0,
9129
10012
  subagentToolsEnabled: resolvedSubagentConfig.enabled
9130
10013
  });
10014
+ loggingSession?.logLine(
10015
+ [
10016
+ `[agent:${runId}] run_started`,
10017
+ `depth=${context.depth.toString()}`,
10018
+ `model=${request.model}`,
10019
+ `tools=${Object.keys(mergedTools).length.toString()}`,
10020
+ `filesystemTools=${Object.keys(filesystemTools).length > 0 ? "true" : "false"}`,
10021
+ `subagentTools=${resolvedSubagentConfig.enabled ? "true" : "false"}`
10022
+ ].join(" ")
10023
+ );
9131
10024
  const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
9132
10025
  const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
9133
10026
  const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
@@ -9135,6 +10028,14 @@ async function runAgentLoopInternal(request, context) {
9135
10028
  if (includeLlmStreamEvents) {
9136
10029
  emitTelemetry({ type: "agent.run.stream", event });
9137
10030
  }
10031
+ if (loggingSession) {
10032
+ appendAgentStreamEventLog({
10033
+ event,
10034
+ append: (line) => {
10035
+ loggingSession.logLine(`[agent:${runId}] ${line}`);
10036
+ }
10037
+ });
10038
+ }
9138
10039
  } : void 0;
9139
10040
  try {
9140
10041
  const result = await runToolLoop({
@@ -9152,14 +10053,43 @@ async function runAgentLoopInternal(request, context) {
9152
10053
  totalCostUsd: result.totalCostUsd,
9153
10054
  usage: summarizeResultUsage(result)
9154
10055
  });
10056
+ loggingSession?.logLine(
10057
+ [
10058
+ `[agent:${runId}] run_completed`,
10059
+ `status=ok`,
10060
+ `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
10061
+ `steps=${result.steps.length.toString()}`,
10062
+ `toolCalls=${countToolCalls(result).toString()}`,
10063
+ `totalCostUsd=${(result.totalCostUsd ?? 0).toFixed(6)}`
10064
+ ].join(" ")
10065
+ );
10066
+ for (const step of result.steps) {
10067
+ loggingSession?.logLine(
10068
+ [
10069
+ `[agent:${runId}] step_completed`,
10070
+ `step=${step.step.toString()}`,
10071
+ `modelVersion=${step.modelVersion}`,
10072
+ `toolCalls=${step.toolCalls.length.toString()}`,
10073
+ `costUsd=${(step.costUsd ?? 0).toFixed(6)}`
10074
+ ].join(" ")
10075
+ );
10076
+ }
9155
10077
  return result;
9156
10078
  } catch (error) {
9157
10079
  emitTelemetry({
9158
10080
  type: "agent.run.completed",
9159
10081
  success: false,
9160
10082
  durationMs: Math.max(0, Date.now() - startedAtMs),
9161
- error: toErrorMessage2(error)
10083
+ error: toErrorMessage3(error)
9162
10084
  });
10085
+ loggingSession?.logLine(
10086
+ [
10087
+ `[agent:${runId}] run_completed`,
10088
+ `status=error`,
10089
+ `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
10090
+ `error=${toErrorMessage3(error)}`
10091
+ ].join(" ")
10092
+ );
9163
10093
  throw error;
9164
10094
  } finally {
9165
10095
  await subagentController?.closeAll();
@@ -9230,7 +10160,8 @@ function createSubagentController(params) {
9230
10160
  {
9231
10161
  depth: params.depth + 1,
9232
10162
  parentRunId: params.runId,
9233
- telemetry: params.telemetry
10163
+ telemetry: params.telemetry,
10164
+ logging: params.logging
9234
10165
  }
9235
10166
  );
9236
10167
  }
@@ -9294,10 +10225,10 @@ function trimToUndefined2(value) {
9294
10225
  function randomRunId() {
9295
10226
  return randomBytes3(8).toString("hex");
9296
10227
  }
9297
- function toIsoNow() {
10228
+ function toIsoNow2() {
9298
10229
  return (/* @__PURE__ */ new Date()).toISOString();
9299
10230
  }
9300
- function toErrorMessage2(error) {
10231
+ function toErrorMessage3(error) {
9301
10232
  if (error instanceof Error && error.message) {
9302
10233
  return error.message;
9303
10234
  }
@@ -9342,9 +10273,41 @@ function summarizeResultUsage(result) {
9342
10273
  }
9343
10274
  return summary;
9344
10275
  }
9345
- function isPromiseLike(value) {
10276
+ function isPromiseLike2(value) {
9346
10277
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
9347
10278
  }
10279
+ function resolveAgentLoggingSelection(value) {
10280
+ if (value === false) {
10281
+ return void 0;
10282
+ }
10283
+ if (value === void 0 || value === true) {
10284
+ return {
10285
+ mirrorToConsole: true
10286
+ };
10287
+ }
10288
+ return value;
10289
+ }
10290
+ function resolveWorkspaceDirForLogging(request) {
10291
+ const explicitSelection = request.filesystemTool ?? request.filesystem_tool;
10292
+ if (explicitSelection && typeof explicitSelection === "object" && !Array.isArray(explicitSelection)) {
10293
+ const cwd = explicitSelection.options?.cwd;
10294
+ if (typeof cwd === "string" && cwd.trim().length > 0) {
10295
+ return path7.resolve(cwd);
10296
+ }
10297
+ }
10298
+ return process.cwd();
10299
+ }
10300
+ function createRootAgentLoggingSession(request) {
10301
+ const selected = resolveAgentLoggingSelection(request.logging);
10302
+ if (!selected) {
10303
+ return void 0;
10304
+ }
10305
+ return createAgentLoggingSession({
10306
+ ...selected,
10307
+ workspaceDir: typeof selected.workspaceDir === "string" && selected.workspaceDir.trim().length > 0 ? path7.resolve(selected.workspaceDir) : resolveWorkspaceDirForLogging(request),
10308
+ mirrorToConsole: selected.mirrorToConsole !== false
10309
+ });
10310
+ }
9348
10311
  function isAgentTelemetrySink(value) {
9349
10312
  return typeof value === "object" && value !== null && typeof value.emit === "function";
9350
10313
  }
@@ -9375,7 +10338,7 @@ function createAgentTelemetrySession(telemetry) {
9375
10338
  const emit = (event) => {
9376
10339
  try {
9377
10340
  const output = config.sink.emit(event);
9378
- if (isPromiseLike(output)) {
10341
+ if (isPromiseLike2(output)) {
9379
10342
  const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
9380
10343
  trackPromise(task);
9381
10344
  }
@@ -9406,7 +10369,7 @@ function createAgentTelemetryEmitter(params) {
9406
10369
  }
9407
10370
  params.session.emit({
9408
10371
  ...event,
9409
- timestamp: toIsoNow(),
10372
+ timestamp: toIsoNow2(),
9410
10373
  runId: params.runId,
9411
10374
  ...params.parentRunId ? { parentRunId: params.parentRunId } : {},
9412
10375
  depth: params.depth,