@ljoukov/llm 4.0.0 → 4.0.2

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,
@@ -2643,8 +2643,375 @@ function stripChatGptPrefix(model) {
2643
2643
  return model.slice("chatgpt-".length);
2644
2644
  }
2645
2645
 
2646
+ // src/agentLogging.ts
2647
+ import { AsyncLocalStorage } from "async_hooks";
2648
+ import { Buffer as Buffer3 } from "buffer";
2649
+ import { appendFile, mkdir, writeFile } from "fs/promises";
2650
+ import path3 from "path";
2651
+ function toIsoNow() {
2652
+ return (/* @__PURE__ */ new Date()).toISOString();
2653
+ }
2654
+ function toErrorMessage(error) {
2655
+ if (error instanceof Error && error.message) {
2656
+ return error.message;
2657
+ }
2658
+ if (typeof error === "string") {
2659
+ return error;
2660
+ }
2661
+ return String(error);
2662
+ }
2663
+ function isPromiseLike(value) {
2664
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
2665
+ }
2666
+ function normalisePathSegment(value) {
2667
+ const cleaned = value.trim().replace(/[^a-z0-9._-]+/giu, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
2668
+ return cleaned.length > 0 ? cleaned : "segment";
2669
+ }
2670
+ function ensureTrailingNewline(value) {
2671
+ return value.endsWith("\n") ? value : `${value}
2672
+ `;
2673
+ }
2674
+ function redactDataUrlPayload(value) {
2675
+ if (!value.toLowerCase().startsWith("data:")) {
2676
+ return value;
2677
+ }
2678
+ const commaIndex = value.indexOf(",");
2679
+ if (commaIndex < 0) {
2680
+ return value;
2681
+ }
2682
+ return `${value.slice(0, commaIndex + 1)}...`;
2683
+ }
2684
+ function sanitiseLogValue(value, seen = /* @__PURE__ */ new WeakSet()) {
2685
+ if (typeof value === "string") {
2686
+ return redactDataUrlPayload(value);
2687
+ }
2688
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) {
2689
+ return value;
2690
+ }
2691
+ if (Array.isArray(value)) {
2692
+ return value.map((entry) => sanitiseLogValue(entry, seen));
2693
+ }
2694
+ if (typeof value !== "object") {
2695
+ return String(value);
2696
+ }
2697
+ if (seen.has(value)) {
2698
+ return "[circular]";
2699
+ }
2700
+ seen.add(value);
2701
+ const record = value;
2702
+ const output = {};
2703
+ const hasInlineMime = typeof record.mimeType === "string" && record.mimeType.trim().length > 0 || typeof record.mime_type === "string" && record.mime_type.trim().length > 0;
2704
+ for (const [key, entryValue] of Object.entries(record)) {
2705
+ if (key === "image_url") {
2706
+ if (typeof entryValue === "string") {
2707
+ output[key] = redactDataUrlPayload(entryValue);
2708
+ continue;
2709
+ }
2710
+ if (entryValue && typeof entryValue === "object") {
2711
+ const nested = entryValue;
2712
+ if (typeof nested.url === "string") {
2713
+ output[key] = {
2714
+ ...nested,
2715
+ url: redactDataUrlPayload(nested.url)
2716
+ };
2717
+ continue;
2718
+ }
2719
+ }
2720
+ }
2721
+ if (key === "data" && hasInlineMime && typeof entryValue === "string") {
2722
+ output[key] = `[omitted:${Buffer3.byteLength(entryValue, "utf8")}b]`;
2723
+ continue;
2724
+ }
2725
+ output[key] = sanitiseLogValue(entryValue, seen);
2726
+ }
2727
+ return output;
2728
+ }
2729
+ function serialiseForSnippet(value) {
2730
+ if (typeof value === "string") {
2731
+ return value;
2732
+ }
2733
+ try {
2734
+ return JSON.stringify(sanitiseLogValue(value));
2735
+ } catch {
2736
+ return String(value);
2737
+ }
2738
+ }
2739
+ function formatToolLogSnippet(value) {
2740
+ const compact = serialiseForSnippet(value).replace(/\s+/gu, " ").trim();
2741
+ if (compact.length === 0) {
2742
+ return "<empty>";
2743
+ }
2744
+ const max = 600;
2745
+ if (compact.length <= max) {
2746
+ return compact;
2747
+ }
2748
+ return `${compact.slice(0, max)}...`;
2749
+ }
2750
+ function formatUsd(value) {
2751
+ const amount = typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
2752
+ return amount.toFixed(6);
2753
+ }
2754
+ function appendToolCallStreamLog(options) {
2755
+ const event = options.event;
2756
+ if (event.type !== "tool_call") {
2757
+ return;
2758
+ }
2759
+ const callIdSegment = typeof event.callId === "string" && event.callId.trim().length > 0 ? ` callId=${event.callId}` : "";
2760
+ const prefix = [
2761
+ `tool_call_${event.phase}:`,
2762
+ `turn=${event.turn.toString()}`,
2763
+ `index=${event.toolIndex.toString()}`,
2764
+ `tool=${event.toolName}${callIdSegment}`
2765
+ ].join(" ");
2766
+ if (event.phase === "started") {
2767
+ options.append(prefix);
2768
+ options.append(`tool_call_input: ${formatToolLogSnippet(sanitiseLogValue(event.input))}`);
2769
+ return;
2770
+ }
2771
+ const durationSegment = typeof event.durationMs === "number" && Number.isFinite(event.durationMs) ? ` durationMs=${Math.max(0, Math.round(event.durationMs)).toString()}` : "";
2772
+ options.append(`${prefix} status=${event.error ? "error" : "ok"}${durationSegment}`);
2773
+ options.append(`tool_call_output: ${formatToolLogSnippet(sanitiseLogValue(event.output))}`);
2774
+ if (typeof event.error === "string" && event.error.trim().length > 0) {
2775
+ options.append(`tool_call_error: ${event.error.trim()}`);
2776
+ }
2777
+ }
2778
+ function appendAgentStreamEventLog(options) {
2779
+ const event = options.event;
2780
+ switch (event.type) {
2781
+ case "delta": {
2782
+ const channelPrefix = event.channel === "thought" ? "thought_delta" : "response_delta";
2783
+ options.append(`${channelPrefix}: ${event.text}`);
2784
+ return;
2785
+ }
2786
+ case "model": {
2787
+ options.append(`model: ${event.modelVersion}`);
2788
+ return;
2789
+ }
2790
+ case "usage": {
2791
+ options.append(
2792
+ [
2793
+ "usage:",
2794
+ `modelVersion=${event.modelVersion}`,
2795
+ `costUsd=${formatUsd(event.costUsd)}`,
2796
+ `tokens=${formatToolLogSnippet(sanitiseLogValue(event.usage))}`
2797
+ ].join(" ")
2798
+ );
2799
+ return;
2800
+ }
2801
+ case "blocked": {
2802
+ options.append("blocked");
2803
+ return;
2804
+ }
2805
+ case "tool_call": {
2806
+ appendToolCallStreamLog({
2807
+ event,
2808
+ append: options.append
2809
+ });
2810
+ return;
2811
+ }
2812
+ }
2813
+ }
2814
+ var AgentLoggingSessionImpl = class {
2815
+ workspaceDir;
2816
+ logsRootDir;
2817
+ mirrorToConsole;
2818
+ sink;
2819
+ agentLogPath;
2820
+ ensureReady;
2821
+ pending = /* @__PURE__ */ new Set();
2822
+ lineChain = Promise.resolve();
2823
+ callCounter = 0;
2824
+ constructor(config) {
2825
+ this.workspaceDir = path3.resolve(config.workspaceDir ?? process.cwd());
2826
+ this.logsRootDir = path3.join(path3.dirname(this.workspaceDir), "logs");
2827
+ this.mirrorToConsole = config.mirrorToConsole !== false;
2828
+ this.sink = config.sink;
2829
+ this.agentLogPath = path3.join(this.workspaceDir, "agent.log");
2830
+ this.ensureReady = this.prepare();
2831
+ this.track(this.ensureReady);
2832
+ }
2833
+ async prepare() {
2834
+ await mkdir(this.workspaceDir, { recursive: true });
2835
+ await mkdir(this.logsRootDir, { recursive: true });
2836
+ }
2837
+ track(task) {
2838
+ this.pending.add(task);
2839
+ task.finally(() => {
2840
+ this.pending.delete(task);
2841
+ });
2842
+ }
2843
+ enqueueLineWrite(line) {
2844
+ const next = this.lineChain.then(async () => {
2845
+ await this.ensureReady;
2846
+ await appendFile(this.agentLogPath, `${line}
2847
+ `, "utf8");
2848
+ const sinkResult = this.sink?.append(line);
2849
+ if (isPromiseLike(sinkResult)) {
2850
+ await sinkResult;
2851
+ }
2852
+ });
2853
+ const tracked = next.catch(() => void 0);
2854
+ this.lineChain = tracked;
2855
+ this.track(tracked);
2856
+ }
2857
+ logLine(line) {
2858
+ const timestamped = `${toIsoNow()} ${line}`;
2859
+ if (this.mirrorToConsole) {
2860
+ console.log(timestamped);
2861
+ }
2862
+ this.enqueueLineWrite(timestamped);
2863
+ }
2864
+ startLlmCall(input) {
2865
+ const callNumber = this.callCounter + 1;
2866
+ this.callCounter = callNumber;
2867
+ const timestampSegment = toIsoNow().replace(/[:]/g, "-");
2868
+ const modelSegment = normalisePathSegment(input.modelId);
2869
+ const baseDir = path3.join(
2870
+ this.logsRootDir,
2871
+ `${timestampSegment}-${callNumber.toString().padStart(4, "0")}`,
2872
+ modelSegment
2873
+ );
2874
+ const responsePath = path3.join(baseDir, "response.txt");
2875
+ const thoughtsPath = path3.join(baseDir, "thoughts.txt");
2876
+ const responseMetadataPath = path3.join(baseDir, "response.metadata.json");
2877
+ let chain = this.ensureReady.then(async () => {
2878
+ await mkdir(baseDir, { recursive: true });
2879
+ const requestText = input.requestText.trim().length > 0 ? input.requestText : "<empty request>";
2880
+ await writeFile(
2881
+ path3.join(baseDir, "request.txt"),
2882
+ ensureTrailingNewline(requestText),
2883
+ "utf8"
2884
+ );
2885
+ const requestMetadata = {
2886
+ capturedAt: toIsoNow(),
2887
+ provider: input.provider,
2888
+ modelId: input.modelId,
2889
+ ...input.requestMetadata ? { request: sanitiseLogValue(input.requestMetadata) } : {}
2890
+ };
2891
+ await writeFile(
2892
+ path3.join(baseDir, "request.metadata.json"),
2893
+ `${JSON.stringify(requestMetadata, null, 2)}
2894
+ `,
2895
+ "utf8"
2896
+ );
2897
+ const usedNames = /* @__PURE__ */ new Set();
2898
+ for (const attachment of input.attachments ?? []) {
2899
+ let filename = normalisePathSegment(attachment.filename);
2900
+ if (!filename.includes(".")) {
2901
+ filename = `${filename}.bin`;
2902
+ }
2903
+ const ext = path3.extname(filename);
2904
+ const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
2905
+ let candidate = filename;
2906
+ let duplicateIndex = 2;
2907
+ while (usedNames.has(candidate)) {
2908
+ candidate = `${base}-${duplicateIndex.toString()}${ext}`;
2909
+ duplicateIndex += 1;
2910
+ }
2911
+ usedNames.add(candidate);
2912
+ await writeFile(path3.join(baseDir, candidate), attachment.bytes);
2913
+ }
2914
+ }).catch(() => void 0);
2915
+ this.track(chain);
2916
+ let closed = false;
2917
+ const enqueue = (operation) => {
2918
+ const next = chain.then(operation);
2919
+ const tracked = next.catch(() => void 0);
2920
+ chain = tracked;
2921
+ this.track(tracked);
2922
+ };
2923
+ return {
2924
+ appendThoughtDelta: (text) => {
2925
+ if (closed || text.length === 0) {
2926
+ return;
2927
+ }
2928
+ enqueue(async () => {
2929
+ await appendFile(thoughtsPath, text, "utf8");
2930
+ });
2931
+ },
2932
+ appendResponseDelta: (text) => {
2933
+ if (closed || text.length === 0) {
2934
+ return;
2935
+ }
2936
+ enqueue(async () => {
2937
+ await appendFile(responsePath, text, "utf8");
2938
+ });
2939
+ },
2940
+ complete: (metadata) => {
2941
+ if (closed) {
2942
+ return;
2943
+ }
2944
+ closed = true;
2945
+ enqueue(async () => {
2946
+ const payload = {
2947
+ capturedAt: toIsoNow(),
2948
+ status: "completed"
2949
+ };
2950
+ if (metadata) {
2951
+ const sanitised = sanitiseLogValue(metadata);
2952
+ if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
2953
+ Object.assign(payload, sanitised);
2954
+ } else if (sanitised !== void 0) {
2955
+ payload.metadata = sanitised;
2956
+ }
2957
+ }
2958
+ await writeFile(responseMetadataPath, `${JSON.stringify(payload, null, 2)}
2959
+ `, "utf8");
2960
+ });
2961
+ },
2962
+ fail: (error, metadata) => {
2963
+ if (closed) {
2964
+ return;
2965
+ }
2966
+ closed = true;
2967
+ enqueue(async () => {
2968
+ const payload = {
2969
+ capturedAt: toIsoNow(),
2970
+ status: "failed",
2971
+ error: toErrorMessage(error)
2972
+ };
2973
+ if (metadata) {
2974
+ const sanitised = sanitiseLogValue(metadata);
2975
+ if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
2976
+ Object.assign(payload, sanitised);
2977
+ } else if (sanitised !== void 0) {
2978
+ payload.metadata = sanitised;
2979
+ }
2980
+ }
2981
+ await writeFile(responseMetadataPath, `${JSON.stringify(payload, null, 2)}
2982
+ `, "utf8");
2983
+ });
2984
+ }
2985
+ };
2986
+ }
2987
+ async flush() {
2988
+ while (this.pending.size > 0) {
2989
+ await Promise.allSettled([...this.pending]);
2990
+ }
2991
+ if (typeof this.sink?.flush === "function") {
2992
+ try {
2993
+ await this.sink.flush();
2994
+ } catch {
2995
+ }
2996
+ }
2997
+ }
2998
+ };
2999
+ var loggingSessionStorage = new AsyncLocalStorage();
3000
+ function createAgentLoggingSession(config) {
3001
+ return new AgentLoggingSessionImpl(config);
3002
+ }
3003
+ function runWithAgentLoggingSession(session, fn) {
3004
+ if (!session) {
3005
+ return fn();
3006
+ }
3007
+ return loggingSessionStorage.run(session, fn);
3008
+ }
3009
+ function getCurrentAgentLoggingSession() {
3010
+ return loggingSessionStorage.getStore();
3011
+ }
3012
+
2646
3013
  // src/llm.ts
2647
- var toolCallContextStorage = new AsyncLocalStorage();
3014
+ var toolCallContextStorage = new AsyncLocalStorage2();
2648
3015
  function getCurrentToolCallContext() {
2649
3016
  return toolCallContextStorage.getStore() ?? null;
2650
3017
  }
@@ -2936,9 +3303,9 @@ function sanitisePartForLogging(part) {
2936
3303
  case "inlineData": {
2937
3304
  let omittedBytes;
2938
3305
  try {
2939
- omittedBytes = Buffer3.from(part.data, "base64").byteLength;
3306
+ omittedBytes = Buffer4.from(part.data, "base64").byteLength;
2940
3307
  } catch {
2941
- omittedBytes = Buffer3.byteLength(part.data, "utf8");
3308
+ omittedBytes = Buffer4.byteLength(part.data, "utf8");
2942
3309
  }
2943
3310
  return {
2944
3311
  type: "inlineData",
@@ -4001,8 +4368,8 @@ function parseOpenAiToolArguments(raw) {
4001
4368
  function formatZodIssues(issues) {
4002
4369
  const messages = [];
4003
4370
  for (const issue of issues) {
4004
- const path6 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
4005
- messages.push(`${path6}: ${issue.message}`);
4371
+ const path8 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
4372
+ messages.push(`${path8}: ${issue.message}`);
4006
4373
  }
4007
4374
  return messages.join("; ");
4008
4375
  }
@@ -4388,9 +4755,9 @@ function resolveGeminiThinkingConfig(modelId, thinkingLevel) {
4388
4755
  }
4389
4756
  function decodeInlineDataBuffer(base64) {
4390
4757
  try {
4391
- return Buffer3.from(base64, "base64");
4758
+ return Buffer4.from(base64, "base64");
4392
4759
  } catch {
4393
- return Buffer3.from(base64, "base64url");
4760
+ return Buffer4.from(base64, "base64url");
4394
4761
  }
4395
4762
  }
4396
4763
  function extractImages(content) {
@@ -4407,6 +4774,195 @@ function extractImages(content) {
4407
4774
  }
4408
4775
  return images;
4409
4776
  }
4777
+ function resolveAttachmentExtension(mimeType) {
4778
+ const normalized = (mimeType ?? "").trim().toLowerCase();
4779
+ switch (normalized) {
4780
+ case "image/jpeg":
4781
+ return "jpg";
4782
+ case "image/png":
4783
+ return "png";
4784
+ case "image/webp":
4785
+ return "webp";
4786
+ case "image/gif":
4787
+ return "gif";
4788
+ case "image/heic":
4789
+ return "heic";
4790
+ case "image/heif":
4791
+ return "heif";
4792
+ case "application/pdf":
4793
+ return "pdf";
4794
+ case "application/json":
4795
+ return "json";
4796
+ case "text/plain":
4797
+ return "txt";
4798
+ case "text/markdown":
4799
+ return "md";
4800
+ default: {
4801
+ const slashIndex = normalized.indexOf("/");
4802
+ if (slashIndex >= 0) {
4803
+ const subtype = normalized.slice(slashIndex + 1).split("+")[0] ?? "";
4804
+ const cleaned = subtype.replace(/[^a-z0-9]+/giu, "");
4805
+ if (cleaned.length > 0) {
4806
+ return cleaned;
4807
+ }
4808
+ }
4809
+ return "bin";
4810
+ }
4811
+ }
4812
+ }
4813
+ function decodeDataUrlAttachment(value, basename) {
4814
+ const trimmed = value.trim();
4815
+ if (!trimmed.toLowerCase().startsWith("data:")) {
4816
+ return null;
4817
+ }
4818
+ const commaIndex = trimmed.indexOf(",");
4819
+ if (commaIndex < 0) {
4820
+ return null;
4821
+ }
4822
+ const header = trimmed.slice(5, commaIndex);
4823
+ const payload = trimmed.slice(commaIndex + 1);
4824
+ const isBase64 = /;base64(?:;|$)/iu.test(header);
4825
+ const mimeType = (header.split(";")[0] ?? "application/octet-stream").trim().toLowerCase();
4826
+ try {
4827
+ const bytes = isBase64 ? Buffer4.from(payload, "base64") : Buffer4.from(decodeURIComponent(payload), "utf8");
4828
+ return {
4829
+ filename: `${basename}.${resolveAttachmentExtension(mimeType)}`,
4830
+ bytes
4831
+ };
4832
+ } catch {
4833
+ return null;
4834
+ }
4835
+ }
4836
+ function collectPayloadAttachments(value, options) {
4837
+ if (typeof value === "string") {
4838
+ const attachment = decodeDataUrlAttachment(
4839
+ value,
4840
+ `${options.prefix}-${options.counter.toString()}`
4841
+ );
4842
+ if (attachment) {
4843
+ options.attachments.push(attachment);
4844
+ options.counter += 1;
4845
+ }
4846
+ return;
4847
+ }
4848
+ if (!value || typeof value !== "object") {
4849
+ return;
4850
+ }
4851
+ if (options.seen.has(value)) {
4852
+ return;
4853
+ }
4854
+ options.seen.add(value);
4855
+ if (Array.isArray(value)) {
4856
+ for (const entry of value) {
4857
+ collectPayloadAttachments(entry, options);
4858
+ }
4859
+ return;
4860
+ }
4861
+ const record = value;
4862
+ const mimeType = typeof record.mimeType === "string" ? record.mimeType : void 0;
4863
+ if (typeof record.data === "string" && mimeType) {
4864
+ try {
4865
+ options.attachments.push({
4866
+ filename: `${options.prefix}-${options.counter.toString()}.${resolveAttachmentExtension(mimeType)}`,
4867
+ bytes: decodeInlineDataBuffer(record.data)
4868
+ });
4869
+ options.counter += 1;
4870
+ } catch {
4871
+ }
4872
+ }
4873
+ for (const entry of Object.values(record)) {
4874
+ collectPayloadAttachments(entry, options);
4875
+ }
4876
+ }
4877
+ function serialiseRequestPayloadForLogging(value) {
4878
+ try {
4879
+ return `${JSON.stringify(sanitiseLogValue(value), null, 2)}
4880
+ `;
4881
+ } catch {
4882
+ return `${String(value)}
4883
+ `;
4884
+ }
4885
+ }
4886
+ function startLlmCallLoggerFromContents(options) {
4887
+ const session = getCurrentAgentLoggingSession();
4888
+ if (!session) {
4889
+ return void 0;
4890
+ }
4891
+ const attachments = [];
4892
+ const sections = [];
4893
+ for (const [messageIndex, message] of options.contents.entries()) {
4894
+ sections.push(`### message_${(messageIndex + 1).toString()} role=${message.role}`);
4895
+ for (const [partIndex, part] of message.parts.entries()) {
4896
+ if (part.type === "text") {
4897
+ const channel = part.thought === true ? "thought" : "response";
4898
+ sections.push(`[text:${channel}]`);
4899
+ sections.push(part.text);
4900
+ continue;
4901
+ }
4902
+ const filename = `message-${(messageIndex + 1).toString()}-part-${(partIndex + 1).toString()}.${resolveAttachmentExtension(part.mimeType)}`;
4903
+ attachments.push({
4904
+ filename,
4905
+ bytes: decodeInlineDataBuffer(part.data)
4906
+ });
4907
+ sections.push(
4908
+ `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
4909
+ );
4910
+ }
4911
+ sections.push("");
4912
+ }
4913
+ return session.startLlmCall({
4914
+ provider: options.provider,
4915
+ modelId: options.request.model,
4916
+ requestText: sections.join("\n").trim(),
4917
+ requestMetadata: {
4918
+ model: options.request.model,
4919
+ input: options.contents.map((content) => ({
4920
+ role: content.role,
4921
+ parts: content.parts.map((part) => sanitisePartForLogging(part))
4922
+ })),
4923
+ ...options.request.instructions ? {
4924
+ instructions: options.request.instructions
4925
+ } : {},
4926
+ ...options.request.tools ? { tools: options.request.tools } : {},
4927
+ ...options.request.responseMimeType ? {
4928
+ responseMimeType: options.request.responseMimeType
4929
+ } : {},
4930
+ ...options.request.responseJsonSchema ? {
4931
+ responseJsonSchema: sanitiseLogValue(options.request.responseJsonSchema)
4932
+ } : {},
4933
+ ...options.request.responseModalities ? { responseModalities: options.request.responseModalities } : {},
4934
+ ...options.request.imageAspectRatio ? { imageAspectRatio: options.request.imageAspectRatio } : {},
4935
+ ...options.request.imageSize ? { imageSize: options.request.imageSize } : {},
4936
+ ...options.request.thinkingLevel ? { thinkingLevel: options.request.thinkingLevel } : {},
4937
+ ...options.request.openAiTextFormat ? { openAiTextFormat: sanitiseLogValue(options.request.openAiTextFormat) } : {},
4938
+ ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
4939
+ },
4940
+ attachments
4941
+ });
4942
+ }
4943
+ function startLlmCallLoggerFromPayload(options) {
4944
+ const session = getCurrentAgentLoggingSession();
4945
+ if (!session) {
4946
+ return void 0;
4947
+ }
4948
+ const attachments = [];
4949
+ collectPayloadAttachments(options.requestPayload, {
4950
+ prefix: `step-${options.step.toString()}`,
4951
+ attachments,
4952
+ seen: /* @__PURE__ */ new WeakSet(),
4953
+ counter: 1
4954
+ });
4955
+ return session.startLlmCall({
4956
+ provider: options.provider,
4957
+ modelId: options.modelId,
4958
+ requestText: serialiseRequestPayloadForLogging(options.requestPayload),
4959
+ requestMetadata: {
4960
+ step: options.step,
4961
+ ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
4962
+ },
4963
+ attachments
4964
+ });
4965
+ }
4410
4966
  async function runTextCall(params) {
4411
4967
  const { request, queue, abortController } = params;
4412
4968
  const providerInfo = resolveProvider(request.model);
@@ -4416,6 +4972,11 @@ async function runTextCall(params) {
4416
4972
  if (contents.length === 0) {
4417
4973
  throw new Error("LLM call received an empty prompt.");
4418
4974
  }
4975
+ const callLogger = startLlmCallLoggerFromContents({
4976
+ provider,
4977
+ request,
4978
+ contents
4979
+ });
4419
4980
  let modelVersion = request.model;
4420
4981
  let blocked = false;
4421
4982
  let grounding;
@@ -4423,12 +4984,17 @@ async function runTextCall(params) {
4423
4984
  let responseRole;
4424
4985
  let latestUsage;
4425
4986
  let responseImages = 0;
4426
- const pushDelta = (channel, text2) => {
4427
- if (!text2) {
4987
+ const pushDelta = (channel, text) => {
4988
+ if (!text) {
4428
4989
  return;
4429
4990
  }
4430
- responseParts.push({ type: "text", text: text2, ...channel === "thought" ? { thought: true } : {} });
4431
- queue.push({ type: "delta", channel, text: text2 });
4991
+ responseParts.push({ type: "text", text, ...channel === "thought" ? { thought: true } : {} });
4992
+ if (channel === "thought") {
4993
+ callLogger?.appendThoughtDelta(text);
4994
+ } else {
4995
+ callLogger?.appendResponseDelta(text);
4996
+ }
4997
+ queue.push({ type: "delta", channel, text });
4432
4998
  };
4433
4999
  const pushInline = (data, mimeType) => {
4434
5000
  if (!data) {
@@ -4455,263 +5021,294 @@ async function runTextCall(params) {
4455
5021
  return abortController.signal;
4456
5022
  };
4457
5023
  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;
5024
+ try {
5025
+ if (provider === "openai") {
5026
+ const openAiInput = toOpenAiInput(contents);
5027
+ const openAiTools = toOpenAiTools(request.tools);
5028
+ const reasoningEffort = resolveOpenAiReasoningEffort(modelForProvider, request.thinkingLevel);
5029
+ const openAiTextConfig = {
5030
+ format: request.openAiTextFormat ?? { type: "text" },
5031
+ verbosity: resolveOpenAiVerbosity(modelForProvider)
5032
+ };
5033
+ const reasoning = {
5034
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5035
+ summary: "detailed"
5036
+ };
5037
+ await runOpenAiCall(async (client) => {
5038
+ const stream = client.responses.stream(
5039
+ {
5040
+ model: modelForProvider,
5041
+ input: openAiInput,
5042
+ reasoning,
5043
+ text: openAiTextConfig,
5044
+ ...openAiTools ? { tools: openAiTools } : {},
5045
+ include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
5046
+ },
5047
+ { signal }
5048
+ );
5049
+ for await (const event of stream) {
5050
+ switch (event.type) {
5051
+ case "response.output_text.delta": {
5052
+ const delta = event.delta ?? "";
5053
+ pushDelta("response", typeof delta === "string" ? delta : "");
5054
+ break;
5055
+ }
5056
+ case "response.reasoning_summary_text.delta": {
5057
+ const delta = event.delta ?? "";
5058
+ pushDelta("thought", typeof delta === "string" ? delta : "");
5059
+ break;
5060
+ }
5061
+ case "response.refusal.delta": {
5062
+ blocked = true;
5063
+ queue.push({ type: "blocked" });
5064
+ break;
5065
+ }
5066
+ default:
5067
+ break;
4488
5068
  }
4489
- case "response.reasoning_summary_text.delta": {
4490
- const delta = event.delta ?? "";
4491
- pushDelta("thought", typeof delta === "string" ? delta : "");
4492
- break;
5069
+ }
5070
+ const finalResponse = await stream.finalResponse();
5071
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5072
+ queue.push({ type: "model", modelVersion });
5073
+ if (finalResponse.error) {
5074
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5075
+ throw new Error(message);
5076
+ }
5077
+ if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
5078
+ const detail = finalResponse.incomplete_details?.reason;
5079
+ throw new Error(
5080
+ `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
5081
+ );
5082
+ }
5083
+ latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
5084
+ if (responseParts.length === 0) {
5085
+ const fallback = extractOpenAiResponseParts(finalResponse);
5086
+ blocked = blocked || fallback.blocked;
5087
+ for (const part of fallback.parts) {
5088
+ if (part.type === "text") {
5089
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
5090
+ } else {
5091
+ pushInline(part.data, part.mimeType);
5092
+ }
4493
5093
  }
4494
- case "response.refusal.delta": {
4495
- blocked = true;
4496
- queue.push({ type: "blocked" });
4497
- break;
5094
+ }
5095
+ }, modelForProvider);
5096
+ } else if (provider === "chatgpt") {
5097
+ const chatGptInput = toChatGptInput(contents);
5098
+ const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
5099
+ const openAiTools = toOpenAiTools(request.tools);
5100
+ const requestPayload = {
5101
+ model: modelForProvider,
5102
+ store: false,
5103
+ stream: true,
5104
+ instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
5105
+ input: chatGptInput.input,
5106
+ include: ["reasoning.encrypted_content"],
5107
+ reasoning: {
5108
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5109
+ summary: "detailed"
5110
+ },
5111
+ text: {
5112
+ format: request.openAiTextFormat ?? { type: "text" },
5113
+ verbosity: resolveOpenAiVerbosity(request.model)
5114
+ },
5115
+ ...openAiTools ? { tools: openAiTools } : {}
5116
+ };
5117
+ let sawResponseDelta = false;
5118
+ let sawThoughtDelta = false;
5119
+ const result = await collectChatGptCodexResponseWithRetry({
5120
+ request: requestPayload,
5121
+ signal,
5122
+ onDelta: (delta) => {
5123
+ if (delta.thoughtDelta) {
5124
+ sawThoughtDelta = true;
5125
+ pushDelta("thought", delta.thoughtDelta);
5126
+ }
5127
+ if (delta.textDelta) {
5128
+ sawResponseDelta = true;
5129
+ pushDelta("response", delta.textDelta);
4498
5130
  }
4499
- default:
4500
- break;
4501
5131
  }
5132
+ });
5133
+ blocked = blocked || result.blocked;
5134
+ if (blocked) {
5135
+ queue.push({ type: "blocked" });
4502
5136
  }
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);
5137
+ if (result.model) {
5138
+ modelVersion = `chatgpt-${result.model}`;
5139
+ queue.push({ type: "model", modelVersion });
4509
5140
  }
4510
- if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
4511
- const detail = finalResponse.incomplete_details?.reason;
5141
+ latestUsage = extractChatGptUsageTokens(result.usage);
5142
+ const fallbackText = typeof result.text === "string" ? result.text : "";
5143
+ const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
5144
+ if (!sawThoughtDelta && fallbackThoughts.length > 0) {
5145
+ pushDelta("thought", fallbackThoughts);
5146
+ }
5147
+ if (!sawResponseDelta && fallbackText.length > 0) {
5148
+ pushDelta("response", fallbackText);
5149
+ }
5150
+ } else if (provider === "fireworks") {
5151
+ if (request.tools && request.tools.length > 0) {
4512
5152
  throw new Error(
4513
- `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
5153
+ "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
4514
5154
  );
4515
5155
  }
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);
5156
+ const fireworksMessages = toFireworksMessages(contents, {
5157
+ responseMimeType: request.responseMimeType,
5158
+ responseJsonSchema: request.responseJsonSchema
5159
+ });
5160
+ await runFireworksCall(async (client) => {
5161
+ const responseFormat = request.responseJsonSchema ? {
5162
+ type: "json_schema",
5163
+ json_schema: {
5164
+ name: "llm-response",
5165
+ schema: request.responseJsonSchema
4525
5166
  }
4526
- }
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);
4556
- }
4557
- if (delta.textDelta) {
4558
- sawResponseDelta = true;
4559
- pushDelta("response", delta.textDelta);
4560
- }
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
4596
- }
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 } : {}
4603
- },
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;
4611
- queue.push({ type: "blocked" });
4612
- }
4613
- const textOutput = extractFireworksMessageText(
4614
- choice?.message
4615
- );
4616
- if (textOutput.length > 0) {
4617
- pushDelta("response", textOutput);
4618
- }
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
4646
- });
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) {
5167
+ } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
5168
+ const response = await client.chat.completions.create(
5169
+ {
5170
+ model: modelForProvider,
5171
+ messages: fireworksMessages,
5172
+ ...responseFormat ? { response_format: responseFormat } : {}
5173
+ },
5174
+ { signal }
5175
+ );
5176
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
5177
+ queue.push({ type: "model", modelVersion });
5178
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5179
+ if (choice?.finish_reason === "content_filter") {
4654
5180
  blocked = true;
4655
5181
  queue.push({ type: "blocked" });
4656
5182
  }
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" });
5183
+ const textOutput = extractFireworksMessageText(
5184
+ choice?.message
5185
+ );
5186
+ if (textOutput.length > 0) {
5187
+ pushDelta("response", textOutput);
4666
5188
  }
4667
- for (const candidate of candidates) {
4668
- const candidateContent = candidate.content;
4669
- if (!candidateContent) {
4670
- continue;
5189
+ latestUsage = extractFireworksUsageTokens(response.usage);
5190
+ }, modelForProvider);
5191
+ } else {
5192
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
5193
+ const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
5194
+ const config = {
5195
+ maxOutputTokens: 32e3,
5196
+ ...thinkingConfig ? { thinkingConfig } : {},
5197
+ ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
5198
+ ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
5199
+ ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
5200
+ ...request.imageAspectRatio || request.imageSize ? {
5201
+ imageConfig: {
5202
+ ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
5203
+ ...request.imageSize ? { imageSize: request.imageSize } : {}
4671
5204
  }
4672
- if (candidate.groundingMetadata) {
4673
- latestGrounding = candidate.groundingMetadata;
5205
+ } : {}
5206
+ };
5207
+ const geminiTools = toGeminiTools(request.tools);
5208
+ if (geminiTools) {
5209
+ config.tools = geminiTools;
5210
+ }
5211
+ await runGeminiCall(async (client) => {
5212
+ const stream = await client.models.generateContentStream({
5213
+ model: modelForProvider,
5214
+ contents: geminiContents,
5215
+ config
5216
+ });
5217
+ let latestGrounding;
5218
+ for await (const chunk of stream) {
5219
+ if (chunk.modelVersion) {
5220
+ modelVersion = chunk.modelVersion;
5221
+ queue.push({ type: "model", modelVersion });
4674
5222
  }
4675
- const content2 = convertGeminiContentToLlmContent(candidateContent);
4676
- if (!responseRole) {
4677
- responseRole = content2.role;
5223
+ if (chunk.promptFeedback?.blockReason) {
5224
+ blocked = true;
5225
+ queue.push({ type: "blocked" });
4678
5226
  }
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);
5227
+ latestUsage = mergeTokenUpdates(
5228
+ latestUsage,
5229
+ extractGeminiUsageTokens(chunk.usageMetadata)
5230
+ );
5231
+ const candidates = chunk.candidates;
5232
+ if (!candidates || candidates.length === 0) {
5233
+ continue;
5234
+ }
5235
+ const primary = candidates[0];
5236
+ if (primary && isModerationFinish(primary.finishReason)) {
5237
+ blocked = true;
5238
+ queue.push({ type: "blocked" });
5239
+ }
5240
+ for (const candidate of candidates) {
5241
+ const candidateContent = candidate.content;
5242
+ if (!candidateContent) {
5243
+ continue;
5244
+ }
5245
+ if (candidate.groundingMetadata) {
5246
+ latestGrounding = candidate.groundingMetadata;
5247
+ }
5248
+ const content2 = convertGeminiContentToLlmContent(candidateContent);
5249
+ if (!responseRole) {
5250
+ responseRole = content2.role;
5251
+ }
5252
+ for (const part of content2.parts) {
5253
+ if (part.type === "text") {
5254
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
5255
+ } else {
5256
+ pushInline(part.data, part.mimeType);
5257
+ }
4684
5258
  }
4685
5259
  }
4686
5260
  }
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 });
5261
+ grounding = latestGrounding;
5262
+ }, modelForProvider);
5263
+ }
5264
+ const mergedParts = mergeConsecutiveTextParts(responseParts);
5265
+ const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
5266
+ const { text, thoughts } = extractTextByChannel(content);
5267
+ const costUsd = estimateCallCostUsd({
5268
+ modelId: modelVersion,
5269
+ tokens: latestUsage,
5270
+ responseImages,
5271
+ imageSize: request.imageSize
5272
+ });
5273
+ if (latestUsage) {
5274
+ queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5275
+ }
5276
+ callLogger?.complete({
5277
+ provider,
5278
+ model: request.model,
5279
+ modelVersion,
5280
+ blocked,
5281
+ costUsd,
5282
+ usage: latestUsage,
5283
+ grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5284
+ responseChars: text.length,
5285
+ thoughtChars: thoughts.length,
5286
+ responseImages
5287
+ });
5288
+ return {
5289
+ provider,
5290
+ model: request.model,
5291
+ modelVersion,
5292
+ content,
5293
+ text,
5294
+ thoughts,
5295
+ blocked,
5296
+ usage: latestUsage,
5297
+ costUsd,
5298
+ grounding
5299
+ };
5300
+ } catch (error) {
5301
+ callLogger?.fail(error, {
5302
+ provider,
5303
+ model: request.model,
5304
+ modelVersion,
5305
+ blocked,
5306
+ usage: latestUsage,
5307
+ partialResponseParts: responseParts.length,
5308
+ responseImages
5309
+ });
5310
+ throw error;
4702
5311
  }
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
5312
  }
4716
5313
  function streamText(request) {
4717
5314
  const queue = createAsyncQueue();
@@ -5174,6 +5771,23 @@ async function runToolLoop(request) {
5174
5771
  let modelVersion = request.model;
5175
5772
  let usageTokens;
5176
5773
  let thoughtDeltaEmitted = false;
5774
+ let blocked = false;
5775
+ const stepRequestPayload = {
5776
+ model: providerInfo.model,
5777
+ input,
5778
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5779
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5780
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5781
+ reasoning,
5782
+ text: textConfig,
5783
+ include: ["reasoning.encrypted_content"]
5784
+ };
5785
+ const stepCallLogger = startLlmCallLoggerFromPayload({
5786
+ provider: "openai",
5787
+ modelId: request.model,
5788
+ requestPayload: stepRequestPayload,
5789
+ step: turn
5790
+ });
5177
5791
  const emitEvent = (ev) => {
5178
5792
  onEvent?.(ev);
5179
5793
  };
@@ -5182,226 +5796,276 @@ async function runToolLoop(request) {
5182
5796
  firstModelEventAtMs = Date.now();
5183
5797
  }
5184
5798
  };
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;
5799
+ try {
5800
+ const finalResponse = await runOpenAiCall(
5801
+ async (client) => {
5802
+ const stream = client.responses.stream(
5803
+ {
5804
+ model: providerInfo.model,
5805
+ input,
5806
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5807
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5808
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5809
+ reasoning,
5810
+ text: textConfig,
5811
+ include: ["reasoning.encrypted_content"]
5812
+ },
5813
+ { signal: abortController.signal }
5814
+ );
5815
+ for await (const event of stream) {
5816
+ markFirstModelEvent();
5817
+ switch (event.type) {
5818
+ case "response.output_text.delta": {
5819
+ const text = typeof event.delta === "string" ? event.delta : "";
5820
+ if (text.length > 0) {
5821
+ stepCallLogger?.appendResponseDelta(text);
5822
+ }
5823
+ emitEvent({
5824
+ type: "delta",
5825
+ channel: "response",
5826
+ text
5827
+ });
5828
+ break;
5829
+ }
5830
+ case "response.reasoning_summary_text.delta": {
5831
+ thoughtDeltaEmitted = true;
5832
+ const text = typeof event.delta === "string" ? event.delta : "";
5833
+ if (text.length > 0) {
5834
+ stepCallLogger?.appendThoughtDelta(text);
5835
+ }
5836
+ emitEvent({
5837
+ type: "delta",
5838
+ channel: "thought",
5839
+ text
5840
+ });
5841
+ break;
5842
+ }
5843
+ case "response.refusal.delta":
5844
+ blocked = true;
5845
+ emitEvent({ type: "blocked" });
5846
+ break;
5847
+ default:
5848
+ break;
5849
+ }
5850
+ }
5851
+ return await stream.finalResponse();
5852
+ },
5853
+ providerInfo.model,
5854
+ {
5855
+ onSettled: (metrics) => {
5856
+ schedulerMetrics = metrics;
5223
5857
  }
5224
5858
  }
5225
- return await stream.finalResponse();
5226
- },
5227
- providerInfo.model,
5228
- {
5229
- onSettled: (metrics) => {
5230
- schedulerMetrics = metrics;
5859
+ );
5860
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5861
+ emitEvent({ type: "model", modelVersion });
5862
+ if (finalResponse.error) {
5863
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5864
+ throw new Error(message);
5865
+ }
5866
+ usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
5867
+ const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
5868
+ const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
5869
+ if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
5870
+ stepCallLogger?.appendThoughtDelta(reasoningSummary);
5871
+ emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
5872
+ }
5873
+ const modelCompletedAtMs = Date.now();
5874
+ const stepCostUsd = estimateCallCostUsd({
5875
+ modelId: modelVersion,
5876
+ tokens: usageTokens,
5877
+ responseImages: 0
5878
+ });
5879
+ totalCostUsd += stepCostUsd;
5880
+ if (usageTokens) {
5881
+ emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
5882
+ }
5883
+ const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
5884
+ const stepToolCalls = [];
5885
+ if (responseToolCalls.length === 0) {
5886
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5887
+ const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2) : [];
5888
+ finalText = responseText;
5889
+ finalThoughts = reasoningSummary;
5890
+ const stepCompletedAtMs2 = Date.now();
5891
+ const timing2 = buildStepTiming({
5892
+ stepStartedAtMs,
5893
+ stepCompletedAtMs: stepCompletedAtMs2,
5894
+ modelCompletedAtMs,
5895
+ firstModelEventAtMs,
5896
+ schedulerMetrics,
5897
+ toolExecutionMs: 0,
5898
+ waitToolMs: 0
5899
+ });
5900
+ steps.push({
5901
+ step: steps.length + 1,
5902
+ modelVersion,
5903
+ text: responseText || void 0,
5904
+ thoughts: reasoningSummary || void 0,
5905
+ toolCalls: [],
5906
+ usage: usageTokens,
5907
+ costUsd: stepCostUsd,
5908
+ timing: timing2
5909
+ });
5910
+ stepCallLogger?.complete({
5911
+ provider: "openai",
5912
+ model: request.model,
5913
+ modelVersion,
5914
+ step: turn,
5915
+ usage: usageTokens,
5916
+ costUsd: stepCostUsd,
5917
+ blocked,
5918
+ responseChars: responseText.length,
5919
+ thoughtChars: reasoningSummary.length,
5920
+ toolCalls: 0,
5921
+ finalStep: steeringItems2.length === 0
5922
+ });
5923
+ if (steeringItems2.length === 0) {
5924
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5231
5925
  }
5926
+ previousResponseId = finalResponse.id;
5927
+ input = steeringItems2;
5928
+ continue;
5232
5929
  }
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({
5930
+ const callInputs = responseToolCalls.map((call, index) => {
5931
+ const toolIndex = index + 1;
5932
+ const toolId = buildToolLogId(turn, toolIndex);
5933
+ const toolName = call.name;
5934
+ if (call.kind === "custom") {
5935
+ return {
5936
+ call,
5937
+ toolName,
5938
+ value: call.input,
5939
+ parseError: void 0,
5940
+ toolId,
5941
+ turn,
5942
+ toolIndex
5943
+ };
5944
+ }
5945
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5946
+ return { call, toolName, value, parseError, toolId, turn, toolIndex };
5947
+ });
5948
+ for (const entry of callInputs) {
5949
+ emitEvent({
5950
+ type: "tool_call",
5951
+ phase: "started",
5952
+ turn: entry.turn,
5953
+ toolIndex: entry.toolIndex,
5954
+ toolName: entry.toolName,
5955
+ toolId: entry.toolId,
5956
+ callKind: entry.call.kind,
5957
+ callId: entry.call.call_id,
5958
+ input: entry.value
5959
+ });
5960
+ }
5961
+ const callResults = await Promise.all(
5962
+ callInputs.map(async (entry) => {
5963
+ return await toolCallContextStorage.run(
5964
+ {
5965
+ toolName: entry.toolName,
5966
+ toolId: entry.toolId,
5967
+ turn: entry.turn,
5968
+ toolIndex: entry.toolIndex
5969
+ },
5970
+ async () => {
5971
+ const { result, outputPayload } = await executeToolCall({
5972
+ callKind: entry.call.kind,
5973
+ toolName: entry.toolName,
5974
+ tool: request.tools[entry.toolName],
5975
+ rawInput: entry.value,
5976
+ parseError: entry.parseError
5977
+ });
5978
+ return { entry, result, outputPayload };
5979
+ }
5980
+ );
5981
+ })
5982
+ );
5983
+ const toolOutputs = [];
5984
+ let toolExecutionMs = 0;
5985
+ let waitToolMs = 0;
5986
+ for (const { entry, result, outputPayload } of callResults) {
5987
+ stepToolCalls.push({ ...result, callId: entry.call.call_id });
5988
+ const callDurationMs = toToolResultDuration(result);
5989
+ toolExecutionMs += callDurationMs;
5990
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5991
+ waitToolMs += callDurationMs;
5992
+ }
5993
+ emitEvent({
5994
+ type: "tool_call",
5995
+ phase: "completed",
5996
+ turn: entry.turn,
5997
+ toolIndex: entry.toolIndex,
5998
+ toolName: entry.toolName,
5999
+ toolId: entry.toolId,
6000
+ callKind: entry.call.kind,
6001
+ callId: entry.call.call_id,
6002
+ input: entry.value,
6003
+ output: result.output,
6004
+ error: result.error,
6005
+ durationMs: result.durationMs
6006
+ });
6007
+ if (entry.call.kind === "custom") {
6008
+ toolOutputs.push({
6009
+ type: "custom_tool_call_output",
6010
+ call_id: entry.call.call_id,
6011
+ output: toOpenAiToolOutput(outputPayload)
6012
+ });
6013
+ } else {
6014
+ toolOutputs.push({
6015
+ type: "function_call_output",
6016
+ call_id: entry.call.call_id,
6017
+ output: toOpenAiToolOutput(outputPayload)
6018
+ });
6019
+ }
6020
+ }
6021
+ const stepCompletedAtMs = Date.now();
6022
+ const timing = buildStepTiming({
5265
6023
  stepStartedAtMs,
5266
- stepCompletedAtMs: stepCompletedAtMs2,
6024
+ stepCompletedAtMs,
5267
6025
  modelCompletedAtMs,
5268
6026
  firstModelEventAtMs,
5269
6027
  schedulerMetrics,
5270
- toolExecutionMs: 0,
5271
- waitToolMs: 0
6028
+ toolExecutionMs,
6029
+ waitToolMs
5272
6030
  });
5273
6031
  steps.push({
5274
6032
  step: steps.length + 1,
5275
6033
  modelVersion,
5276
6034
  text: responseText || void 0,
5277
6035
  thoughts: reasoningSummary || void 0,
5278
- toolCalls: [],
6036
+ toolCalls: stepToolCalls,
5279
6037
  usage: usageTokens,
5280
6038
  costUsd: stepCostUsd,
5281
- timing: timing2
6039
+ timing
5282
6040
  });
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
6041
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6042
+ const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
6043
+ stepCallLogger?.complete({
6044
+ provider: "openai",
6045
+ model: request.model,
6046
+ modelVersion,
6047
+ step: turn,
6048
+ usage: usageTokens,
6049
+ costUsd: stepCostUsd,
6050
+ blocked,
6051
+ responseChars: responseText.length,
6052
+ thoughtChars: reasoningSummary.length,
6053
+ toolCalls: stepToolCalls.length,
6054
+ finalStep: false
5319
6055
  });
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
6056
+ previousResponseId = finalResponse.id;
6057
+ input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
6058
+ } catch (error) {
6059
+ stepCallLogger?.fail(error, {
6060
+ provider: "openai",
6061
+ model: request.model,
6062
+ modelVersion,
6063
+ step: turn,
6064
+ usage: usageTokens,
6065
+ blocked
5366
6066
  });
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
- }
6067
+ throw error;
5380
6068
  }
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
6069
  }
5406
6070
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5407
6071
  }
@@ -5419,242 +6083,655 @@ async function runToolLoop(request) {
5419
6083
  const stepStartedAtMs = Date.now();
5420
6084
  let firstModelEventAtMs;
5421
6085
  let thoughtDeltaEmitted = false;
6086
+ let sawResponseDelta = false;
6087
+ let modelVersion = request.model;
6088
+ let usageTokens;
6089
+ let responseText = "";
6090
+ let reasoningSummaryText = "";
5422
6091
  const markFirstModelEvent = () => {
5423
6092
  if (firstModelEventAtMs === void 0) {
5424
6093
  firstModelEventAtMs = Date.now();
5425
6094
  }
5426
6095
  };
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) }
6096
+ const stepRequestPayload = {
6097
+ model: providerInfo.model,
6098
+ store: false,
6099
+ stream: true,
6100
+ instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
6101
+ input,
6102
+ prompt_cache_key: promptCacheKey,
6103
+ include: ["reasoning.encrypted_content"],
6104
+ tools: openAiTools,
6105
+ tool_choice: "auto",
6106
+ parallel_tool_calls: true,
6107
+ reasoning: {
6108
+ effort: toOpenAiReasoningEffort(reasoningEffort),
6109
+ summary: "detailed"
5445
6110
  },
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 });
6111
+ text: { verbosity: resolveOpenAiVerbosity(request.model) }
6112
+ };
6113
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6114
+ provider: "chatgpt",
6115
+ modelId: request.model,
6116
+ requestPayload: stepRequestPayload,
6117
+ step: turn
6118
+ });
6119
+ try {
6120
+ const response = await collectChatGptCodexResponseWithRetry({
6121
+ sessionId: conversationId,
6122
+ request: stepRequestPayload,
6123
+ signal: request.signal,
6124
+ onDelta: (delta) => {
6125
+ if (delta.thoughtDelta) {
6126
+ markFirstModelEvent();
6127
+ thoughtDeltaEmitted = true;
6128
+ stepCallLogger?.appendThoughtDelta(delta.thoughtDelta);
6129
+ request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
6130
+ }
6131
+ if (delta.textDelta) {
6132
+ markFirstModelEvent();
6133
+ sawResponseDelta = true;
6134
+ stepCallLogger?.appendResponseDelta(delta.textDelta);
6135
+ request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
6136
+ }
5452
6137
  }
5453
- if (delta.textDelta) {
5454
- markFirstModelEvent();
5455
- request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
6138
+ });
6139
+ const modelCompletedAtMs = Date.now();
6140
+ modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
6141
+ usageTokens = extractChatGptUsageTokens(response.usage);
6142
+ const stepCostUsd = estimateCallCostUsd({
6143
+ modelId: modelVersion,
6144
+ tokens: usageTokens,
6145
+ responseImages: 0
6146
+ });
6147
+ totalCostUsd += stepCostUsd;
6148
+ responseText = (response.text ?? "").trim();
6149
+ reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
6150
+ if (!thoughtDeltaEmitted && reasoningSummaryText.length > 0) {
6151
+ stepCallLogger?.appendThoughtDelta(reasoningSummaryText);
6152
+ request.onEvent?.({ type: "delta", channel: "thought", text: reasoningSummaryText });
6153
+ }
6154
+ if (!sawResponseDelta && responseText.length > 0) {
6155
+ stepCallLogger?.appendResponseDelta(responseText);
6156
+ }
6157
+ const responseToolCalls = response.toolCalls ?? [];
6158
+ if (responseToolCalls.length === 0) {
6159
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6160
+ const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
6161
+ finalText = responseText;
6162
+ finalThoughts = reasoningSummaryText;
6163
+ const stepCompletedAtMs2 = Date.now();
6164
+ const timing2 = buildStepTiming({
6165
+ stepStartedAtMs,
6166
+ stepCompletedAtMs: stepCompletedAtMs2,
6167
+ modelCompletedAtMs,
6168
+ firstModelEventAtMs,
6169
+ toolExecutionMs: 0,
6170
+ waitToolMs: 0
6171
+ });
6172
+ steps.push({
6173
+ step: steps.length + 1,
6174
+ modelVersion,
6175
+ text: responseText || void 0,
6176
+ thoughts: reasoningSummaryText || void 0,
6177
+ toolCalls: [],
6178
+ usage: usageTokens,
6179
+ costUsd: stepCostUsd,
6180
+ timing: timing2
6181
+ });
6182
+ stepCallLogger?.complete({
6183
+ provider: "chatgpt",
6184
+ model: request.model,
6185
+ modelVersion,
6186
+ step: turn,
6187
+ usage: usageTokens,
6188
+ costUsd: stepCostUsd,
6189
+ responseChars: responseText.length,
6190
+ thoughtChars: reasoningSummaryText.length,
6191
+ toolCalls: 0,
6192
+ finalStep: steeringItems2.length === 0
6193
+ });
6194
+ if (steeringItems2.length === 0) {
6195
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5456
6196
  }
6197
+ const assistantItem = toChatGptAssistantMessage(responseText);
6198
+ input = assistantItem ? input.concat(assistantItem, steeringItems2) : input.concat(steeringItems2);
6199
+ continue;
5457
6200
  }
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({
6201
+ const toolCalls = [];
6202
+ const toolOutputs = [];
6203
+ const callInputs = responseToolCalls.map((call, index) => {
6204
+ const toolIndex = index + 1;
6205
+ const toolId = buildToolLogId(turn, toolIndex);
6206
+ const toolName = call.name;
6207
+ const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
6208
+ const ids = normalizeChatGptToolIds({
6209
+ callKind: call.kind,
6210
+ callId: call.callId,
6211
+ itemId: call.id
6212
+ });
6213
+ return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
6214
+ });
6215
+ for (const entry of callInputs) {
6216
+ request.onEvent?.({
6217
+ type: "tool_call",
6218
+ phase: "started",
6219
+ turn: entry.turn,
6220
+ toolIndex: entry.toolIndex,
6221
+ toolName: entry.toolName,
6222
+ toolId: entry.toolId,
6223
+ callKind: entry.call.kind,
6224
+ callId: entry.ids.callId,
6225
+ input: entry.value
6226
+ });
6227
+ }
6228
+ const callResults = await Promise.all(
6229
+ callInputs.map(async (entry) => {
6230
+ return await toolCallContextStorage.run(
6231
+ {
6232
+ toolName: entry.toolName,
6233
+ toolId: entry.toolId,
6234
+ turn: entry.turn,
6235
+ toolIndex: entry.toolIndex
6236
+ },
6237
+ async () => {
6238
+ const { result, outputPayload } = await executeToolCall({
6239
+ callKind: entry.call.kind,
6240
+ toolName: entry.toolName,
6241
+ tool: request.tools[entry.toolName],
6242
+ rawInput: entry.value,
6243
+ parseError: entry.parseError
6244
+ });
6245
+ return { entry, result, outputPayload };
6246
+ }
6247
+ );
6248
+ })
6249
+ );
6250
+ let toolExecutionMs = 0;
6251
+ let waitToolMs = 0;
6252
+ for (const { entry, result, outputPayload } of callResults) {
6253
+ toolCalls.push({ ...result, callId: entry.ids.callId });
6254
+ const callDurationMs = toToolResultDuration(result);
6255
+ toolExecutionMs += callDurationMs;
6256
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6257
+ waitToolMs += callDurationMs;
6258
+ }
6259
+ request.onEvent?.({
6260
+ type: "tool_call",
6261
+ phase: "completed",
6262
+ turn: entry.turn,
6263
+ toolIndex: entry.toolIndex,
6264
+ toolName: entry.toolName,
6265
+ toolId: entry.toolId,
6266
+ callKind: entry.call.kind,
6267
+ callId: entry.ids.callId,
6268
+ input: entry.value,
6269
+ output: result.output,
6270
+ error: result.error,
6271
+ durationMs: result.durationMs
6272
+ });
6273
+ if (entry.call.kind === "custom") {
6274
+ toolOutputs.push({
6275
+ type: "custom_tool_call",
6276
+ id: entry.ids.itemId,
6277
+ call_id: entry.ids.callId,
6278
+ name: entry.toolName,
6279
+ input: entry.call.input,
6280
+ status: "completed"
6281
+ });
6282
+ toolOutputs.push({
6283
+ type: "custom_tool_call_output",
6284
+ call_id: entry.ids.callId,
6285
+ output: toOpenAiToolOutput(outputPayload)
6286
+ });
6287
+ } else {
6288
+ toolOutputs.push({
6289
+ type: "function_call",
6290
+ id: entry.ids.itemId,
6291
+ call_id: entry.ids.callId,
6292
+ name: entry.toolName,
6293
+ arguments: entry.call.arguments,
6294
+ status: "completed"
6295
+ });
6296
+ toolOutputs.push({
6297
+ type: "function_call_output",
6298
+ call_id: entry.ids.callId,
6299
+ output: toOpenAiToolOutput(outputPayload)
6300
+ });
6301
+ }
6302
+ }
6303
+ const stepCompletedAtMs = Date.now();
6304
+ const timing = buildStepTiming({
5481
6305
  stepStartedAtMs,
5482
- stepCompletedAtMs: stepCompletedAtMs2,
6306
+ stepCompletedAtMs,
5483
6307
  modelCompletedAtMs,
5484
6308
  firstModelEventAtMs,
5485
- toolExecutionMs: 0,
5486
- waitToolMs: 0
6309
+ toolExecutionMs,
6310
+ waitToolMs
5487
6311
  });
5488
6312
  steps.push({
5489
6313
  step: steps.length + 1,
5490
6314
  modelVersion,
5491
6315
  text: responseText || void 0,
5492
6316
  thoughts: reasoningSummaryText || void 0,
5493
- toolCalls: [],
6317
+ toolCalls,
5494
6318
  usage: usageTokens,
5495
6319
  costUsd: stepCostUsd,
5496
- timing: timing2
6320
+ timing
5497
6321
  });
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
6322
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6323
+ const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
6324
+ stepCallLogger?.complete({
6325
+ provider: "chatgpt",
6326
+ model: request.model,
6327
+ modelVersion,
6328
+ step: turn,
6329
+ usage: usageTokens,
6330
+ costUsd: stepCostUsd,
6331
+ responseChars: responseText.length,
6332
+ thoughtChars: reasoningSummaryText.length,
6333
+ toolCalls: toolCalls.length,
6334
+ finalStep: false
5516
6335
  });
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
6336
+ input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
6337
+ } catch (error) {
6338
+ stepCallLogger?.fail(error, {
6339
+ provider: "chatgpt",
6340
+ model: request.model,
6341
+ modelVersion,
6342
+ step: turn,
6343
+ usage: usageTokens
5530
6344
  });
6345
+ throw error;
5531
6346
  }
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
- })
6347
+ }
6348
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
6349
+ }
6350
+ if (providerInfo.provider === "fireworks") {
6351
+ if (request.modelTools && request.modelTools.length > 0) {
6352
+ throw new Error(
6353
+ "Fireworks provider does not support provider-native modelTools in runToolLoop."
5553
6354
  );
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;
6355
+ }
6356
+ const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
6357
+ const messages = toFireworksMessages(contents);
6358
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
6359
+ const turn = stepIndex + 1;
6360
+ const stepStartedAtMs = Date.now();
6361
+ let schedulerMetrics;
6362
+ let modelVersion = request.model;
6363
+ let usageTokens;
6364
+ let responseText = "";
6365
+ let blocked = false;
6366
+ const stepRequestPayload = {
6367
+ model: providerInfo.model,
6368
+ messages,
6369
+ tools: fireworksTools,
6370
+ tool_choice: "auto",
6371
+ parallel_tool_calls: true
6372
+ };
6373
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6374
+ provider: "fireworks",
6375
+ modelId: request.model,
6376
+ requestPayload: stepRequestPayload,
6377
+ step: turn
6378
+ });
6379
+ try {
6380
+ const response = await runFireworksCall(
6381
+ async (client) => {
6382
+ return await client.chat.completions.create(
6383
+ {
6384
+ model: providerInfo.model,
6385
+ messages,
6386
+ tools: fireworksTools,
6387
+ tool_choice: "auto",
6388
+ parallel_tool_calls: true
6389
+ },
6390
+ { signal: request.signal }
6391
+ );
6392
+ },
6393
+ providerInfo.model,
6394
+ {
6395
+ onSettled: (metrics) => {
6396
+ schedulerMetrics = metrics;
6397
+ }
6398
+ }
6399
+ );
6400
+ const modelCompletedAtMs = Date.now();
6401
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
6402
+ request.onEvent?.({ type: "model", modelVersion });
6403
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
6404
+ if (choice?.finish_reason === "content_filter") {
6405
+ blocked = true;
6406
+ request.onEvent?.({ type: "blocked" });
5562
6407
  }
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
6408
+ const message = choice?.message;
6409
+ responseText = extractFireworksMessageText(message).trim();
6410
+ if (responseText.length > 0) {
6411
+ stepCallLogger?.appendResponseDelta(responseText);
6412
+ request.onEvent?.({ type: "delta", channel: "response", text: responseText });
6413
+ }
6414
+ usageTokens = extractFireworksUsageTokens(response.usage);
6415
+ const stepCostUsd = estimateCallCostUsd({
6416
+ modelId: modelVersion,
6417
+ tokens: usageTokens,
6418
+ responseImages: 0
5576
6419
  });
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"
6420
+ totalCostUsd += stepCostUsd;
6421
+ if (usageTokens) {
6422
+ request.onEvent?.({
6423
+ type: "usage",
6424
+ usage: usageTokens,
6425
+ costUsd: stepCostUsd,
6426
+ modelVersion
5585
6427
  });
5586
- toolOutputs.push({
5587
- type: "custom_tool_call_output",
5588
- call_id: entry.ids.callId,
5589
- output: toOpenAiToolOutput(outputPayload)
6428
+ }
6429
+ const responseToolCalls = extractFireworksToolCalls(message);
6430
+ if (responseToolCalls.length === 0) {
6431
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6432
+ const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
6433
+ finalText = responseText;
6434
+ finalThoughts = "";
6435
+ const stepCompletedAtMs2 = Date.now();
6436
+ const timing2 = buildStepTiming({
6437
+ stepStartedAtMs,
6438
+ stepCompletedAtMs: stepCompletedAtMs2,
6439
+ modelCompletedAtMs,
6440
+ schedulerMetrics,
6441
+ toolExecutionMs: 0,
6442
+ waitToolMs: 0
5590
6443
  });
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"
6444
+ steps.push({
6445
+ step: steps.length + 1,
6446
+ modelVersion,
6447
+ text: responseText || void 0,
6448
+ thoughts: void 0,
6449
+ toolCalls: [],
6450
+ usage: usageTokens,
6451
+ costUsd: stepCostUsd,
6452
+ timing: timing2
5599
6453
  });
5600
- toolOutputs.push({
5601
- type: "function_call_output",
5602
- call_id: entry.ids.callId,
5603
- output: toOpenAiToolOutput(outputPayload)
6454
+ stepCallLogger?.complete({
6455
+ provider: "fireworks",
6456
+ model: request.model,
6457
+ modelVersion,
6458
+ step: turn,
6459
+ usage: usageTokens,
6460
+ costUsd: stepCostUsd,
6461
+ blocked,
6462
+ responseChars: responseText.length,
6463
+ thoughtChars: 0,
6464
+ toolCalls: 0,
6465
+ finalStep: steeringMessages.length === 0
5604
6466
  });
6467
+ if (steeringMessages.length === 0) {
6468
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
6469
+ }
6470
+ if (responseText.length > 0) {
6471
+ messages.push({ role: "assistant", content: responseText });
6472
+ }
6473
+ messages.push(...steeringMessages);
6474
+ continue;
5605
6475
  }
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);
6476
+ const stepToolCalls = [];
6477
+ const callInputs = responseToolCalls.map((call, index) => {
6478
+ const toolIndex = index + 1;
6479
+ const toolId = buildToolLogId(turn, toolIndex);
6480
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
6481
+ return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
6482
+ });
6483
+ for (const entry of callInputs) {
6484
+ request.onEvent?.({
6485
+ type: "tool_call",
6486
+ phase: "started",
6487
+ turn: entry.turn,
6488
+ toolIndex: entry.toolIndex,
6489
+ toolName: entry.toolName,
6490
+ toolId: entry.toolId,
6491
+ callKind: "function",
6492
+ callId: entry.call.id,
6493
+ input: entry.value
6494
+ });
6495
+ }
6496
+ const callResults = await Promise.all(
6497
+ callInputs.map(async (entry) => {
6498
+ return await toolCallContextStorage.run(
6499
+ {
6500
+ toolName: entry.toolName,
6501
+ toolId: entry.toolId,
6502
+ turn: entry.turn,
6503
+ toolIndex: entry.toolIndex
6504
+ },
6505
+ async () => {
6506
+ const { result, outputPayload } = await executeToolCall({
6507
+ callKind: "function",
6508
+ toolName: entry.toolName,
6509
+ tool: request.tools[entry.toolName],
6510
+ rawInput: entry.value,
6511
+ parseError: entry.parseError
6512
+ });
6513
+ return { entry, result, outputPayload };
6514
+ }
6515
+ );
6516
+ })
6517
+ );
6518
+ const assistantToolCalls = [];
6519
+ const toolMessages = [];
6520
+ let toolExecutionMs = 0;
6521
+ let waitToolMs = 0;
6522
+ for (const { entry, result, outputPayload } of callResults) {
6523
+ stepToolCalls.push({ ...result, callId: entry.call.id });
6524
+ const callDurationMs = toToolResultDuration(result);
6525
+ toolExecutionMs += callDurationMs;
6526
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6527
+ waitToolMs += callDurationMs;
6528
+ }
6529
+ request.onEvent?.({
6530
+ type: "tool_call",
6531
+ phase: "completed",
6532
+ turn: entry.turn,
6533
+ toolIndex: entry.toolIndex,
6534
+ toolName: entry.toolName,
6535
+ toolId: entry.toolId,
6536
+ callKind: "function",
6537
+ callId: entry.call.id,
6538
+ input: entry.value,
6539
+ output: result.output,
6540
+ error: result.error,
6541
+ durationMs: result.durationMs
6542
+ });
6543
+ assistantToolCalls.push({
6544
+ id: entry.call.id,
6545
+ type: "function",
6546
+ function: {
6547
+ name: entry.toolName,
6548
+ arguments: entry.call.arguments
6549
+ }
6550
+ });
6551
+ toolMessages.push({
6552
+ role: "tool",
6553
+ tool_call_id: entry.call.id,
6554
+ content: mergeToolOutput(outputPayload)
6555
+ });
6556
+ }
6557
+ const stepCompletedAtMs = Date.now();
6558
+ const timing = buildStepTiming({
6559
+ stepStartedAtMs,
6560
+ stepCompletedAtMs,
6561
+ modelCompletedAtMs,
6562
+ schedulerMetrics,
6563
+ toolExecutionMs,
6564
+ waitToolMs
6565
+ });
6566
+ steps.push({
6567
+ step: steps.length + 1,
6568
+ modelVersion,
6569
+ text: responseText || void 0,
6570
+ thoughts: void 0,
6571
+ toolCalls: stepToolCalls,
6572
+ usage: usageTokens,
6573
+ costUsd: stepCostUsd,
6574
+ timing
6575
+ });
6576
+ stepCallLogger?.complete({
6577
+ provider: "fireworks",
6578
+ model: request.model,
6579
+ modelVersion,
6580
+ step: turn,
6581
+ usage: usageTokens,
6582
+ costUsd: stepCostUsd,
6583
+ blocked,
6584
+ responseChars: responseText.length,
6585
+ thoughtChars: 0,
6586
+ toolCalls: stepToolCalls.length,
6587
+ finalStep: false
6588
+ });
6589
+ messages.push({
6590
+ role: "assistant",
6591
+ ...responseText.length > 0 ? { content: responseText } : {},
6592
+ tool_calls: assistantToolCalls
6593
+ });
6594
+ messages.push(...toolMessages);
6595
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6596
+ if (steeringInput.length > 0) {
6597
+ messages.push(...toFireworksMessages(steeringInput));
6598
+ }
6599
+ } catch (error) {
6600
+ stepCallLogger?.fail(error, {
6601
+ provider: "fireworks",
6602
+ model: request.model,
6603
+ modelVersion,
6604
+ step: turn,
6605
+ usage: usageTokens,
6606
+ blocked
6607
+ });
6608
+ throw error;
6609
+ }
5629
6610
  }
5630
6611
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5631
6612
  }
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(
6613
+ const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
6614
+ const geminiNativeTools = toGeminiTools(request.modelTools);
6615
+ const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
6616
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
6617
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
6618
+ const turn = stepIndex + 1;
6619
+ const stepStartedAtMs = Date.now();
6620
+ let firstModelEventAtMs;
6621
+ let schedulerMetrics;
6622
+ let modelVersion = request.model;
6623
+ let usageTokens;
6624
+ let responseText = "";
6625
+ let thoughtsText = "";
6626
+ const markFirstModelEvent = () => {
6627
+ if (firstModelEventAtMs === void 0) {
6628
+ firstModelEventAtMs = Date.now();
6629
+ }
6630
+ };
6631
+ const thinkingConfig = resolveGeminiThinkingConfig(request.model, request.thinkingLevel);
6632
+ const config = {
6633
+ maxOutputTokens: 32e3,
6634
+ tools: geminiTools,
6635
+ toolConfig: {
6636
+ functionCallingConfig: {
6637
+ mode: FunctionCallingConfigMode.VALIDATED
6638
+ }
6639
+ },
6640
+ ...thinkingConfig ? { thinkingConfig } : {}
6641
+ };
6642
+ const onEvent = request.onEvent;
6643
+ const stepRequestPayload = {
6644
+ model: request.model,
6645
+ contents: geminiContents,
6646
+ config
6647
+ };
6648
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6649
+ provider: "gemini",
6650
+ modelId: request.model,
6651
+ requestPayload: stepRequestPayload,
6652
+ step: turn
6653
+ });
6654
+ try {
6655
+ const response = await runGeminiCall(
5645
6656
  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
- );
6657
+ const stream = await client.models.generateContentStream({
6658
+ model: request.model,
6659
+ contents: geminiContents,
6660
+ config
6661
+ });
6662
+ let responseText2 = "";
6663
+ let thoughtsText2 = "";
6664
+ const modelParts = [];
6665
+ const functionCalls = [];
6666
+ const seenFunctionCallIds = /* @__PURE__ */ new Set();
6667
+ const seenFunctionCallKeys = /* @__PURE__ */ new Set();
6668
+ let latestUsageMetadata;
6669
+ let resolvedModelVersion;
6670
+ for await (const chunk of stream) {
6671
+ markFirstModelEvent();
6672
+ if (chunk.modelVersion) {
6673
+ resolvedModelVersion = chunk.modelVersion;
6674
+ onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
6675
+ }
6676
+ if (chunk.usageMetadata) {
6677
+ latestUsageMetadata = chunk.usageMetadata;
6678
+ }
6679
+ const candidates = chunk.candidates;
6680
+ if (!candidates || candidates.length === 0) {
6681
+ continue;
6682
+ }
6683
+ const primary = candidates[0];
6684
+ const parts = primary?.content?.parts;
6685
+ if (!parts || parts.length === 0) {
6686
+ continue;
6687
+ }
6688
+ for (const part of parts) {
6689
+ modelParts.push(part);
6690
+ const call = part.functionCall;
6691
+ if (call) {
6692
+ const id = typeof call.id === "string" ? call.id : "";
6693
+ const shouldAdd = (() => {
6694
+ if (id.length > 0) {
6695
+ if (seenFunctionCallIds.has(id)) {
6696
+ return false;
6697
+ }
6698
+ seenFunctionCallIds.add(id);
6699
+ return true;
6700
+ }
6701
+ const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
6702
+ if (seenFunctionCallKeys.has(key)) {
6703
+ return false;
6704
+ }
6705
+ seenFunctionCallKeys.add(key);
6706
+ return true;
6707
+ })();
6708
+ if (shouldAdd) {
6709
+ functionCalls.push(call);
6710
+ }
6711
+ }
6712
+ if (typeof part.text === "string" && part.text.length > 0) {
6713
+ if (part.thought) {
6714
+ thoughtsText2 += part.text;
6715
+ stepCallLogger?.appendThoughtDelta(part.text);
6716
+ onEvent?.({ type: "delta", channel: "thought", text: part.text });
6717
+ } else {
6718
+ responseText2 += part.text;
6719
+ stepCallLogger?.appendResponseDelta(part.text);
6720
+ onEvent?.({ type: "delta", channel: "response", text: part.text });
6721
+ }
6722
+ }
6723
+ }
6724
+ }
6725
+ return {
6726
+ responseText: responseText2,
6727
+ thoughtsText: thoughtsText2,
6728
+ functionCalls,
6729
+ modelParts,
6730
+ usageMetadata: latestUsageMetadata,
6731
+ modelVersion: resolvedModelVersion ?? request.model
6732
+ };
5656
6733
  },
5657
- providerInfo.model,
6734
+ request.model,
5658
6735
  {
5659
6736
  onSettled: (metrics) => {
5660
6737
  schedulerMetrics = metrics;
@@ -5662,43 +6739,26 @@ async function runToolLoop(request) {
5662
6739
  }
5663
6740
  );
5664
6741
  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);
6742
+ usageTokens = extractGeminiUsageTokens(response.usageMetadata);
6743
+ modelVersion = response.modelVersion ?? request.model;
6744
+ responseText = response.responseText.trim();
6745
+ thoughtsText = response.thoughtsText.trim();
5677
6746
  const stepCostUsd = estimateCallCostUsd({
5678
6747
  modelId: modelVersion,
5679
6748
  tokens: usageTokens,
5680
6749
  responseImages: 0
5681
6750
  });
5682
6751
  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) {
6752
+ if (response.functionCalls.length === 0) {
5693
6753
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5694
- const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
5695
6754
  finalText = responseText;
5696
- finalThoughts = "";
6755
+ finalThoughts = thoughtsText;
5697
6756
  const stepCompletedAtMs2 = Date.now();
5698
6757
  const timing2 = buildStepTiming({
5699
6758
  stepStartedAtMs,
5700
6759
  stepCompletedAtMs: stepCompletedAtMs2,
5701
6760
  modelCompletedAtMs,
6761
+ firstModelEventAtMs,
5702
6762
  schedulerMetrics,
5703
6763
  toolExecutionMs: 0,
5704
6764
  waitToolMs: 0
@@ -5706,31 +6766,65 @@ async function runToolLoop(request) {
5706
6766
  steps.push({
5707
6767
  step: steps.length + 1,
5708
6768
  modelVersion,
5709
- text: responseText || void 0,
5710
- thoughts: void 0,
6769
+ text: finalText || void 0,
6770
+ thoughts: finalThoughts || void 0,
5711
6771
  toolCalls: [],
5712
6772
  usage: usageTokens,
5713
6773
  costUsd: stepCostUsd,
5714
6774
  timing: timing2
5715
6775
  });
5716
- if (steeringMessages.length === 0) {
6776
+ stepCallLogger?.complete({
6777
+ provider: "gemini",
6778
+ model: request.model,
6779
+ modelVersion,
6780
+ step: turn,
6781
+ usage: usageTokens,
6782
+ costUsd: stepCostUsd,
6783
+ responseChars: responseText.length,
6784
+ thoughtChars: thoughtsText.length,
6785
+ toolCalls: 0,
6786
+ finalStep: steeringInput2.length === 0
6787
+ });
6788
+ if (steeringInput2.length === 0) {
5717
6789
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5718
6790
  }
5719
- if (responseText.length > 0) {
5720
- messages.push({ role: "assistant", content: responseText });
6791
+ const modelPartsForHistory2 = response.modelParts.filter(
6792
+ (part) => !(typeof part.text === "string" && part.thought === true)
6793
+ );
6794
+ if (modelPartsForHistory2.length > 0) {
6795
+ geminiContents.push({ role: "model", parts: modelPartsForHistory2 });
6796
+ } else if (response.responseText.length > 0) {
6797
+ geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
5721
6798
  }
5722
- messages.push(...steeringMessages);
6799
+ geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
5723
6800
  continue;
5724
6801
  }
5725
- const stepToolCalls = [];
5726
- const callInputs = responseToolCalls.map((call, index) => {
6802
+ const toolCalls = [];
6803
+ const modelPartsForHistory = response.modelParts.filter(
6804
+ (part) => !(typeof part.text === "string" && part.thought === true)
6805
+ );
6806
+ if (modelPartsForHistory.length > 0) {
6807
+ geminiContents.push({ role: "model", parts: modelPartsForHistory });
6808
+ } else {
6809
+ const parts = [];
6810
+ if (response.responseText) {
6811
+ parts.push({ text: response.responseText });
6812
+ }
6813
+ for (const call of response.functionCalls) {
6814
+ parts.push({ functionCall: call });
6815
+ }
6816
+ geminiContents.push({ role: "model", parts });
6817
+ }
6818
+ const responseParts = [];
6819
+ const callInputs = response.functionCalls.map((call, index) => {
5727
6820
  const toolIndex = index + 1;
5728
6821
  const toolId = buildToolLogId(turn, toolIndex);
5729
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5730
- return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
6822
+ const toolName = call.name ?? "unknown";
6823
+ const rawInput = call.args ?? {};
6824
+ return { call, toolName, rawInput, toolId, turn, toolIndex };
5731
6825
  });
5732
6826
  for (const entry of callInputs) {
5733
- request.onEvent?.({
6827
+ onEvent?.({
5734
6828
  type: "tool_call",
5735
6829
  phase: "started",
5736
6830
  turn: entry.turn,
@@ -5739,7 +6833,7 @@ async function runToolLoop(request) {
5739
6833
  toolId: entry.toolId,
5740
6834
  callKind: "function",
5741
6835
  callId: entry.call.id,
5742
- input: entry.value
6836
+ input: entry.rawInput
5743
6837
  });
5744
6838
  }
5745
6839
  const callResults = await Promise.all(
@@ -5756,26 +6850,23 @@ async function runToolLoop(request) {
5756
6850
  callKind: "function",
5757
6851
  toolName: entry.toolName,
5758
6852
  tool: request.tools[entry.toolName],
5759
- rawInput: entry.value,
5760
- parseError: entry.parseError
6853
+ rawInput: entry.rawInput
5761
6854
  });
5762
6855
  return { entry, result, outputPayload };
5763
6856
  }
5764
6857
  );
5765
6858
  })
5766
6859
  );
5767
- const assistantToolCalls = [];
5768
- const toolMessages = [];
5769
6860
  let toolExecutionMs = 0;
5770
6861
  let waitToolMs = 0;
5771
6862
  for (const { entry, result, outputPayload } of callResults) {
5772
- stepToolCalls.push({ ...result, callId: entry.call.id });
6863
+ toolCalls.push({ ...result, callId: entry.call.id });
5773
6864
  const callDurationMs = toToolResultDuration(result);
5774
6865
  toolExecutionMs += callDurationMs;
5775
6866
  if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5776
6867
  waitToolMs += callDurationMs;
5777
6868
  }
5778
- request.onEvent?.({
6869
+ onEvent?.({
5779
6870
  type: "tool_call",
5780
6871
  phase: "completed",
5781
6872
  turn: entry.turn,
@@ -5784,30 +6875,26 @@ async function runToolLoop(request) {
5784
6875
  toolId: entry.toolId,
5785
6876
  callKind: "function",
5786
6877
  callId: entry.call.id,
5787
- input: entry.value,
6878
+ input: entry.rawInput,
5788
6879
  output: result.output,
5789
6880
  error: result.error,
5790
6881
  durationMs: result.durationMs
5791
6882
  });
5792
- assistantToolCalls.push({
5793
- id: entry.call.id,
5794
- type: "function",
5795
- function: {
6883
+ const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
6884
+ responseParts.push({
6885
+ functionResponse: {
5796
6886
  name: entry.toolName,
5797
- arguments: entry.call.arguments
6887
+ response: responsePayload,
6888
+ ...entry.call.id ? { id: entry.call.id } : {}
5798
6889
  }
5799
6890
  });
5800
- toolMessages.push({
5801
- role: "tool",
5802
- tool_call_id: entry.call.id,
5803
- content: mergeToolOutput(outputPayload)
5804
- });
5805
6891
  }
5806
6892
  const stepCompletedAtMs = Date.now();
5807
6893
  const timing = buildStepTiming({
5808
6894
  stepStartedAtMs,
5809
6895
  stepCompletedAtMs,
5810
6896
  modelCompletedAtMs,
6897
+ firstModelEventAtMs,
5811
6898
  schedulerMetrics,
5812
6899
  toolExecutionMs,
5813
6900
  waitToolMs
@@ -5816,296 +6903,40 @@ async function runToolLoop(request) {
5816
6903
  step: steps.length + 1,
5817
6904
  modelVersion,
5818
6905
  text: responseText || void 0,
5819
- thoughts: void 0,
5820
- toolCalls: stepToolCalls,
6906
+ thoughts: thoughtsText || void 0,
6907
+ toolCalls,
5821
6908
  usage: usageTokens,
5822
6909
  costUsd: stepCostUsd,
5823
6910
  timing
5824
6911
  });
5825
- messages.push({
5826
- role: "assistant",
5827
- ...responseText.length > 0 ? { content: responseText } : {},
5828
- tool_calls: assistantToolCalls
6912
+ stepCallLogger?.complete({
6913
+ provider: "gemini",
6914
+ model: request.model,
6915
+ modelVersion,
6916
+ step: turn,
6917
+ usage: usageTokens,
6918
+ costUsd: stepCostUsd,
6919
+ responseChars: responseText.length,
6920
+ thoughtChars: thoughtsText.length,
6921
+ toolCalls: toolCalls.length,
6922
+ finalStep: false
5829
6923
  });
5830
- messages.push(...toolMessages);
6924
+ geminiContents.push({ role: "user", parts: responseParts });
5831
6925
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5832
6926
  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
- }
6927
+ geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
5945
6928
  }
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,
6929
+ } catch (error) {
6930
+ stepCallLogger?.fail(error, {
6931
+ provider: "gemini",
6932
+ model: request.model,
5972
6933
  modelVersion,
5973
- text: finalText || void 0,
5974
- thoughts: finalThoughts || void 0,
5975
- toolCalls: [],
6934
+ step: turn,
5976
6935
  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
- }
6936
+ responseChars: responseText.length,
6937
+ thoughtChars: thoughtsText.length
6083
6938
  });
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));
6939
+ throw error;
6109
6940
  }
6110
6941
  }
6111
6942
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
@@ -6419,6 +7250,7 @@ ${lines}`;
6419
7250
 
6420
7251
  // src/agent.ts
6421
7252
  import { randomBytes as randomBytes3 } from "crypto";
7253
+ import path7 from "path";
6422
7254
 
6423
7255
  // src/agent/subagents.ts
6424
7256
  import { randomBytes as randomBytes2 } from "crypto";
@@ -6893,26 +7725,26 @@ function resolveInputItemsText(items) {
6893
7725
  }
6894
7726
  const itemType = typeof item.type === "string" ? item.type.trim() : "";
6895
7727
  const name = typeof item.name === "string" ? item.name.trim() : "";
6896
- const path6 = typeof item.path === "string" ? item.path.trim() : "";
7728
+ const path8 = typeof item.path === "string" ? item.path.trim() : "";
6897
7729
  const imageUrl = typeof item.image_url === "string" ? item.image_url.trim() : "";
6898
7730
  if (itemType === "image") {
6899
7731
  lines.push("[image]");
6900
7732
  continue;
6901
7733
  }
6902
- if (itemType === "local_image" && path6) {
6903
- lines.push(`[local_image:${path6}]`);
7734
+ if (itemType === "local_image" && path8) {
7735
+ lines.push(`[local_image:${path8}]`);
6904
7736
  continue;
6905
7737
  }
6906
- if (itemType === "skill" && name && path6) {
6907
- lines.push(`[skill:$${name}](${path6})`);
7738
+ if (itemType === "skill" && name && path8) {
7739
+ lines.push(`[skill:$${name}](${path8})`);
6908
7740
  continue;
6909
7741
  }
6910
- if (itemType === "mention" && name && path6) {
6911
- lines.push(`[mention:$${name}](${path6})`);
7742
+ if (itemType === "mention" && name && path8) {
7743
+ lines.push(`[mention:$${name}](${path8})`);
6912
7744
  continue;
6913
7745
  }
6914
- if (path6 || imageUrl) {
6915
- lines.push(`[${itemType || "input"}:${path6 || imageUrl}]`);
7746
+ if (path8 || imageUrl) {
7747
+ lines.push(`[${itemType || "input"}:${path8 || imageUrl}]`);
6916
7748
  continue;
6917
7749
  }
6918
7750
  if (name) {
@@ -7037,7 +7869,7 @@ function startRun(agent, options) {
7037
7869
  setLifecycle(agent, "idle", "input_queued", `Subagent ${agent.id} run interrupted.`);
7038
7870
  return;
7039
7871
  }
7040
- const message = toErrorMessage(error);
7872
+ const message = toErrorMessage2(error);
7041
7873
  agent.lastError = message;
7042
7874
  setLifecycle(agent, "failed", "run_failed", `Subagent ${agent.id} failed: ${message}`);
7043
7875
  emitBackgroundNotification(agent, options);
@@ -7217,7 +8049,7 @@ function trimToUndefined(value) {
7217
8049
  const trimmed = value?.trim();
7218
8050
  return trimmed && trimmed.length > 0 ? trimmed : void 0;
7219
8051
  }
7220
- function toErrorMessage(error) {
8052
+ function toErrorMessage2(error) {
7221
8053
  if (error instanceof Error) {
7222
8054
  return error.message;
7223
8055
  }
@@ -7230,27 +8062,27 @@ function sleep2(ms) {
7230
8062
  }
7231
8063
 
7232
8064
  // src/tools/filesystemTools.ts
7233
- import path5 from "path";
7234
- import { Buffer as Buffer4 } from "buffer";
8065
+ import path6 from "path";
8066
+ import { Buffer as Buffer5 } from "buffer";
7235
8067
  import { z as z6 } from "zod";
7236
8068
 
7237
8069
  // src/tools/applyPatch.ts
7238
- import path4 from "path";
8070
+ import path5 from "path";
7239
8071
  import { z as z5 } from "zod";
7240
8072
 
7241
8073
  // src/tools/filesystem.ts
7242
8074
  import { promises as fs3 } from "fs";
7243
- import path3 from "path";
8075
+ import path4 from "path";
7244
8076
  var InMemoryAgentFilesystem = class {
7245
8077
  #files = /* @__PURE__ */ new Map();
7246
8078
  #dirs = /* @__PURE__ */ new Map();
7247
8079
  #clock = 0;
7248
8080
  constructor(initialFiles = {}) {
7249
- const root = path3.resolve("/");
8081
+ const root = path4.resolve("/");
7250
8082
  this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
7251
8083
  for (const [filePath, content] of Object.entries(initialFiles)) {
7252
- const absolutePath = path3.resolve(filePath);
7253
- this.#ensureDirSync(path3.dirname(absolutePath));
8084
+ const absolutePath = path4.resolve(filePath);
8085
+ this.#ensureDirSync(path4.dirname(absolutePath));
7254
8086
  this.#files.set(absolutePath, {
7255
8087
  content,
7256
8088
  mtimeMs: this.#nextMtime()
@@ -7258,7 +8090,7 @@ var InMemoryAgentFilesystem = class {
7258
8090
  }
7259
8091
  }
7260
8092
  async readTextFile(filePath) {
7261
- const absolutePath = path3.resolve(filePath);
8093
+ const absolutePath = path4.resolve(filePath);
7262
8094
  const file = this.#files.get(absolutePath);
7263
8095
  if (!file) {
7264
8096
  throw createNoSuchFileError("open", absolutePath);
@@ -7270,24 +8102,24 @@ var InMemoryAgentFilesystem = class {
7270
8102
  return Buffer.from(content, "utf8");
7271
8103
  }
7272
8104
  async writeTextFile(filePath, content) {
7273
- const absolutePath = path3.resolve(filePath);
7274
- const parentPath = path3.dirname(absolutePath);
8105
+ const absolutePath = path4.resolve(filePath);
8106
+ const parentPath = path4.dirname(absolutePath);
7275
8107
  if (!this.#dirs.has(parentPath)) {
7276
8108
  throw createNoSuchFileError("open", parentPath);
7277
8109
  }
7278
8110
  this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
7279
8111
  }
7280
8112
  async deleteFile(filePath) {
7281
- const absolutePath = path3.resolve(filePath);
8113
+ const absolutePath = path4.resolve(filePath);
7282
8114
  if (!this.#files.delete(absolutePath)) {
7283
8115
  throw createNoSuchFileError("unlink", absolutePath);
7284
8116
  }
7285
8117
  }
7286
8118
  async ensureDir(directoryPath) {
7287
- this.#ensureDirSync(path3.resolve(directoryPath));
8119
+ this.#ensureDirSync(path4.resolve(directoryPath));
7288
8120
  }
7289
8121
  async readDir(directoryPath) {
7290
- const absolutePath = path3.resolve(directoryPath);
8122
+ const absolutePath = path4.resolve(directoryPath);
7291
8123
  const directory = this.#dirs.get(absolutePath);
7292
8124
  if (!directory) {
7293
8125
  throw createNoSuchFileError("scandir", absolutePath);
@@ -7298,10 +8130,10 @@ var InMemoryAgentFilesystem = class {
7298
8130
  if (dirPath === absolutePath) {
7299
8131
  continue;
7300
8132
  }
7301
- if (path3.dirname(dirPath) !== absolutePath) {
8133
+ if (path4.dirname(dirPath) !== absolutePath) {
7302
8134
  continue;
7303
8135
  }
7304
- const name = path3.basename(dirPath);
8136
+ const name = path4.basename(dirPath);
7305
8137
  if (seenNames.has(name)) {
7306
8138
  continue;
7307
8139
  }
@@ -7314,10 +8146,10 @@ var InMemoryAgentFilesystem = class {
7314
8146
  });
7315
8147
  }
7316
8148
  for (const [filePath, fileRecord] of this.#files.entries()) {
7317
- if (path3.dirname(filePath) !== absolutePath) {
8149
+ if (path4.dirname(filePath) !== absolutePath) {
7318
8150
  continue;
7319
8151
  }
7320
- const name = path3.basename(filePath);
8152
+ const name = path4.basename(filePath);
7321
8153
  if (seenNames.has(name)) {
7322
8154
  continue;
7323
8155
  }
@@ -7333,7 +8165,7 @@ var InMemoryAgentFilesystem = class {
7333
8165
  return entries;
7334
8166
  }
7335
8167
  async stat(entryPath) {
7336
- const absolutePath = path3.resolve(entryPath);
8168
+ const absolutePath = path4.resolve(entryPath);
7337
8169
  const file = this.#files.get(absolutePath);
7338
8170
  if (file) {
7339
8171
  return { kind: "file", mtimeMs: file.mtimeMs };
@@ -7349,7 +8181,7 @@ var InMemoryAgentFilesystem = class {
7349
8181
  return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
7350
8182
  }
7351
8183
  #ensureDirSync(directoryPath) {
7352
- const absolutePath = path3.resolve(directoryPath);
8184
+ const absolutePath = path4.resolve(directoryPath);
7353
8185
  const parts = [];
7354
8186
  let cursor = absolutePath;
7355
8187
  for (; ; ) {
@@ -7357,7 +8189,7 @@ var InMemoryAgentFilesystem = class {
7357
8189
  break;
7358
8190
  }
7359
8191
  parts.push(cursor);
7360
- const parent = path3.dirname(cursor);
8192
+ const parent = path4.dirname(cursor);
7361
8193
  if (parent === cursor) {
7362
8194
  break;
7363
8195
  }
@@ -7391,7 +8223,7 @@ function createNodeAgentFilesystem() {
7391
8223
  const entries = await fs3.readdir(directoryPath, { withFileTypes: true });
7392
8224
  const result = [];
7393
8225
  for (const entry of entries) {
7394
- const entryPath = path3.resolve(directoryPath, entry.name);
8226
+ const entryPath = path4.resolve(directoryPath, entry.name);
7395
8227
  const stats = await fs3.lstat(entryPath);
7396
8228
  result.push({
7397
8229
  name: entry.name,
@@ -7555,7 +8387,7 @@ function createApplyPatchTool(options = {}) {
7555
8387
  });
7556
8388
  }
7557
8389
  async function applyPatch(request) {
7558
- const cwd = path4.resolve(request.cwd ?? process.cwd());
8390
+ const cwd = path5.resolve(request.cwd ?? process.cwd());
7559
8391
  const adapter = request.fs ?? createNodeAgentFilesystem();
7560
8392
  const allowOutsideCwd = request.allowOutsideCwd === true;
7561
8393
  const patchBytes = Buffer.byteLength(request.patch, "utf8");
@@ -7577,7 +8409,7 @@ async function applyPatch(request) {
7577
8409
  kind: "add",
7578
8410
  path: absolutePath2
7579
8411
  });
7580
- await adapter.ensureDir(path4.dirname(absolutePath2));
8412
+ await adapter.ensureDir(path5.dirname(absolutePath2));
7581
8413
  await adapter.writeTextFile(absolutePath2, operation.content);
7582
8414
  added.push(toDisplayPath(absolutePath2, cwd));
7583
8415
  continue;
@@ -7611,7 +8443,7 @@ async function applyPatch(request) {
7611
8443
  fromPath: absolutePath,
7612
8444
  toPath: destinationPath
7613
8445
  });
7614
- await adapter.ensureDir(path4.dirname(destinationPath));
8446
+ await adapter.ensureDir(path5.dirname(destinationPath));
7615
8447
  await adapter.writeTextFile(destinationPath, next);
7616
8448
  await adapter.deleteFile(absolutePath);
7617
8449
  modified.push(toDisplayPath(destinationPath, cwd));
@@ -7642,22 +8474,22 @@ function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
7642
8474
  if (trimmed.length === 0) {
7643
8475
  throw new Error("apply_patch failed: empty file path");
7644
8476
  }
7645
- const absolutePath = path4.isAbsolute(trimmed) ? path4.resolve(trimmed) : path4.resolve(cwd, trimmed);
8477
+ const absolutePath = path5.isAbsolute(trimmed) ? path5.resolve(trimmed) : path5.resolve(cwd, trimmed);
7646
8478
  if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
7647
8479
  throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
7648
8480
  }
7649
8481
  return absolutePath;
7650
8482
  }
7651
8483
  function isPathInsideCwd(candidatePath, cwd) {
7652
- const relative = path4.relative(cwd, candidatePath);
7653
- return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
8484
+ const relative = path5.relative(cwd, candidatePath);
8485
+ return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
7654
8486
  }
7655
8487
  function toDisplayPath(absolutePath, cwd) {
7656
- const relative = path4.relative(cwd, absolutePath);
8488
+ const relative = path5.relative(cwd, absolutePath);
7657
8489
  if (relative === "") {
7658
8490
  return ".";
7659
8491
  }
7660
- if (!relative.startsWith("..") && !path4.isAbsolute(relative)) {
8492
+ if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
7661
8493
  return relative;
7662
8494
  }
7663
8495
  return absolutePath;
@@ -8424,7 +9256,7 @@ async function readBinaryFile(filesystem, filePath) {
8424
9256
  return await filesystem.readBinaryFile(filePath);
8425
9257
  }
8426
9258
  const text = await filesystem.readTextFile(filePath);
8427
- return Buffer4.from(text, "utf8");
9259
+ return Buffer5.from(text, "utf8");
8428
9260
  }
8429
9261
  function detectImageMimeType(buffer, filePath) {
8430
9262
  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 +9274,7 @@ function detectImageMimeType(buffer, filePath) {
8442
9274
  if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
8443
9275
  return "image/webp";
8444
9276
  }
8445
- const fromExtension = IMAGE_MIME_BY_EXTENSION[path5.extname(filePath).toLowerCase()];
9277
+ const fromExtension = IMAGE_MIME_BY_EXTENSION[path6.extname(filePath).toLowerCase()];
8446
9278
  if (fromExtension && SUPPORTED_IMAGE_MIME_TYPES.has(fromExtension)) {
8447
9279
  return fromExtension;
8448
9280
  }
@@ -8452,13 +9284,13 @@ function isPdfFile(buffer, filePath) {
8452
9284
  if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
8453
9285
  return true;
8454
9286
  }
8455
- return path5.extname(filePath).toLowerCase() === ".pdf";
9287
+ return path6.extname(filePath).toLowerCase() === ".pdf";
8456
9288
  }
8457
9289
  function isValidUtf8(buffer) {
8458
9290
  if (buffer.length === 0) {
8459
9291
  return true;
8460
9292
  }
8461
- return Buffer4.from(buffer.toString("utf8"), "utf8").equals(buffer);
9293
+ return Buffer5.from(buffer.toString("utf8"), "utf8").equals(buffer);
8462
9294
  }
8463
9295
  async function readFileGemini(input, options) {
8464
9296
  const runtime = resolveRuntime(options);
@@ -8490,7 +9322,7 @@ async function writeFileGemini(input, options) {
8490
9322
  action: "write",
8491
9323
  path: filePath
8492
9324
  });
8493
- await runtime.filesystem.ensureDir(path5.dirname(filePath));
9325
+ await runtime.filesystem.ensureDir(path6.dirname(filePath));
8494
9326
  await runtime.filesystem.writeTextFile(filePath, input.content);
8495
9327
  return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
8496
9328
  }
@@ -8511,7 +9343,7 @@ async function replaceFileContentGemini(input, options) {
8511
9343
  originalContent = await runtime.filesystem.readTextFile(filePath);
8512
9344
  } catch (error) {
8513
9345
  if (isNoEntError(error) && oldValue.length === 0) {
8514
- await runtime.filesystem.ensureDir(path5.dirname(filePath));
9346
+ await runtime.filesystem.ensureDir(path6.dirname(filePath));
8515
9347
  await runtime.filesystem.writeTextFile(filePath, newValue);
8516
9348
  return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
8517
9349
  }
@@ -8689,7 +9521,7 @@ async function globFilesGemini(input, options) {
8689
9521
  });
8690
9522
  const matched = [];
8691
9523
  for (const filePath of files) {
8692
- const relativePath = normalizeSlashes(path5.relative(dirPath, filePath));
9524
+ const relativePath = normalizeSlashes(path6.relative(dirPath, filePath));
8693
9525
  if (!matcher(relativePath)) {
8694
9526
  continue;
8695
9527
  }
@@ -8707,7 +9539,7 @@ async function globFilesGemini(input, options) {
8707
9539
  }
8708
9540
  function resolveRuntime(options) {
8709
9541
  return {
8710
- cwd: path5.resolve(options.cwd ?? process.cwd()),
9542
+ cwd: path6.resolve(options.cwd ?? process.cwd()),
8711
9543
  filesystem: options.fs ?? createNodeAgentFilesystem(),
8712
9544
  allowOutsideCwd: options.allowOutsideCwd === true,
8713
9545
  checkAccess: options.checkAccess,
@@ -8738,13 +9570,13 @@ function mapApplyPatchAction(action) {
8738
9570
  return "move";
8739
9571
  }
8740
9572
  function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
8741
- const absolutePath = path5.isAbsolute(inputPath) ? path5.resolve(inputPath) : path5.resolve(cwd, inputPath);
9573
+ const absolutePath = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(cwd, inputPath);
8742
9574
  if (allowOutsideCwd || isPathInsideCwd2(absolutePath, cwd)) {
8743
9575
  return absolutePath;
8744
9576
  }
8745
- if (path5.isAbsolute(inputPath)) {
9577
+ if (path6.isAbsolute(inputPath)) {
8746
9578
  const sandboxRelativePath = inputPath.replace(/^[/\\]+/, "");
8747
- const sandboxRootedPath = path5.resolve(cwd, sandboxRelativePath);
9579
+ const sandboxRootedPath = path6.resolve(cwd, sandboxRelativePath);
8748
9580
  if (isPathInsideCwd2(sandboxRootedPath, cwd)) {
8749
9581
  return sandboxRootedPath;
8750
9582
  }
@@ -8752,25 +9584,25 @@ function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
8752
9584
  throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
8753
9585
  }
8754
9586
  function isPathInsideCwd2(candidatePath, cwd) {
8755
- const relative = path5.relative(cwd, candidatePath);
8756
- return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
9587
+ const relative = path6.relative(cwd, candidatePath);
9588
+ return relative === "" || !relative.startsWith("..") && !path6.isAbsolute(relative);
8757
9589
  }
8758
9590
  function toDisplayPath2(absolutePath, cwd) {
8759
- const relative = path5.relative(cwd, absolutePath);
9591
+ const relative = path6.relative(cwd, absolutePath);
8760
9592
  if (relative === "") {
8761
9593
  return ".";
8762
9594
  }
8763
- if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
9595
+ if (!relative.startsWith("..") && !path6.isAbsolute(relative)) {
8764
9596
  return relative;
8765
9597
  }
8766
9598
  return absolutePath;
8767
9599
  }
8768
9600
  function toSandboxDisplayPath(absolutePath, cwd) {
8769
- const relative = path5.relative(cwd, absolutePath);
9601
+ const relative = path6.relative(cwd, absolutePath);
8770
9602
  if (relative === "") {
8771
9603
  return "/";
8772
9604
  }
8773
- if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
9605
+ if (!relative.startsWith("..") && !path6.isAbsolute(relative)) {
8774
9606
  return `/${normalizeSlashes(relative)}`;
8775
9607
  }
8776
9608
  return normalizeSlashes(absolutePath);
@@ -8886,7 +9718,7 @@ function createGlobMatcher(pattern, caseSensitive = false) {
8886
9718
  }));
8887
9719
  return (candidatePath) => {
8888
9720
  const normalizedPath = normalizeSlashes(candidatePath);
8889
- const basename = path5.posix.basename(normalizedPath);
9721
+ const basename = path6.posix.basename(normalizedPath);
8890
9722
  return compiled.some(
8891
9723
  (entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
8892
9724
  );
@@ -9002,10 +9834,18 @@ function isNoEntError(error) {
9002
9834
  // src/agent.ts
9003
9835
  async function runAgentLoop(request) {
9004
9836
  const telemetry = createAgentTelemetrySession(request.telemetry);
9837
+ const logging = createRootAgentLoggingSession(request);
9005
9838
  try {
9006
- return await runAgentLoopInternal(request, { depth: 0, telemetry });
9839
+ return await runWithAgentLoggingSession(logging, async () => {
9840
+ return await runAgentLoopInternal(request, {
9841
+ depth: 0,
9842
+ telemetry,
9843
+ logging
9844
+ });
9845
+ });
9007
9846
  } finally {
9008
9847
  await telemetry?.flush();
9848
+ await logging?.flush();
9009
9849
  }
9010
9850
  }
9011
9851
  function mergeAbortSignals2(first, second) {
@@ -9076,9 +9916,11 @@ async function runAgentLoopInternal(request, context) {
9076
9916
  subagent_tool,
9077
9917
  subagents,
9078
9918
  telemetry,
9919
+ logging: _logging,
9079
9920
  ...toolLoopRequest
9080
9921
  } = request;
9081
9922
  const telemetrySession = context.telemetry ?? createAgentTelemetrySession(telemetry);
9923
+ const loggingSession = context.logging;
9082
9924
  const runId = randomRunId();
9083
9925
  const startedAtMs = Date.now();
9084
9926
  const steeringChannel = toolLoopRequest.steering ?? createToolLoopSteeringChannel();
@@ -9092,6 +9934,7 @@ async function runAgentLoopInternal(request, context) {
9092
9934
  model: request.model,
9093
9935
  depth: context.depth,
9094
9936
  telemetry: telemetrySession,
9937
+ logging: loggingSession,
9095
9938
  customTools: customTools ?? {},
9096
9939
  filesystemSelection,
9097
9940
  subagentSelection,
@@ -9128,6 +9971,16 @@ async function runAgentLoopInternal(request, context) {
9128
9971
  filesystemToolsEnabled: Object.keys(filesystemTools).length > 0,
9129
9972
  subagentToolsEnabled: resolvedSubagentConfig.enabled
9130
9973
  });
9974
+ loggingSession?.logLine(
9975
+ [
9976
+ `[agent:${runId}] run_started`,
9977
+ `depth=${context.depth.toString()}`,
9978
+ `model=${request.model}`,
9979
+ `tools=${Object.keys(mergedTools).length.toString()}`,
9980
+ `filesystemTools=${Object.keys(filesystemTools).length > 0 ? "true" : "false"}`,
9981
+ `subagentTools=${resolvedSubagentConfig.enabled ? "true" : "false"}`
9982
+ ].join(" ")
9983
+ );
9131
9984
  const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
9132
9985
  const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
9133
9986
  const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
@@ -9135,6 +9988,14 @@ async function runAgentLoopInternal(request, context) {
9135
9988
  if (includeLlmStreamEvents) {
9136
9989
  emitTelemetry({ type: "agent.run.stream", event });
9137
9990
  }
9991
+ if (loggingSession) {
9992
+ appendAgentStreamEventLog({
9993
+ event,
9994
+ append: (line) => {
9995
+ loggingSession.logLine(`[agent:${runId}] ${line}`);
9996
+ }
9997
+ });
9998
+ }
9138
9999
  } : void 0;
9139
10000
  try {
9140
10001
  const result = await runToolLoop({
@@ -9152,14 +10013,43 @@ async function runAgentLoopInternal(request, context) {
9152
10013
  totalCostUsd: result.totalCostUsd,
9153
10014
  usage: summarizeResultUsage(result)
9154
10015
  });
10016
+ loggingSession?.logLine(
10017
+ [
10018
+ `[agent:${runId}] run_completed`,
10019
+ `status=ok`,
10020
+ `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
10021
+ `steps=${result.steps.length.toString()}`,
10022
+ `toolCalls=${countToolCalls(result).toString()}`,
10023
+ `totalCostUsd=${(result.totalCostUsd ?? 0).toFixed(6)}`
10024
+ ].join(" ")
10025
+ );
10026
+ for (const step of result.steps) {
10027
+ loggingSession?.logLine(
10028
+ [
10029
+ `[agent:${runId}] step_completed`,
10030
+ `step=${step.step.toString()}`,
10031
+ `modelVersion=${step.modelVersion}`,
10032
+ `toolCalls=${step.toolCalls.length.toString()}`,
10033
+ `costUsd=${(step.costUsd ?? 0).toFixed(6)}`
10034
+ ].join(" ")
10035
+ );
10036
+ }
9155
10037
  return result;
9156
10038
  } catch (error) {
9157
10039
  emitTelemetry({
9158
10040
  type: "agent.run.completed",
9159
10041
  success: false,
9160
10042
  durationMs: Math.max(0, Date.now() - startedAtMs),
9161
- error: toErrorMessage2(error)
10043
+ error: toErrorMessage3(error)
9162
10044
  });
10045
+ loggingSession?.logLine(
10046
+ [
10047
+ `[agent:${runId}] run_completed`,
10048
+ `status=error`,
10049
+ `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
10050
+ `error=${toErrorMessage3(error)}`
10051
+ ].join(" ")
10052
+ );
9163
10053
  throw error;
9164
10054
  } finally {
9165
10055
  await subagentController?.closeAll();
@@ -9230,7 +10120,8 @@ function createSubagentController(params) {
9230
10120
  {
9231
10121
  depth: params.depth + 1,
9232
10122
  parentRunId: params.runId,
9233
- telemetry: params.telemetry
10123
+ telemetry: params.telemetry,
10124
+ logging: params.logging
9234
10125
  }
9235
10126
  );
9236
10127
  }
@@ -9294,10 +10185,10 @@ function trimToUndefined2(value) {
9294
10185
  function randomRunId() {
9295
10186
  return randomBytes3(8).toString("hex");
9296
10187
  }
9297
- function toIsoNow() {
10188
+ function toIsoNow2() {
9298
10189
  return (/* @__PURE__ */ new Date()).toISOString();
9299
10190
  }
9300
- function toErrorMessage2(error) {
10191
+ function toErrorMessage3(error) {
9301
10192
  if (error instanceof Error && error.message) {
9302
10193
  return error.message;
9303
10194
  }
@@ -9342,9 +10233,41 @@ function summarizeResultUsage(result) {
9342
10233
  }
9343
10234
  return summary;
9344
10235
  }
9345
- function isPromiseLike(value) {
10236
+ function isPromiseLike2(value) {
9346
10237
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
9347
10238
  }
10239
+ function resolveAgentLoggingSelection(value) {
10240
+ if (value === false) {
10241
+ return void 0;
10242
+ }
10243
+ if (value === void 0 || value === true) {
10244
+ return {
10245
+ mirrorToConsole: true
10246
+ };
10247
+ }
10248
+ return value;
10249
+ }
10250
+ function resolveWorkspaceDirForLogging(request) {
10251
+ const explicitSelection = request.filesystemTool ?? request.filesystem_tool;
10252
+ if (explicitSelection && typeof explicitSelection === "object" && !Array.isArray(explicitSelection)) {
10253
+ const cwd = explicitSelection.options?.cwd;
10254
+ if (typeof cwd === "string" && cwd.trim().length > 0) {
10255
+ return path7.resolve(cwd);
10256
+ }
10257
+ }
10258
+ return process.cwd();
10259
+ }
10260
+ function createRootAgentLoggingSession(request) {
10261
+ const selected = resolveAgentLoggingSelection(request.logging);
10262
+ if (!selected) {
10263
+ return void 0;
10264
+ }
10265
+ return createAgentLoggingSession({
10266
+ ...selected,
10267
+ workspaceDir: typeof selected.workspaceDir === "string" && selected.workspaceDir.trim().length > 0 ? path7.resolve(selected.workspaceDir) : resolveWorkspaceDirForLogging(request),
10268
+ mirrorToConsole: selected.mirrorToConsole !== false
10269
+ });
10270
+ }
9348
10271
  function isAgentTelemetrySink(value) {
9349
10272
  return typeof value === "object" && value !== null && typeof value.emit === "function";
9350
10273
  }
@@ -9375,7 +10298,7 @@ function createAgentTelemetrySession(telemetry) {
9375
10298
  const emit = (event) => {
9376
10299
  try {
9377
10300
  const output = config.sink.emit(event);
9378
- if (isPromiseLike(output)) {
10301
+ if (isPromiseLike2(output)) {
9379
10302
  const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
9380
10303
  trackPromise(task);
9381
10304
  }
@@ -9406,7 +10329,7 @@ function createAgentTelemetryEmitter(params) {
9406
10329
  }
9407
10330
  params.session.emit({
9408
10331
  ...event,
9409
- timestamp: toIsoNow(),
10332
+ timestamp: toIsoNow2(),
9410
10333
  runId: params.runId,
9411
10334
  ...params.parentRunId ? { parentRunId: params.parentRunId } : {},
9412
10335
  depth: params.depth,