@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.cjs CHANGED
@@ -115,8 +115,8 @@ __export(index_exports, {
115
115
  module.exports = __toCommonJS(index_exports);
116
116
 
117
117
  // src/llm.ts
118
- var import_node_buffer2 = require("buffer");
119
- var import_node_async_hooks = require("async_hooks");
118
+ var import_node_buffer3 = require("buffer");
119
+ var import_node_async_hooks2 = require("async_hooks");
120
120
  var import_node_crypto = require("crypto");
121
121
  var import_genai2 = require("@google/genai");
122
122
  var import_zod_to_json_schema = require("@alcyone-labs/zod-to-json-schema");
@@ -2755,8 +2755,375 @@ function stripChatGptPrefix(model) {
2755
2755
  return model.slice("chatgpt-".length);
2756
2756
  }
2757
2757
 
2758
+ // src/agentLogging.ts
2759
+ var import_node_async_hooks = require("async_hooks");
2760
+ var import_node_buffer2 = require("buffer");
2761
+ var import_promises = require("fs/promises");
2762
+ var import_node_path3 = __toESM(require("path"), 1);
2763
+ function toIsoNow() {
2764
+ return (/* @__PURE__ */ new Date()).toISOString();
2765
+ }
2766
+ function toErrorMessage(error) {
2767
+ if (error instanceof Error && error.message) {
2768
+ return error.message;
2769
+ }
2770
+ if (typeof error === "string") {
2771
+ return error;
2772
+ }
2773
+ return String(error);
2774
+ }
2775
+ function isPromiseLike(value) {
2776
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
2777
+ }
2778
+ function normalisePathSegment(value) {
2779
+ const cleaned = value.trim().replace(/[^a-z0-9._-]+/giu, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
2780
+ return cleaned.length > 0 ? cleaned : "segment";
2781
+ }
2782
+ function ensureTrailingNewline(value) {
2783
+ return value.endsWith("\n") ? value : `${value}
2784
+ `;
2785
+ }
2786
+ function redactDataUrlPayload(value) {
2787
+ if (!value.toLowerCase().startsWith("data:")) {
2788
+ return value;
2789
+ }
2790
+ const commaIndex = value.indexOf(",");
2791
+ if (commaIndex < 0) {
2792
+ return value;
2793
+ }
2794
+ return `${value.slice(0, commaIndex + 1)}...`;
2795
+ }
2796
+ function sanitiseLogValue(value, seen = /* @__PURE__ */ new WeakSet()) {
2797
+ if (typeof value === "string") {
2798
+ return redactDataUrlPayload(value);
2799
+ }
2800
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) {
2801
+ return value;
2802
+ }
2803
+ if (Array.isArray(value)) {
2804
+ return value.map((entry) => sanitiseLogValue(entry, seen));
2805
+ }
2806
+ if (typeof value !== "object") {
2807
+ return String(value);
2808
+ }
2809
+ if (seen.has(value)) {
2810
+ return "[circular]";
2811
+ }
2812
+ seen.add(value);
2813
+ const record = value;
2814
+ const output = {};
2815
+ const hasInlineMime = typeof record.mimeType === "string" && record.mimeType.trim().length > 0 || typeof record.mime_type === "string" && record.mime_type.trim().length > 0;
2816
+ for (const [key, entryValue] of Object.entries(record)) {
2817
+ if (key === "image_url") {
2818
+ if (typeof entryValue === "string") {
2819
+ output[key] = redactDataUrlPayload(entryValue);
2820
+ continue;
2821
+ }
2822
+ if (entryValue && typeof entryValue === "object") {
2823
+ const nested = entryValue;
2824
+ if (typeof nested.url === "string") {
2825
+ output[key] = {
2826
+ ...nested,
2827
+ url: redactDataUrlPayload(nested.url)
2828
+ };
2829
+ continue;
2830
+ }
2831
+ }
2832
+ }
2833
+ if (key === "data" && hasInlineMime && typeof entryValue === "string") {
2834
+ output[key] = `[omitted:${import_node_buffer2.Buffer.byteLength(entryValue, "utf8")}b]`;
2835
+ continue;
2836
+ }
2837
+ output[key] = sanitiseLogValue(entryValue, seen);
2838
+ }
2839
+ return output;
2840
+ }
2841
+ function serialiseForSnippet(value) {
2842
+ if (typeof value === "string") {
2843
+ return value;
2844
+ }
2845
+ try {
2846
+ return JSON.stringify(sanitiseLogValue(value));
2847
+ } catch {
2848
+ return String(value);
2849
+ }
2850
+ }
2851
+ function formatToolLogSnippet(value) {
2852
+ const compact = serialiseForSnippet(value).replace(/\s+/gu, " ").trim();
2853
+ if (compact.length === 0) {
2854
+ return "<empty>";
2855
+ }
2856
+ const max = 600;
2857
+ if (compact.length <= max) {
2858
+ return compact;
2859
+ }
2860
+ return `${compact.slice(0, max)}...`;
2861
+ }
2862
+ function formatUsd(value) {
2863
+ const amount = typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
2864
+ return amount.toFixed(6);
2865
+ }
2866
+ function appendToolCallStreamLog(options) {
2867
+ const event = options.event;
2868
+ if (event.type !== "tool_call") {
2869
+ return;
2870
+ }
2871
+ const callIdSegment = typeof event.callId === "string" && event.callId.trim().length > 0 ? ` callId=${event.callId}` : "";
2872
+ const prefix = [
2873
+ `tool_call_${event.phase}:`,
2874
+ `turn=${event.turn.toString()}`,
2875
+ `index=${event.toolIndex.toString()}`,
2876
+ `tool=${event.toolName}${callIdSegment}`
2877
+ ].join(" ");
2878
+ if (event.phase === "started") {
2879
+ options.append(prefix);
2880
+ options.append(`tool_call_input: ${formatToolLogSnippet(sanitiseLogValue(event.input))}`);
2881
+ return;
2882
+ }
2883
+ const durationSegment = typeof event.durationMs === "number" && Number.isFinite(event.durationMs) ? ` durationMs=${Math.max(0, Math.round(event.durationMs)).toString()}` : "";
2884
+ options.append(`${prefix} status=${event.error ? "error" : "ok"}${durationSegment}`);
2885
+ options.append(`tool_call_output: ${formatToolLogSnippet(sanitiseLogValue(event.output))}`);
2886
+ if (typeof event.error === "string" && event.error.trim().length > 0) {
2887
+ options.append(`tool_call_error: ${event.error.trim()}`);
2888
+ }
2889
+ }
2890
+ function appendAgentStreamEventLog(options) {
2891
+ const event = options.event;
2892
+ switch (event.type) {
2893
+ case "delta": {
2894
+ const channelPrefix = event.channel === "thought" ? "thought_delta" : "response_delta";
2895
+ options.append(`${channelPrefix}: ${event.text}`);
2896
+ return;
2897
+ }
2898
+ case "model": {
2899
+ options.append(`model: ${event.modelVersion}`);
2900
+ return;
2901
+ }
2902
+ case "usage": {
2903
+ options.append(
2904
+ [
2905
+ "usage:",
2906
+ `modelVersion=${event.modelVersion}`,
2907
+ `costUsd=${formatUsd(event.costUsd)}`,
2908
+ `tokens=${formatToolLogSnippet(sanitiseLogValue(event.usage))}`
2909
+ ].join(" ")
2910
+ );
2911
+ return;
2912
+ }
2913
+ case "blocked": {
2914
+ options.append("blocked");
2915
+ return;
2916
+ }
2917
+ case "tool_call": {
2918
+ appendToolCallStreamLog({
2919
+ event,
2920
+ append: options.append
2921
+ });
2922
+ return;
2923
+ }
2924
+ }
2925
+ }
2926
+ var AgentLoggingSessionImpl = class {
2927
+ workspaceDir;
2928
+ logsRootDir;
2929
+ mirrorToConsole;
2930
+ sink;
2931
+ agentLogPath;
2932
+ ensureReady;
2933
+ pending = /* @__PURE__ */ new Set();
2934
+ lineChain = Promise.resolve();
2935
+ callCounter = 0;
2936
+ constructor(config) {
2937
+ this.workspaceDir = import_node_path3.default.resolve(config.workspaceDir ?? process.cwd());
2938
+ this.logsRootDir = import_node_path3.default.join(import_node_path3.default.dirname(this.workspaceDir), "logs");
2939
+ this.mirrorToConsole = config.mirrorToConsole !== false;
2940
+ this.sink = config.sink;
2941
+ this.agentLogPath = import_node_path3.default.join(this.workspaceDir, "agent.log");
2942
+ this.ensureReady = this.prepare();
2943
+ this.track(this.ensureReady);
2944
+ }
2945
+ async prepare() {
2946
+ await (0, import_promises.mkdir)(this.workspaceDir, { recursive: true });
2947
+ await (0, import_promises.mkdir)(this.logsRootDir, { recursive: true });
2948
+ }
2949
+ track(task) {
2950
+ this.pending.add(task);
2951
+ task.finally(() => {
2952
+ this.pending.delete(task);
2953
+ });
2954
+ }
2955
+ enqueueLineWrite(line) {
2956
+ const next = this.lineChain.then(async () => {
2957
+ await this.ensureReady;
2958
+ await (0, import_promises.appendFile)(this.agentLogPath, `${line}
2959
+ `, "utf8");
2960
+ const sinkResult = this.sink?.append(line);
2961
+ if (isPromiseLike(sinkResult)) {
2962
+ await sinkResult;
2963
+ }
2964
+ });
2965
+ const tracked = next.catch(() => void 0);
2966
+ this.lineChain = tracked;
2967
+ this.track(tracked);
2968
+ }
2969
+ logLine(line) {
2970
+ const timestamped = `${toIsoNow()} ${line}`;
2971
+ if (this.mirrorToConsole) {
2972
+ console.log(timestamped);
2973
+ }
2974
+ this.enqueueLineWrite(timestamped);
2975
+ }
2976
+ startLlmCall(input) {
2977
+ const callNumber = this.callCounter + 1;
2978
+ this.callCounter = callNumber;
2979
+ const timestampSegment = toIsoNow().replace(/[:]/g, "-");
2980
+ const modelSegment = normalisePathSegment(input.modelId);
2981
+ const baseDir = import_node_path3.default.join(
2982
+ this.logsRootDir,
2983
+ `${timestampSegment}-${callNumber.toString().padStart(4, "0")}`,
2984
+ modelSegment
2985
+ );
2986
+ const responsePath = import_node_path3.default.join(baseDir, "response.txt");
2987
+ const thoughtsPath = import_node_path3.default.join(baseDir, "thoughts.txt");
2988
+ const responseMetadataPath = import_node_path3.default.join(baseDir, "response.metadata.json");
2989
+ let chain = this.ensureReady.then(async () => {
2990
+ await (0, import_promises.mkdir)(baseDir, { recursive: true });
2991
+ const requestText = input.requestText.trim().length > 0 ? input.requestText : "<empty request>";
2992
+ await (0, import_promises.writeFile)(
2993
+ import_node_path3.default.join(baseDir, "request.txt"),
2994
+ ensureTrailingNewline(requestText),
2995
+ "utf8"
2996
+ );
2997
+ const requestMetadata = {
2998
+ capturedAt: toIsoNow(),
2999
+ provider: input.provider,
3000
+ modelId: input.modelId,
3001
+ ...input.requestMetadata ? { request: sanitiseLogValue(input.requestMetadata) } : {}
3002
+ };
3003
+ await (0, import_promises.writeFile)(
3004
+ import_node_path3.default.join(baseDir, "request.metadata.json"),
3005
+ `${JSON.stringify(requestMetadata, null, 2)}
3006
+ `,
3007
+ "utf8"
3008
+ );
3009
+ const usedNames = /* @__PURE__ */ new Set();
3010
+ for (const attachment of input.attachments ?? []) {
3011
+ let filename = normalisePathSegment(attachment.filename);
3012
+ if (!filename.includes(".")) {
3013
+ filename = `${filename}.bin`;
3014
+ }
3015
+ const ext = import_node_path3.default.extname(filename);
3016
+ const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
3017
+ let candidate = filename;
3018
+ let duplicateIndex = 2;
3019
+ while (usedNames.has(candidate)) {
3020
+ candidate = `${base}-${duplicateIndex.toString()}${ext}`;
3021
+ duplicateIndex += 1;
3022
+ }
3023
+ usedNames.add(candidate);
3024
+ await (0, import_promises.writeFile)(import_node_path3.default.join(baseDir, candidate), attachment.bytes);
3025
+ }
3026
+ }).catch(() => void 0);
3027
+ this.track(chain);
3028
+ let closed = false;
3029
+ const enqueue = (operation) => {
3030
+ const next = chain.then(operation);
3031
+ const tracked = next.catch(() => void 0);
3032
+ chain = tracked;
3033
+ this.track(tracked);
3034
+ };
3035
+ return {
3036
+ appendThoughtDelta: (text) => {
3037
+ if (closed || text.length === 0) {
3038
+ return;
3039
+ }
3040
+ enqueue(async () => {
3041
+ await (0, import_promises.appendFile)(thoughtsPath, text, "utf8");
3042
+ });
3043
+ },
3044
+ appendResponseDelta: (text) => {
3045
+ if (closed || text.length === 0) {
3046
+ return;
3047
+ }
3048
+ enqueue(async () => {
3049
+ await (0, import_promises.appendFile)(responsePath, text, "utf8");
3050
+ });
3051
+ },
3052
+ complete: (metadata) => {
3053
+ if (closed) {
3054
+ return;
3055
+ }
3056
+ closed = true;
3057
+ enqueue(async () => {
3058
+ const payload = {
3059
+ capturedAt: toIsoNow(),
3060
+ status: "completed"
3061
+ };
3062
+ if (metadata) {
3063
+ const sanitised = sanitiseLogValue(metadata);
3064
+ if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3065
+ Object.assign(payload, sanitised);
3066
+ } else if (sanitised !== void 0) {
3067
+ payload.metadata = sanitised;
3068
+ }
3069
+ }
3070
+ await (0, import_promises.writeFile)(responseMetadataPath, `${JSON.stringify(payload, null, 2)}
3071
+ `, "utf8");
3072
+ });
3073
+ },
3074
+ fail: (error, metadata) => {
3075
+ if (closed) {
3076
+ return;
3077
+ }
3078
+ closed = true;
3079
+ enqueue(async () => {
3080
+ const payload = {
3081
+ capturedAt: toIsoNow(),
3082
+ status: "failed",
3083
+ error: toErrorMessage(error)
3084
+ };
3085
+ if (metadata) {
3086
+ const sanitised = sanitiseLogValue(metadata);
3087
+ if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3088
+ Object.assign(payload, sanitised);
3089
+ } else if (sanitised !== void 0) {
3090
+ payload.metadata = sanitised;
3091
+ }
3092
+ }
3093
+ await (0, import_promises.writeFile)(responseMetadataPath, `${JSON.stringify(payload, null, 2)}
3094
+ `, "utf8");
3095
+ });
3096
+ }
3097
+ };
3098
+ }
3099
+ async flush() {
3100
+ while (this.pending.size > 0) {
3101
+ await Promise.allSettled([...this.pending]);
3102
+ }
3103
+ if (typeof this.sink?.flush === "function") {
3104
+ try {
3105
+ await this.sink.flush();
3106
+ } catch {
3107
+ }
3108
+ }
3109
+ }
3110
+ };
3111
+ var loggingSessionStorage = new import_node_async_hooks.AsyncLocalStorage();
3112
+ function createAgentLoggingSession(config) {
3113
+ return new AgentLoggingSessionImpl(config);
3114
+ }
3115
+ function runWithAgentLoggingSession(session, fn) {
3116
+ if (!session) {
3117
+ return fn();
3118
+ }
3119
+ return loggingSessionStorage.run(session, fn);
3120
+ }
3121
+ function getCurrentAgentLoggingSession() {
3122
+ return loggingSessionStorage.getStore();
3123
+ }
3124
+
2758
3125
  // src/llm.ts
2759
- var toolCallContextStorage = new import_node_async_hooks.AsyncLocalStorage();
3126
+ var toolCallContextStorage = new import_node_async_hooks2.AsyncLocalStorage();
2760
3127
  function getCurrentToolCallContext() {
2761
3128
  return toolCallContextStorage.getStore() ?? null;
2762
3129
  }
@@ -3048,9 +3415,9 @@ function sanitisePartForLogging(part) {
3048
3415
  case "inlineData": {
3049
3416
  let omittedBytes;
3050
3417
  try {
3051
- omittedBytes = import_node_buffer2.Buffer.from(part.data, "base64").byteLength;
3418
+ omittedBytes = import_node_buffer3.Buffer.from(part.data, "base64").byteLength;
3052
3419
  } catch {
3053
- omittedBytes = import_node_buffer2.Buffer.byteLength(part.data, "utf8");
3420
+ omittedBytes = import_node_buffer3.Buffer.byteLength(part.data, "utf8");
3054
3421
  }
3055
3422
  return {
3056
3423
  type: "inlineData",
@@ -4113,8 +4480,8 @@ function parseOpenAiToolArguments(raw) {
4113
4480
  function formatZodIssues(issues) {
4114
4481
  const messages = [];
4115
4482
  for (const issue of issues) {
4116
- const path6 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
4117
- messages.push(`${path6}: ${issue.message}`);
4483
+ const path8 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
4484
+ messages.push(`${path8}: ${issue.message}`);
4118
4485
  }
4119
4486
  return messages.join("; ");
4120
4487
  }
@@ -4500,9 +4867,9 @@ function resolveGeminiThinkingConfig(modelId, thinkingLevel) {
4500
4867
  }
4501
4868
  function decodeInlineDataBuffer(base64) {
4502
4869
  try {
4503
- return import_node_buffer2.Buffer.from(base64, "base64");
4870
+ return import_node_buffer3.Buffer.from(base64, "base64");
4504
4871
  } catch {
4505
- return import_node_buffer2.Buffer.from(base64, "base64url");
4872
+ return import_node_buffer3.Buffer.from(base64, "base64url");
4506
4873
  }
4507
4874
  }
4508
4875
  function extractImages(content) {
@@ -4519,6 +4886,195 @@ function extractImages(content) {
4519
4886
  }
4520
4887
  return images;
4521
4888
  }
4889
+ function resolveAttachmentExtension(mimeType) {
4890
+ const normalized = (mimeType ?? "").trim().toLowerCase();
4891
+ switch (normalized) {
4892
+ case "image/jpeg":
4893
+ return "jpg";
4894
+ case "image/png":
4895
+ return "png";
4896
+ case "image/webp":
4897
+ return "webp";
4898
+ case "image/gif":
4899
+ return "gif";
4900
+ case "image/heic":
4901
+ return "heic";
4902
+ case "image/heif":
4903
+ return "heif";
4904
+ case "application/pdf":
4905
+ return "pdf";
4906
+ case "application/json":
4907
+ return "json";
4908
+ case "text/plain":
4909
+ return "txt";
4910
+ case "text/markdown":
4911
+ return "md";
4912
+ default: {
4913
+ const slashIndex = normalized.indexOf("/");
4914
+ if (slashIndex >= 0) {
4915
+ const subtype = normalized.slice(slashIndex + 1).split("+")[0] ?? "";
4916
+ const cleaned = subtype.replace(/[^a-z0-9]+/giu, "");
4917
+ if (cleaned.length > 0) {
4918
+ return cleaned;
4919
+ }
4920
+ }
4921
+ return "bin";
4922
+ }
4923
+ }
4924
+ }
4925
+ function decodeDataUrlAttachment(value, basename) {
4926
+ const trimmed = value.trim();
4927
+ if (!trimmed.toLowerCase().startsWith("data:")) {
4928
+ return null;
4929
+ }
4930
+ const commaIndex = trimmed.indexOf(",");
4931
+ if (commaIndex < 0) {
4932
+ return null;
4933
+ }
4934
+ const header = trimmed.slice(5, commaIndex);
4935
+ const payload = trimmed.slice(commaIndex + 1);
4936
+ const isBase64 = /;base64(?:;|$)/iu.test(header);
4937
+ const mimeType = (header.split(";")[0] ?? "application/octet-stream").trim().toLowerCase();
4938
+ try {
4939
+ const bytes = isBase64 ? import_node_buffer3.Buffer.from(payload, "base64") : import_node_buffer3.Buffer.from(decodeURIComponent(payload), "utf8");
4940
+ return {
4941
+ filename: `${basename}.${resolveAttachmentExtension(mimeType)}`,
4942
+ bytes
4943
+ };
4944
+ } catch {
4945
+ return null;
4946
+ }
4947
+ }
4948
+ function collectPayloadAttachments(value, options) {
4949
+ if (typeof value === "string") {
4950
+ const attachment = decodeDataUrlAttachment(
4951
+ value,
4952
+ `${options.prefix}-${options.counter.toString()}`
4953
+ );
4954
+ if (attachment) {
4955
+ options.attachments.push(attachment);
4956
+ options.counter += 1;
4957
+ }
4958
+ return;
4959
+ }
4960
+ if (!value || typeof value !== "object") {
4961
+ return;
4962
+ }
4963
+ if (options.seen.has(value)) {
4964
+ return;
4965
+ }
4966
+ options.seen.add(value);
4967
+ if (Array.isArray(value)) {
4968
+ for (const entry of value) {
4969
+ collectPayloadAttachments(entry, options);
4970
+ }
4971
+ return;
4972
+ }
4973
+ const record = value;
4974
+ const mimeType = typeof record.mimeType === "string" ? record.mimeType : void 0;
4975
+ if (typeof record.data === "string" && mimeType) {
4976
+ try {
4977
+ options.attachments.push({
4978
+ filename: `${options.prefix}-${options.counter.toString()}.${resolveAttachmentExtension(mimeType)}`,
4979
+ bytes: decodeInlineDataBuffer(record.data)
4980
+ });
4981
+ options.counter += 1;
4982
+ } catch {
4983
+ }
4984
+ }
4985
+ for (const entry of Object.values(record)) {
4986
+ collectPayloadAttachments(entry, options);
4987
+ }
4988
+ }
4989
+ function serialiseRequestPayloadForLogging(value) {
4990
+ try {
4991
+ return `${JSON.stringify(sanitiseLogValue(value), null, 2)}
4992
+ `;
4993
+ } catch {
4994
+ return `${String(value)}
4995
+ `;
4996
+ }
4997
+ }
4998
+ function startLlmCallLoggerFromContents(options) {
4999
+ const session = getCurrentAgentLoggingSession();
5000
+ if (!session) {
5001
+ return void 0;
5002
+ }
5003
+ const attachments = [];
5004
+ const sections = [];
5005
+ for (const [messageIndex, message] of options.contents.entries()) {
5006
+ sections.push(`### message_${(messageIndex + 1).toString()} role=${message.role}`);
5007
+ for (const [partIndex, part] of message.parts.entries()) {
5008
+ if (part.type === "text") {
5009
+ const channel = part.thought === true ? "thought" : "response";
5010
+ sections.push(`[text:${channel}]`);
5011
+ sections.push(part.text);
5012
+ continue;
5013
+ }
5014
+ const filename = `message-${(messageIndex + 1).toString()}-part-${(partIndex + 1).toString()}.${resolveAttachmentExtension(part.mimeType)}`;
5015
+ attachments.push({
5016
+ filename,
5017
+ bytes: decodeInlineDataBuffer(part.data)
5018
+ });
5019
+ sections.push(
5020
+ `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
5021
+ );
5022
+ }
5023
+ sections.push("");
5024
+ }
5025
+ return session.startLlmCall({
5026
+ provider: options.provider,
5027
+ modelId: options.request.model,
5028
+ requestText: sections.join("\n").trim(),
5029
+ requestMetadata: {
5030
+ model: options.request.model,
5031
+ input: options.contents.map((content) => ({
5032
+ role: content.role,
5033
+ parts: content.parts.map((part) => sanitisePartForLogging(part))
5034
+ })),
5035
+ ...options.request.instructions ? {
5036
+ instructions: options.request.instructions
5037
+ } : {},
5038
+ ...options.request.tools ? { tools: options.request.tools } : {},
5039
+ ...options.request.responseMimeType ? {
5040
+ responseMimeType: options.request.responseMimeType
5041
+ } : {},
5042
+ ...options.request.responseJsonSchema ? {
5043
+ responseJsonSchema: sanitiseLogValue(options.request.responseJsonSchema)
5044
+ } : {},
5045
+ ...options.request.responseModalities ? { responseModalities: options.request.responseModalities } : {},
5046
+ ...options.request.imageAspectRatio ? { imageAspectRatio: options.request.imageAspectRatio } : {},
5047
+ ...options.request.imageSize ? { imageSize: options.request.imageSize } : {},
5048
+ ...options.request.thinkingLevel ? { thinkingLevel: options.request.thinkingLevel } : {},
5049
+ ...options.request.openAiTextFormat ? { openAiTextFormat: sanitiseLogValue(options.request.openAiTextFormat) } : {},
5050
+ ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
5051
+ },
5052
+ attachments
5053
+ });
5054
+ }
5055
+ function startLlmCallLoggerFromPayload(options) {
5056
+ const session = getCurrentAgentLoggingSession();
5057
+ if (!session) {
5058
+ return void 0;
5059
+ }
5060
+ const attachments = [];
5061
+ collectPayloadAttachments(options.requestPayload, {
5062
+ prefix: `step-${options.step.toString()}`,
5063
+ attachments,
5064
+ seen: /* @__PURE__ */ new WeakSet(),
5065
+ counter: 1
5066
+ });
5067
+ return session.startLlmCall({
5068
+ provider: options.provider,
5069
+ modelId: options.modelId,
5070
+ requestText: serialiseRequestPayloadForLogging(options.requestPayload),
5071
+ requestMetadata: {
5072
+ step: options.step,
5073
+ ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
5074
+ },
5075
+ attachments
5076
+ });
5077
+ }
4522
5078
  async function runTextCall(params) {
4523
5079
  const { request, queue, abortController } = params;
4524
5080
  const providerInfo = resolveProvider(request.model);
@@ -4528,6 +5084,11 @@ async function runTextCall(params) {
4528
5084
  if (contents.length === 0) {
4529
5085
  throw new Error("LLM call received an empty prompt.");
4530
5086
  }
5087
+ const callLogger = startLlmCallLoggerFromContents({
5088
+ provider,
5089
+ request,
5090
+ contents
5091
+ });
4531
5092
  let modelVersion = request.model;
4532
5093
  let blocked = false;
4533
5094
  let grounding;
@@ -4535,12 +5096,17 @@ async function runTextCall(params) {
4535
5096
  let responseRole;
4536
5097
  let latestUsage;
4537
5098
  let responseImages = 0;
4538
- const pushDelta = (channel, text2) => {
4539
- if (!text2) {
5099
+ const pushDelta = (channel, text) => {
5100
+ if (!text) {
4540
5101
  return;
4541
5102
  }
4542
- responseParts.push({ type: "text", text: text2, ...channel === "thought" ? { thought: true } : {} });
4543
- queue.push({ type: "delta", channel, text: text2 });
5103
+ responseParts.push({ type: "text", text, ...channel === "thought" ? { thought: true } : {} });
5104
+ if (channel === "thought") {
5105
+ callLogger?.appendThoughtDelta(text);
5106
+ } else {
5107
+ callLogger?.appendResponseDelta(text);
5108
+ }
5109
+ queue.push({ type: "delta", channel, text });
4544
5110
  };
4545
5111
  const pushInline = (data, mimeType) => {
4546
5112
  if (!data) {
@@ -4567,263 +5133,294 @@ async function runTextCall(params) {
4567
5133
  return abortController.signal;
4568
5134
  };
4569
5135
  const signal = resolveAbortSignal();
4570
- if (provider === "openai") {
4571
- const openAiInput = toOpenAiInput(contents);
4572
- const openAiTools = toOpenAiTools(request.tools);
4573
- const reasoningEffort = resolveOpenAiReasoningEffort(modelForProvider, request.thinkingLevel);
4574
- const openAiTextConfig = {
4575
- format: request.openAiTextFormat ?? { type: "text" },
4576
- verbosity: resolveOpenAiVerbosity(modelForProvider)
4577
- };
4578
- const reasoning = {
4579
- effort: toOpenAiReasoningEffort(reasoningEffort),
4580
- summary: "detailed"
4581
- };
4582
- await runOpenAiCall(async (client) => {
4583
- const stream = client.responses.stream(
4584
- {
4585
- model: modelForProvider,
4586
- input: openAiInput,
4587
- reasoning,
4588
- text: openAiTextConfig,
4589
- ...openAiTools ? { tools: openAiTools } : {},
4590
- include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
4591
- },
4592
- { signal }
4593
- );
4594
- for await (const event of stream) {
4595
- switch (event.type) {
4596
- case "response.output_text.delta": {
4597
- const delta = event.delta ?? "";
4598
- pushDelta("response", typeof delta === "string" ? delta : "");
4599
- break;
5136
+ try {
5137
+ if (provider === "openai") {
5138
+ const openAiInput = toOpenAiInput(contents);
5139
+ const openAiTools = toOpenAiTools(request.tools);
5140
+ const reasoningEffort = resolveOpenAiReasoningEffort(modelForProvider, request.thinkingLevel);
5141
+ const openAiTextConfig = {
5142
+ format: request.openAiTextFormat ?? { type: "text" },
5143
+ verbosity: resolveOpenAiVerbosity(modelForProvider)
5144
+ };
5145
+ const reasoning = {
5146
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5147
+ summary: "detailed"
5148
+ };
5149
+ await runOpenAiCall(async (client) => {
5150
+ const stream = client.responses.stream(
5151
+ {
5152
+ model: modelForProvider,
5153
+ input: openAiInput,
5154
+ reasoning,
5155
+ text: openAiTextConfig,
5156
+ ...openAiTools ? { tools: openAiTools } : {},
5157
+ include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
5158
+ },
5159
+ { signal }
5160
+ );
5161
+ for await (const event of stream) {
5162
+ switch (event.type) {
5163
+ case "response.output_text.delta": {
5164
+ const delta = event.delta ?? "";
5165
+ pushDelta("response", typeof delta === "string" ? delta : "");
5166
+ break;
5167
+ }
5168
+ case "response.reasoning_summary_text.delta": {
5169
+ const delta = event.delta ?? "";
5170
+ pushDelta("thought", typeof delta === "string" ? delta : "");
5171
+ break;
5172
+ }
5173
+ case "response.refusal.delta": {
5174
+ blocked = true;
5175
+ queue.push({ type: "blocked" });
5176
+ break;
5177
+ }
5178
+ default:
5179
+ break;
4600
5180
  }
4601
- case "response.reasoning_summary_text.delta": {
4602
- const delta = event.delta ?? "";
4603
- pushDelta("thought", typeof delta === "string" ? delta : "");
4604
- break;
5181
+ }
5182
+ const finalResponse = await stream.finalResponse();
5183
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5184
+ queue.push({ type: "model", modelVersion });
5185
+ if (finalResponse.error) {
5186
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5187
+ throw new Error(message);
5188
+ }
5189
+ if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
5190
+ const detail = finalResponse.incomplete_details?.reason;
5191
+ throw new Error(
5192
+ `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
5193
+ );
5194
+ }
5195
+ latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
5196
+ if (responseParts.length === 0) {
5197
+ const fallback = extractOpenAiResponseParts(finalResponse);
5198
+ blocked = blocked || fallback.blocked;
5199
+ for (const part of fallback.parts) {
5200
+ if (part.type === "text") {
5201
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
5202
+ } else {
5203
+ pushInline(part.data, part.mimeType);
5204
+ }
4605
5205
  }
4606
- case "response.refusal.delta": {
4607
- blocked = true;
4608
- queue.push({ type: "blocked" });
4609
- break;
5206
+ }
5207
+ }, modelForProvider);
5208
+ } else if (provider === "chatgpt") {
5209
+ const chatGptInput = toChatGptInput(contents);
5210
+ const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
5211
+ const openAiTools = toOpenAiTools(request.tools);
5212
+ const requestPayload = {
5213
+ model: modelForProvider,
5214
+ store: false,
5215
+ stream: true,
5216
+ instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
5217
+ input: chatGptInput.input,
5218
+ include: ["reasoning.encrypted_content"],
5219
+ reasoning: {
5220
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5221
+ summary: "detailed"
5222
+ },
5223
+ text: {
5224
+ format: request.openAiTextFormat ?? { type: "text" },
5225
+ verbosity: resolveOpenAiVerbosity(request.model)
5226
+ },
5227
+ ...openAiTools ? { tools: openAiTools } : {}
5228
+ };
5229
+ let sawResponseDelta = false;
5230
+ let sawThoughtDelta = false;
5231
+ const result = await collectChatGptCodexResponseWithRetry({
5232
+ request: requestPayload,
5233
+ signal,
5234
+ onDelta: (delta) => {
5235
+ if (delta.thoughtDelta) {
5236
+ sawThoughtDelta = true;
5237
+ pushDelta("thought", delta.thoughtDelta);
5238
+ }
5239
+ if (delta.textDelta) {
5240
+ sawResponseDelta = true;
5241
+ pushDelta("response", delta.textDelta);
4610
5242
  }
4611
- default:
4612
- break;
4613
5243
  }
5244
+ });
5245
+ blocked = blocked || result.blocked;
5246
+ if (blocked) {
5247
+ queue.push({ type: "blocked" });
4614
5248
  }
4615
- const finalResponse = await stream.finalResponse();
4616
- modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
4617
- queue.push({ type: "model", modelVersion });
4618
- if (finalResponse.error) {
4619
- const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
4620
- throw new Error(message);
5249
+ if (result.model) {
5250
+ modelVersion = `chatgpt-${result.model}`;
5251
+ queue.push({ type: "model", modelVersion });
4621
5252
  }
4622
- if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
4623
- const detail = finalResponse.incomplete_details?.reason;
5253
+ latestUsage = extractChatGptUsageTokens(result.usage);
5254
+ const fallbackText = typeof result.text === "string" ? result.text : "";
5255
+ const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
5256
+ if (!sawThoughtDelta && fallbackThoughts.length > 0) {
5257
+ pushDelta("thought", fallbackThoughts);
5258
+ }
5259
+ if (!sawResponseDelta && fallbackText.length > 0) {
5260
+ pushDelta("response", fallbackText);
5261
+ }
5262
+ } else if (provider === "fireworks") {
5263
+ if (request.tools && request.tools.length > 0) {
4624
5264
  throw new Error(
4625
- `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
5265
+ "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
4626
5266
  );
4627
5267
  }
4628
- latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
4629
- if (responseParts.length === 0) {
4630
- const fallback = extractOpenAiResponseParts(finalResponse);
4631
- blocked = blocked || fallback.blocked;
4632
- for (const part of fallback.parts) {
4633
- if (part.type === "text") {
4634
- pushDelta(part.thought === true ? "thought" : "response", part.text);
4635
- } else {
4636
- pushInline(part.data, part.mimeType);
5268
+ const fireworksMessages = toFireworksMessages(contents, {
5269
+ responseMimeType: request.responseMimeType,
5270
+ responseJsonSchema: request.responseJsonSchema
5271
+ });
5272
+ await runFireworksCall(async (client) => {
5273
+ const responseFormat = request.responseJsonSchema ? {
5274
+ type: "json_schema",
5275
+ json_schema: {
5276
+ name: "llm-response",
5277
+ schema: request.responseJsonSchema
4637
5278
  }
4638
- }
4639
- }
4640
- }, modelForProvider);
4641
- } else if (provider === "chatgpt") {
4642
- const chatGptInput = toChatGptInput(contents);
4643
- const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
4644
- const openAiTools = toOpenAiTools(request.tools);
4645
- const requestPayload = {
4646
- model: modelForProvider,
4647
- store: false,
4648
- stream: true,
4649
- instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
4650
- input: chatGptInput.input,
4651
- include: ["reasoning.encrypted_content"],
4652
- reasoning: { effort: toOpenAiReasoningEffort(reasoningEffort), summary: "detailed" },
4653
- text: {
4654
- format: request.openAiTextFormat ?? { type: "text" },
4655
- verbosity: resolveOpenAiVerbosity(request.model)
4656
- },
4657
- ...openAiTools ? { tools: openAiTools } : {}
4658
- };
4659
- let sawResponseDelta = false;
4660
- let sawThoughtDelta = false;
4661
- const result = await collectChatGptCodexResponseWithRetry({
4662
- request: requestPayload,
4663
- signal,
4664
- onDelta: (delta) => {
4665
- if (delta.thoughtDelta) {
4666
- sawThoughtDelta = true;
4667
- pushDelta("thought", delta.thoughtDelta);
4668
- }
4669
- if (delta.textDelta) {
4670
- sawResponseDelta = true;
4671
- pushDelta("response", delta.textDelta);
4672
- }
4673
- }
4674
- });
4675
- blocked = blocked || result.blocked;
4676
- if (blocked) {
4677
- queue.push({ type: "blocked" });
4678
- }
4679
- if (result.model) {
4680
- modelVersion = `chatgpt-${result.model}`;
4681
- queue.push({ type: "model", modelVersion });
4682
- }
4683
- latestUsage = extractChatGptUsageTokens(result.usage);
4684
- const fallbackText = typeof result.text === "string" ? result.text : "";
4685
- const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
4686
- if (!sawThoughtDelta && fallbackThoughts.length > 0) {
4687
- pushDelta("thought", fallbackThoughts);
4688
- }
4689
- if (!sawResponseDelta && fallbackText.length > 0) {
4690
- pushDelta("response", fallbackText);
4691
- }
4692
- } else if (provider === "fireworks") {
4693
- if (request.tools && request.tools.length > 0) {
4694
- throw new Error(
4695
- "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
4696
- );
4697
- }
4698
- const fireworksMessages = toFireworksMessages(contents, {
4699
- responseMimeType: request.responseMimeType,
4700
- responseJsonSchema: request.responseJsonSchema
4701
- });
4702
- await runFireworksCall(async (client) => {
4703
- const responseFormat = request.responseJsonSchema ? {
4704
- type: "json_schema",
4705
- json_schema: {
4706
- name: "llm-response",
4707
- schema: request.responseJsonSchema
4708
- }
4709
- } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
4710
- const response = await client.chat.completions.create(
4711
- {
4712
- model: modelForProvider,
4713
- messages: fireworksMessages,
4714
- ...responseFormat ? { response_format: responseFormat } : {}
4715
- },
4716
- { signal }
4717
- );
4718
- modelVersion = typeof response.model === "string" ? response.model : request.model;
4719
- queue.push({ type: "model", modelVersion });
4720
- const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
4721
- if (choice?.finish_reason === "content_filter") {
4722
- blocked = true;
4723
- queue.push({ type: "blocked" });
4724
- }
4725
- const textOutput = extractFireworksMessageText(
4726
- choice?.message
4727
- );
4728
- if (textOutput.length > 0) {
4729
- pushDelta("response", textOutput);
4730
- }
4731
- latestUsage = extractFireworksUsageTokens(response.usage);
4732
- }, modelForProvider);
4733
- } else {
4734
- const geminiContents = contents.map(convertLlmContentToGeminiContent);
4735
- const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
4736
- const config = {
4737
- maxOutputTokens: 32e3,
4738
- ...thinkingConfig ? { thinkingConfig } : {},
4739
- ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
4740
- ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
4741
- ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
4742
- ...request.imageAspectRatio || request.imageSize ? {
4743
- imageConfig: {
4744
- ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
4745
- ...request.imageSize ? { imageSize: request.imageSize } : {}
4746
- }
4747
- } : {}
4748
- };
4749
- const geminiTools = toGeminiTools(request.tools);
4750
- if (geminiTools) {
4751
- config.tools = geminiTools;
4752
- }
4753
- await runGeminiCall(async (client) => {
4754
- const stream = await client.models.generateContentStream({
4755
- model: modelForProvider,
4756
- contents: geminiContents,
4757
- config
4758
- });
4759
- let latestGrounding;
4760
- for await (const chunk of stream) {
4761
- if (chunk.modelVersion) {
4762
- modelVersion = chunk.modelVersion;
4763
- queue.push({ type: "model", modelVersion });
4764
- }
4765
- if (chunk.promptFeedback?.blockReason) {
5279
+ } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
5280
+ const response = await client.chat.completions.create(
5281
+ {
5282
+ model: modelForProvider,
5283
+ messages: fireworksMessages,
5284
+ ...responseFormat ? { response_format: responseFormat } : {}
5285
+ },
5286
+ { signal }
5287
+ );
5288
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
5289
+ queue.push({ type: "model", modelVersion });
5290
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5291
+ if (choice?.finish_reason === "content_filter") {
4766
5292
  blocked = true;
4767
5293
  queue.push({ type: "blocked" });
4768
5294
  }
4769
- latestUsage = mergeTokenUpdates(latestUsage, extractGeminiUsageTokens(chunk.usageMetadata));
4770
- const candidates = chunk.candidates;
4771
- if (!candidates || candidates.length === 0) {
4772
- continue;
4773
- }
4774
- const primary = candidates[0];
4775
- if (primary && isModerationFinish(primary.finishReason)) {
4776
- blocked = true;
4777
- queue.push({ type: "blocked" });
5295
+ const textOutput = extractFireworksMessageText(
5296
+ choice?.message
5297
+ );
5298
+ if (textOutput.length > 0) {
5299
+ pushDelta("response", textOutput);
4778
5300
  }
4779
- for (const candidate of candidates) {
4780
- const candidateContent = candidate.content;
4781
- if (!candidateContent) {
4782
- continue;
5301
+ latestUsage = extractFireworksUsageTokens(response.usage);
5302
+ }, modelForProvider);
5303
+ } else {
5304
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
5305
+ const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
5306
+ const config = {
5307
+ maxOutputTokens: 32e3,
5308
+ ...thinkingConfig ? { thinkingConfig } : {},
5309
+ ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
5310
+ ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
5311
+ ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
5312
+ ...request.imageAspectRatio || request.imageSize ? {
5313
+ imageConfig: {
5314
+ ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
5315
+ ...request.imageSize ? { imageSize: request.imageSize } : {}
4783
5316
  }
4784
- if (candidate.groundingMetadata) {
4785
- latestGrounding = candidate.groundingMetadata;
5317
+ } : {}
5318
+ };
5319
+ const geminiTools = toGeminiTools(request.tools);
5320
+ if (geminiTools) {
5321
+ config.tools = geminiTools;
5322
+ }
5323
+ await runGeminiCall(async (client) => {
5324
+ const stream = await client.models.generateContentStream({
5325
+ model: modelForProvider,
5326
+ contents: geminiContents,
5327
+ config
5328
+ });
5329
+ let latestGrounding;
5330
+ for await (const chunk of stream) {
5331
+ if (chunk.modelVersion) {
5332
+ modelVersion = chunk.modelVersion;
5333
+ queue.push({ type: "model", modelVersion });
4786
5334
  }
4787
- const content2 = convertGeminiContentToLlmContent(candidateContent);
4788
- if (!responseRole) {
4789
- responseRole = content2.role;
5335
+ if (chunk.promptFeedback?.blockReason) {
5336
+ blocked = true;
5337
+ queue.push({ type: "blocked" });
4790
5338
  }
4791
- for (const part of content2.parts) {
4792
- if (part.type === "text") {
4793
- pushDelta(part.thought === true ? "thought" : "response", part.text);
4794
- } else {
4795
- pushInline(part.data, part.mimeType);
5339
+ latestUsage = mergeTokenUpdates(
5340
+ latestUsage,
5341
+ extractGeminiUsageTokens(chunk.usageMetadata)
5342
+ );
5343
+ const candidates = chunk.candidates;
5344
+ if (!candidates || candidates.length === 0) {
5345
+ continue;
5346
+ }
5347
+ const primary = candidates[0];
5348
+ if (primary && isModerationFinish(primary.finishReason)) {
5349
+ blocked = true;
5350
+ queue.push({ type: "blocked" });
5351
+ }
5352
+ for (const candidate of candidates) {
5353
+ const candidateContent = candidate.content;
5354
+ if (!candidateContent) {
5355
+ continue;
5356
+ }
5357
+ if (candidate.groundingMetadata) {
5358
+ latestGrounding = candidate.groundingMetadata;
5359
+ }
5360
+ const content2 = convertGeminiContentToLlmContent(candidateContent);
5361
+ if (!responseRole) {
5362
+ responseRole = content2.role;
5363
+ }
5364
+ for (const part of content2.parts) {
5365
+ if (part.type === "text") {
5366
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
5367
+ } else {
5368
+ pushInline(part.data, part.mimeType);
5369
+ }
4796
5370
  }
4797
5371
  }
4798
5372
  }
4799
- }
4800
- grounding = latestGrounding;
4801
- }, modelForProvider);
4802
- }
4803
- const mergedParts = mergeConsecutiveTextParts(responseParts);
4804
- const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
4805
- const { text, thoughts } = extractTextByChannel(content);
4806
- const costUsd = estimateCallCostUsd({
4807
- modelId: modelVersion,
4808
- tokens: latestUsage,
4809
- responseImages,
4810
- imageSize: request.imageSize
4811
- });
4812
- if (latestUsage) {
4813
- queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5373
+ grounding = latestGrounding;
5374
+ }, modelForProvider);
5375
+ }
5376
+ const mergedParts = mergeConsecutiveTextParts(responseParts);
5377
+ const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
5378
+ const { text, thoughts } = extractTextByChannel(content);
5379
+ const costUsd = estimateCallCostUsd({
5380
+ modelId: modelVersion,
5381
+ tokens: latestUsage,
5382
+ responseImages,
5383
+ imageSize: request.imageSize
5384
+ });
5385
+ if (latestUsage) {
5386
+ queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5387
+ }
5388
+ callLogger?.complete({
5389
+ provider,
5390
+ model: request.model,
5391
+ modelVersion,
5392
+ blocked,
5393
+ costUsd,
5394
+ usage: latestUsage,
5395
+ grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5396
+ responseChars: text.length,
5397
+ thoughtChars: thoughts.length,
5398
+ responseImages
5399
+ });
5400
+ return {
5401
+ provider,
5402
+ model: request.model,
5403
+ modelVersion,
5404
+ content,
5405
+ text,
5406
+ thoughts,
5407
+ blocked,
5408
+ usage: latestUsage,
5409
+ costUsd,
5410
+ grounding
5411
+ };
5412
+ } catch (error) {
5413
+ callLogger?.fail(error, {
5414
+ provider,
5415
+ model: request.model,
5416
+ modelVersion,
5417
+ blocked,
5418
+ usage: latestUsage,
5419
+ partialResponseParts: responseParts.length,
5420
+ responseImages
5421
+ });
5422
+ throw error;
4814
5423
  }
4815
- return {
4816
- provider,
4817
- model: request.model,
4818
- modelVersion,
4819
- content,
4820
- text,
4821
- thoughts,
4822
- blocked,
4823
- usage: latestUsage,
4824
- costUsd,
4825
- grounding
4826
- };
4827
5424
  }
4828
5425
  function streamText(request) {
4829
5426
  const queue = createAsyncQueue();
@@ -5286,6 +5883,23 @@ async function runToolLoop(request) {
5286
5883
  let modelVersion = request.model;
5287
5884
  let usageTokens;
5288
5885
  let thoughtDeltaEmitted = false;
5886
+ let blocked = false;
5887
+ const stepRequestPayload = {
5888
+ model: providerInfo.model,
5889
+ input,
5890
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5891
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5892
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5893
+ reasoning,
5894
+ text: textConfig,
5895
+ include: ["reasoning.encrypted_content"]
5896
+ };
5897
+ const stepCallLogger = startLlmCallLoggerFromPayload({
5898
+ provider: "openai",
5899
+ modelId: request.model,
5900
+ requestPayload: stepRequestPayload,
5901
+ step: turn
5902
+ });
5289
5903
  const emitEvent = (ev) => {
5290
5904
  onEvent?.(ev);
5291
5905
  };
@@ -5294,226 +5908,276 @@ async function runToolLoop(request) {
5294
5908
  firstModelEventAtMs = Date.now();
5295
5909
  }
5296
5910
  };
5297
- const finalResponse = await runOpenAiCall(
5298
- async (client) => {
5299
- const stream = client.responses.stream(
5300
- {
5301
- model: providerInfo.model,
5302
- input,
5303
- ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5304
- ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5305
- ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5306
- reasoning,
5307
- text: textConfig,
5308
- include: ["reasoning.encrypted_content"]
5309
- },
5310
- { signal: abortController.signal }
5311
- );
5312
- for await (const event of stream) {
5313
- markFirstModelEvent();
5314
- switch (event.type) {
5315
- case "response.output_text.delta":
5316
- emitEvent({
5317
- type: "delta",
5318
- channel: "response",
5319
- text: typeof event.delta === "string" ? event.delta : ""
5320
- });
5321
- break;
5322
- case "response.reasoning_summary_text.delta":
5323
- thoughtDeltaEmitted = true;
5324
- emitEvent({
5325
- type: "delta",
5326
- channel: "thought",
5327
- text: typeof event.delta === "string" ? event.delta : ""
5328
- });
5329
- break;
5330
- case "response.refusal.delta":
5331
- emitEvent({ type: "blocked" });
5332
- break;
5333
- default:
5334
- break;
5911
+ try {
5912
+ const finalResponse = await runOpenAiCall(
5913
+ async (client) => {
5914
+ const stream = client.responses.stream(
5915
+ {
5916
+ model: providerInfo.model,
5917
+ input,
5918
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5919
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5920
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5921
+ reasoning,
5922
+ text: textConfig,
5923
+ include: ["reasoning.encrypted_content"]
5924
+ },
5925
+ { signal: abortController.signal }
5926
+ );
5927
+ for await (const event of stream) {
5928
+ markFirstModelEvent();
5929
+ switch (event.type) {
5930
+ case "response.output_text.delta": {
5931
+ const text = typeof event.delta === "string" ? event.delta : "";
5932
+ if (text.length > 0) {
5933
+ stepCallLogger?.appendResponseDelta(text);
5934
+ }
5935
+ emitEvent({
5936
+ type: "delta",
5937
+ channel: "response",
5938
+ text
5939
+ });
5940
+ break;
5941
+ }
5942
+ case "response.reasoning_summary_text.delta": {
5943
+ thoughtDeltaEmitted = true;
5944
+ const text = typeof event.delta === "string" ? event.delta : "";
5945
+ if (text.length > 0) {
5946
+ stepCallLogger?.appendThoughtDelta(text);
5947
+ }
5948
+ emitEvent({
5949
+ type: "delta",
5950
+ channel: "thought",
5951
+ text
5952
+ });
5953
+ break;
5954
+ }
5955
+ case "response.refusal.delta":
5956
+ blocked = true;
5957
+ emitEvent({ type: "blocked" });
5958
+ break;
5959
+ default:
5960
+ break;
5961
+ }
5962
+ }
5963
+ return await stream.finalResponse();
5964
+ },
5965
+ providerInfo.model,
5966
+ {
5967
+ onSettled: (metrics) => {
5968
+ schedulerMetrics = metrics;
5335
5969
  }
5336
5970
  }
5337
- return await stream.finalResponse();
5338
- },
5339
- providerInfo.model,
5340
- {
5341
- onSettled: (metrics) => {
5342
- schedulerMetrics = metrics;
5971
+ );
5972
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5973
+ emitEvent({ type: "model", modelVersion });
5974
+ if (finalResponse.error) {
5975
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5976
+ throw new Error(message);
5977
+ }
5978
+ usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
5979
+ const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
5980
+ const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
5981
+ if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
5982
+ stepCallLogger?.appendThoughtDelta(reasoningSummary);
5983
+ emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
5984
+ }
5985
+ const modelCompletedAtMs = Date.now();
5986
+ const stepCostUsd = estimateCallCostUsd({
5987
+ modelId: modelVersion,
5988
+ tokens: usageTokens,
5989
+ responseImages: 0
5990
+ });
5991
+ totalCostUsd += stepCostUsd;
5992
+ if (usageTokens) {
5993
+ emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
5994
+ }
5995
+ const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
5996
+ const stepToolCalls = [];
5997
+ if (responseToolCalls.length === 0) {
5998
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5999
+ const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2) : [];
6000
+ finalText = responseText;
6001
+ finalThoughts = reasoningSummary;
6002
+ const stepCompletedAtMs2 = Date.now();
6003
+ const timing2 = buildStepTiming({
6004
+ stepStartedAtMs,
6005
+ stepCompletedAtMs: stepCompletedAtMs2,
6006
+ modelCompletedAtMs,
6007
+ firstModelEventAtMs,
6008
+ schedulerMetrics,
6009
+ toolExecutionMs: 0,
6010
+ waitToolMs: 0
6011
+ });
6012
+ steps.push({
6013
+ step: steps.length + 1,
6014
+ modelVersion,
6015
+ text: responseText || void 0,
6016
+ thoughts: reasoningSummary || void 0,
6017
+ toolCalls: [],
6018
+ usage: usageTokens,
6019
+ costUsd: stepCostUsd,
6020
+ timing: timing2
6021
+ });
6022
+ stepCallLogger?.complete({
6023
+ provider: "openai",
6024
+ model: request.model,
6025
+ modelVersion,
6026
+ step: turn,
6027
+ usage: usageTokens,
6028
+ costUsd: stepCostUsd,
6029
+ blocked,
6030
+ responseChars: responseText.length,
6031
+ thoughtChars: reasoningSummary.length,
6032
+ toolCalls: 0,
6033
+ finalStep: steeringItems2.length === 0
6034
+ });
6035
+ if (steeringItems2.length === 0) {
6036
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5343
6037
  }
6038
+ previousResponseId = finalResponse.id;
6039
+ input = steeringItems2;
6040
+ continue;
5344
6041
  }
5345
- );
5346
- modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5347
- emitEvent({ type: "model", modelVersion });
5348
- if (finalResponse.error) {
5349
- const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5350
- throw new Error(message);
5351
- }
5352
- usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
5353
- const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
5354
- const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
5355
- if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
5356
- emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
5357
- }
5358
- const modelCompletedAtMs = Date.now();
5359
- const stepCostUsd = estimateCallCostUsd({
5360
- modelId: modelVersion,
5361
- tokens: usageTokens,
5362
- responseImages: 0
5363
- });
5364
- totalCostUsd += stepCostUsd;
5365
- if (usageTokens) {
5366
- emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
5367
- }
5368
- const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
5369
- const stepToolCalls = [];
5370
- if (responseToolCalls.length === 0) {
5371
- const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5372
- const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2) : [];
5373
- finalText = responseText;
5374
- finalThoughts = reasoningSummary;
5375
- const stepCompletedAtMs2 = Date.now();
5376
- const timing2 = buildStepTiming({
6042
+ const callInputs = responseToolCalls.map((call, index) => {
6043
+ const toolIndex = index + 1;
6044
+ const toolId = buildToolLogId(turn, toolIndex);
6045
+ const toolName = call.name;
6046
+ if (call.kind === "custom") {
6047
+ return {
6048
+ call,
6049
+ toolName,
6050
+ value: call.input,
6051
+ parseError: void 0,
6052
+ toolId,
6053
+ turn,
6054
+ toolIndex
6055
+ };
6056
+ }
6057
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
6058
+ return { call, toolName, value, parseError, toolId, turn, toolIndex };
6059
+ });
6060
+ for (const entry of callInputs) {
6061
+ emitEvent({
6062
+ type: "tool_call",
6063
+ phase: "started",
6064
+ turn: entry.turn,
6065
+ toolIndex: entry.toolIndex,
6066
+ toolName: entry.toolName,
6067
+ toolId: entry.toolId,
6068
+ callKind: entry.call.kind,
6069
+ callId: entry.call.call_id,
6070
+ input: entry.value
6071
+ });
6072
+ }
6073
+ const callResults = await Promise.all(
6074
+ callInputs.map(async (entry) => {
6075
+ return await toolCallContextStorage.run(
6076
+ {
6077
+ toolName: entry.toolName,
6078
+ toolId: entry.toolId,
6079
+ turn: entry.turn,
6080
+ toolIndex: entry.toolIndex
6081
+ },
6082
+ async () => {
6083
+ const { result, outputPayload } = await executeToolCall({
6084
+ callKind: entry.call.kind,
6085
+ toolName: entry.toolName,
6086
+ tool: request.tools[entry.toolName],
6087
+ rawInput: entry.value,
6088
+ parseError: entry.parseError
6089
+ });
6090
+ return { entry, result, outputPayload };
6091
+ }
6092
+ );
6093
+ })
6094
+ );
6095
+ const toolOutputs = [];
6096
+ let toolExecutionMs = 0;
6097
+ let waitToolMs = 0;
6098
+ for (const { entry, result, outputPayload } of callResults) {
6099
+ stepToolCalls.push({ ...result, callId: entry.call.call_id });
6100
+ const callDurationMs = toToolResultDuration(result);
6101
+ toolExecutionMs += callDurationMs;
6102
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6103
+ waitToolMs += callDurationMs;
6104
+ }
6105
+ emitEvent({
6106
+ type: "tool_call",
6107
+ phase: "completed",
6108
+ turn: entry.turn,
6109
+ toolIndex: entry.toolIndex,
6110
+ toolName: entry.toolName,
6111
+ toolId: entry.toolId,
6112
+ callKind: entry.call.kind,
6113
+ callId: entry.call.call_id,
6114
+ input: entry.value,
6115
+ output: result.output,
6116
+ error: result.error,
6117
+ durationMs: result.durationMs
6118
+ });
6119
+ if (entry.call.kind === "custom") {
6120
+ toolOutputs.push({
6121
+ type: "custom_tool_call_output",
6122
+ call_id: entry.call.call_id,
6123
+ output: toOpenAiToolOutput(outputPayload)
6124
+ });
6125
+ } else {
6126
+ toolOutputs.push({
6127
+ type: "function_call_output",
6128
+ call_id: entry.call.call_id,
6129
+ output: toOpenAiToolOutput(outputPayload)
6130
+ });
6131
+ }
6132
+ }
6133
+ const stepCompletedAtMs = Date.now();
6134
+ const timing = buildStepTiming({
5377
6135
  stepStartedAtMs,
5378
- stepCompletedAtMs: stepCompletedAtMs2,
6136
+ stepCompletedAtMs,
5379
6137
  modelCompletedAtMs,
5380
6138
  firstModelEventAtMs,
5381
6139
  schedulerMetrics,
5382
- toolExecutionMs: 0,
5383
- waitToolMs: 0
6140
+ toolExecutionMs,
6141
+ waitToolMs
5384
6142
  });
5385
6143
  steps.push({
5386
6144
  step: steps.length + 1,
5387
6145
  modelVersion,
5388
6146
  text: responseText || void 0,
5389
6147
  thoughts: reasoningSummary || void 0,
5390
- toolCalls: [],
6148
+ toolCalls: stepToolCalls,
5391
6149
  usage: usageTokens,
5392
6150
  costUsd: stepCostUsd,
5393
- timing: timing2
6151
+ timing
5394
6152
  });
5395
- if (steeringItems2.length === 0) {
5396
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5397
- }
5398
- previousResponseId = finalResponse.id;
5399
- input = steeringItems2;
5400
- continue;
5401
- }
5402
- const callInputs = responseToolCalls.map((call, index) => {
5403
- const toolIndex = index + 1;
5404
- const toolId = buildToolLogId(turn, toolIndex);
5405
- const toolName = call.name;
5406
- if (call.kind === "custom") {
5407
- return {
5408
- call,
5409
- toolName,
5410
- value: call.input,
5411
- parseError: void 0,
5412
- toolId,
5413
- turn,
5414
- toolIndex
5415
- };
5416
- }
5417
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5418
- return { call, toolName, value, parseError, toolId, turn, toolIndex };
5419
- });
5420
- for (const entry of callInputs) {
5421
- emitEvent({
5422
- type: "tool_call",
5423
- phase: "started",
5424
- turn: entry.turn,
5425
- toolIndex: entry.toolIndex,
5426
- toolName: entry.toolName,
5427
- toolId: entry.toolId,
5428
- callKind: entry.call.kind,
5429
- callId: entry.call.call_id,
5430
- input: entry.value
6153
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6154
+ const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
6155
+ stepCallLogger?.complete({
6156
+ provider: "openai",
6157
+ model: request.model,
6158
+ modelVersion,
6159
+ step: turn,
6160
+ usage: usageTokens,
6161
+ costUsd: stepCostUsd,
6162
+ blocked,
6163
+ responseChars: responseText.length,
6164
+ thoughtChars: reasoningSummary.length,
6165
+ toolCalls: stepToolCalls.length,
6166
+ finalStep: false
5431
6167
  });
5432
- }
5433
- const callResults = await Promise.all(
5434
- callInputs.map(async (entry) => {
5435
- return await toolCallContextStorage.run(
5436
- {
5437
- toolName: entry.toolName,
5438
- toolId: entry.toolId,
5439
- turn: entry.turn,
5440
- toolIndex: entry.toolIndex
5441
- },
5442
- async () => {
5443
- const { result, outputPayload } = await executeToolCall({
5444
- callKind: entry.call.kind,
5445
- toolName: entry.toolName,
5446
- tool: request.tools[entry.toolName],
5447
- rawInput: entry.value,
5448
- parseError: entry.parseError
5449
- });
5450
- return { entry, result, outputPayload };
5451
- }
5452
- );
5453
- })
5454
- );
5455
- const toolOutputs = [];
5456
- let toolExecutionMs = 0;
5457
- let waitToolMs = 0;
5458
- for (const { entry, result, outputPayload } of callResults) {
5459
- stepToolCalls.push({ ...result, callId: entry.call.call_id });
5460
- const callDurationMs = toToolResultDuration(result);
5461
- toolExecutionMs += callDurationMs;
5462
- if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5463
- waitToolMs += callDurationMs;
5464
- }
5465
- emitEvent({
5466
- type: "tool_call",
5467
- phase: "completed",
5468
- turn: entry.turn,
5469
- toolIndex: entry.toolIndex,
5470
- toolName: entry.toolName,
5471
- toolId: entry.toolId,
5472
- callKind: entry.call.kind,
5473
- callId: entry.call.call_id,
5474
- input: entry.value,
5475
- output: result.output,
5476
- error: result.error,
5477
- durationMs: result.durationMs
6168
+ previousResponseId = finalResponse.id;
6169
+ input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
6170
+ } catch (error) {
6171
+ stepCallLogger?.fail(error, {
6172
+ provider: "openai",
6173
+ model: request.model,
6174
+ modelVersion,
6175
+ step: turn,
6176
+ usage: usageTokens,
6177
+ blocked
5478
6178
  });
5479
- if (entry.call.kind === "custom") {
5480
- toolOutputs.push({
5481
- type: "custom_tool_call_output",
5482
- call_id: entry.call.call_id,
5483
- output: toOpenAiToolOutput(outputPayload)
5484
- });
5485
- } else {
5486
- toolOutputs.push({
5487
- type: "function_call_output",
5488
- call_id: entry.call.call_id,
5489
- output: toOpenAiToolOutput(outputPayload)
5490
- });
5491
- }
6179
+ throw error;
5492
6180
  }
5493
- const stepCompletedAtMs = Date.now();
5494
- const timing = buildStepTiming({
5495
- stepStartedAtMs,
5496
- stepCompletedAtMs,
5497
- modelCompletedAtMs,
5498
- firstModelEventAtMs,
5499
- schedulerMetrics,
5500
- toolExecutionMs,
5501
- waitToolMs
5502
- });
5503
- steps.push({
5504
- step: steps.length + 1,
5505
- modelVersion,
5506
- text: responseText || void 0,
5507
- thoughts: reasoningSummary || void 0,
5508
- toolCalls: stepToolCalls,
5509
- usage: usageTokens,
5510
- costUsd: stepCostUsd,
5511
- timing
5512
- });
5513
- const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5514
- const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
5515
- previousResponseId = finalResponse.id;
5516
- input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
5517
6181
  }
5518
6182
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5519
6183
  }
@@ -5531,242 +6195,655 @@ async function runToolLoop(request) {
5531
6195
  const stepStartedAtMs = Date.now();
5532
6196
  let firstModelEventAtMs;
5533
6197
  let thoughtDeltaEmitted = false;
6198
+ let sawResponseDelta = false;
6199
+ let modelVersion = request.model;
6200
+ let usageTokens;
6201
+ let responseText = "";
6202
+ let reasoningSummaryText = "";
5534
6203
  const markFirstModelEvent = () => {
5535
6204
  if (firstModelEventAtMs === void 0) {
5536
6205
  firstModelEventAtMs = Date.now();
5537
6206
  }
5538
6207
  };
5539
- const response = await collectChatGptCodexResponseWithRetry({
5540
- sessionId: conversationId,
5541
- request: {
5542
- model: providerInfo.model,
5543
- store: false,
5544
- stream: true,
5545
- instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
5546
- input,
5547
- prompt_cache_key: promptCacheKey,
5548
- include: ["reasoning.encrypted_content"],
5549
- tools: openAiTools,
5550
- tool_choice: "auto",
5551
- parallel_tool_calls: true,
5552
- reasoning: {
5553
- effort: toOpenAiReasoningEffort(reasoningEffort),
5554
- summary: "detailed"
5555
- },
5556
- text: { verbosity: resolveOpenAiVerbosity(request.model) }
6208
+ const stepRequestPayload = {
6209
+ model: providerInfo.model,
6210
+ store: false,
6211
+ stream: true,
6212
+ instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
6213
+ input,
6214
+ prompt_cache_key: promptCacheKey,
6215
+ include: ["reasoning.encrypted_content"],
6216
+ tools: openAiTools,
6217
+ tool_choice: "auto",
6218
+ parallel_tool_calls: true,
6219
+ reasoning: {
6220
+ effort: toOpenAiReasoningEffort(reasoningEffort),
6221
+ summary: "detailed"
5557
6222
  },
5558
- signal: request.signal,
5559
- onDelta: (delta) => {
5560
- if (delta.thoughtDelta) {
5561
- markFirstModelEvent();
5562
- thoughtDeltaEmitted = true;
5563
- request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
6223
+ text: { verbosity: resolveOpenAiVerbosity(request.model) }
6224
+ };
6225
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6226
+ provider: "chatgpt",
6227
+ modelId: request.model,
6228
+ requestPayload: stepRequestPayload,
6229
+ step: turn
6230
+ });
6231
+ try {
6232
+ const response = await collectChatGptCodexResponseWithRetry({
6233
+ sessionId: conversationId,
6234
+ request: stepRequestPayload,
6235
+ signal: request.signal,
6236
+ onDelta: (delta) => {
6237
+ if (delta.thoughtDelta) {
6238
+ markFirstModelEvent();
6239
+ thoughtDeltaEmitted = true;
6240
+ stepCallLogger?.appendThoughtDelta(delta.thoughtDelta);
6241
+ request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
6242
+ }
6243
+ if (delta.textDelta) {
6244
+ markFirstModelEvent();
6245
+ sawResponseDelta = true;
6246
+ stepCallLogger?.appendResponseDelta(delta.textDelta);
6247
+ request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
6248
+ }
5564
6249
  }
5565
- if (delta.textDelta) {
5566
- markFirstModelEvent();
5567
- request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
6250
+ });
6251
+ const modelCompletedAtMs = Date.now();
6252
+ modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
6253
+ usageTokens = extractChatGptUsageTokens(response.usage);
6254
+ const stepCostUsd = estimateCallCostUsd({
6255
+ modelId: modelVersion,
6256
+ tokens: usageTokens,
6257
+ responseImages: 0
6258
+ });
6259
+ totalCostUsd += stepCostUsd;
6260
+ responseText = (response.text ?? "").trim();
6261
+ reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
6262
+ if (!thoughtDeltaEmitted && reasoningSummaryText.length > 0) {
6263
+ stepCallLogger?.appendThoughtDelta(reasoningSummaryText);
6264
+ request.onEvent?.({ type: "delta", channel: "thought", text: reasoningSummaryText });
6265
+ }
6266
+ if (!sawResponseDelta && responseText.length > 0) {
6267
+ stepCallLogger?.appendResponseDelta(responseText);
6268
+ }
6269
+ const responseToolCalls = response.toolCalls ?? [];
6270
+ if (responseToolCalls.length === 0) {
6271
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6272
+ const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
6273
+ finalText = responseText;
6274
+ finalThoughts = reasoningSummaryText;
6275
+ const stepCompletedAtMs2 = Date.now();
6276
+ const timing2 = buildStepTiming({
6277
+ stepStartedAtMs,
6278
+ stepCompletedAtMs: stepCompletedAtMs2,
6279
+ modelCompletedAtMs,
6280
+ firstModelEventAtMs,
6281
+ toolExecutionMs: 0,
6282
+ waitToolMs: 0
6283
+ });
6284
+ steps.push({
6285
+ step: steps.length + 1,
6286
+ modelVersion,
6287
+ text: responseText || void 0,
6288
+ thoughts: reasoningSummaryText || void 0,
6289
+ toolCalls: [],
6290
+ usage: usageTokens,
6291
+ costUsd: stepCostUsd,
6292
+ timing: timing2
6293
+ });
6294
+ stepCallLogger?.complete({
6295
+ provider: "chatgpt",
6296
+ model: request.model,
6297
+ modelVersion,
6298
+ step: turn,
6299
+ usage: usageTokens,
6300
+ costUsd: stepCostUsd,
6301
+ responseChars: responseText.length,
6302
+ thoughtChars: reasoningSummaryText.length,
6303
+ toolCalls: 0,
6304
+ finalStep: steeringItems2.length === 0
6305
+ });
6306
+ if (steeringItems2.length === 0) {
6307
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5568
6308
  }
6309
+ const assistantItem = toChatGptAssistantMessage(responseText);
6310
+ input = assistantItem ? input.concat(assistantItem, steeringItems2) : input.concat(steeringItems2);
6311
+ continue;
5569
6312
  }
5570
- });
5571
- const modelCompletedAtMs = Date.now();
5572
- const modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
5573
- const usageTokens = extractChatGptUsageTokens(response.usage);
5574
- const stepCostUsd = estimateCallCostUsd({
5575
- modelId: modelVersion,
5576
- tokens: usageTokens,
5577
- responseImages: 0
5578
- });
5579
- totalCostUsd += stepCostUsd;
5580
- const responseText = (response.text ?? "").trim();
5581
- const reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
5582
- if (!thoughtDeltaEmitted && reasoningSummaryText.length > 0) {
5583
- request.onEvent?.({ type: "delta", channel: "thought", text: reasoningSummaryText });
5584
- }
5585
- const responseToolCalls = response.toolCalls ?? [];
5586
- if (responseToolCalls.length === 0) {
5587
- const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5588
- const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
5589
- finalText = responseText;
5590
- finalThoughts = reasoningSummaryText;
5591
- const stepCompletedAtMs2 = Date.now();
5592
- const timing2 = buildStepTiming({
6313
+ const toolCalls = [];
6314
+ const toolOutputs = [];
6315
+ const callInputs = responseToolCalls.map((call, index) => {
6316
+ const toolIndex = index + 1;
6317
+ const toolId = buildToolLogId(turn, toolIndex);
6318
+ const toolName = call.name;
6319
+ const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
6320
+ const ids = normalizeChatGptToolIds({
6321
+ callKind: call.kind,
6322
+ callId: call.callId,
6323
+ itemId: call.id
6324
+ });
6325
+ return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
6326
+ });
6327
+ for (const entry of callInputs) {
6328
+ request.onEvent?.({
6329
+ type: "tool_call",
6330
+ phase: "started",
6331
+ turn: entry.turn,
6332
+ toolIndex: entry.toolIndex,
6333
+ toolName: entry.toolName,
6334
+ toolId: entry.toolId,
6335
+ callKind: entry.call.kind,
6336
+ callId: entry.ids.callId,
6337
+ input: entry.value
6338
+ });
6339
+ }
6340
+ const callResults = await Promise.all(
6341
+ callInputs.map(async (entry) => {
6342
+ return await toolCallContextStorage.run(
6343
+ {
6344
+ toolName: entry.toolName,
6345
+ toolId: entry.toolId,
6346
+ turn: entry.turn,
6347
+ toolIndex: entry.toolIndex
6348
+ },
6349
+ async () => {
6350
+ const { result, outputPayload } = await executeToolCall({
6351
+ callKind: entry.call.kind,
6352
+ toolName: entry.toolName,
6353
+ tool: request.tools[entry.toolName],
6354
+ rawInput: entry.value,
6355
+ parseError: entry.parseError
6356
+ });
6357
+ return { entry, result, outputPayload };
6358
+ }
6359
+ );
6360
+ })
6361
+ );
6362
+ let toolExecutionMs = 0;
6363
+ let waitToolMs = 0;
6364
+ for (const { entry, result, outputPayload } of callResults) {
6365
+ toolCalls.push({ ...result, callId: entry.ids.callId });
6366
+ const callDurationMs = toToolResultDuration(result);
6367
+ toolExecutionMs += callDurationMs;
6368
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6369
+ waitToolMs += callDurationMs;
6370
+ }
6371
+ request.onEvent?.({
6372
+ type: "tool_call",
6373
+ phase: "completed",
6374
+ turn: entry.turn,
6375
+ toolIndex: entry.toolIndex,
6376
+ toolName: entry.toolName,
6377
+ toolId: entry.toolId,
6378
+ callKind: entry.call.kind,
6379
+ callId: entry.ids.callId,
6380
+ input: entry.value,
6381
+ output: result.output,
6382
+ error: result.error,
6383
+ durationMs: result.durationMs
6384
+ });
6385
+ if (entry.call.kind === "custom") {
6386
+ toolOutputs.push({
6387
+ type: "custom_tool_call",
6388
+ id: entry.ids.itemId,
6389
+ call_id: entry.ids.callId,
6390
+ name: entry.toolName,
6391
+ input: entry.call.input,
6392
+ status: "completed"
6393
+ });
6394
+ toolOutputs.push({
6395
+ type: "custom_tool_call_output",
6396
+ call_id: entry.ids.callId,
6397
+ output: toOpenAiToolOutput(outputPayload)
6398
+ });
6399
+ } else {
6400
+ toolOutputs.push({
6401
+ type: "function_call",
6402
+ id: entry.ids.itemId,
6403
+ call_id: entry.ids.callId,
6404
+ name: entry.toolName,
6405
+ arguments: entry.call.arguments,
6406
+ status: "completed"
6407
+ });
6408
+ toolOutputs.push({
6409
+ type: "function_call_output",
6410
+ call_id: entry.ids.callId,
6411
+ output: toOpenAiToolOutput(outputPayload)
6412
+ });
6413
+ }
6414
+ }
6415
+ const stepCompletedAtMs = Date.now();
6416
+ const timing = buildStepTiming({
5593
6417
  stepStartedAtMs,
5594
- stepCompletedAtMs: stepCompletedAtMs2,
6418
+ stepCompletedAtMs,
5595
6419
  modelCompletedAtMs,
5596
6420
  firstModelEventAtMs,
5597
- toolExecutionMs: 0,
5598
- waitToolMs: 0
6421
+ toolExecutionMs,
6422
+ waitToolMs
5599
6423
  });
5600
6424
  steps.push({
5601
6425
  step: steps.length + 1,
5602
6426
  modelVersion,
5603
6427
  text: responseText || void 0,
5604
6428
  thoughts: reasoningSummaryText || void 0,
5605
- toolCalls: [],
6429
+ toolCalls,
5606
6430
  usage: usageTokens,
5607
6431
  costUsd: stepCostUsd,
5608
- timing: timing2
6432
+ timing
5609
6433
  });
5610
- if (steeringItems2.length === 0) {
5611
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5612
- }
5613
- const assistantItem = toChatGptAssistantMessage(responseText);
5614
- input = assistantItem ? input.concat(assistantItem, steeringItems2) : input.concat(steeringItems2);
5615
- continue;
5616
- }
5617
- const toolCalls = [];
5618
- const toolOutputs = [];
5619
- const callInputs = responseToolCalls.map((call, index) => {
5620
- const toolIndex = index + 1;
5621
- const toolId = buildToolLogId(turn, toolIndex);
5622
- const toolName = call.name;
5623
- const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
5624
- const ids = normalizeChatGptToolIds({
5625
- callKind: call.kind,
5626
- callId: call.callId,
5627
- itemId: call.id
6434
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6435
+ const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
6436
+ stepCallLogger?.complete({
6437
+ provider: "chatgpt",
6438
+ model: request.model,
6439
+ modelVersion,
6440
+ step: turn,
6441
+ usage: usageTokens,
6442
+ costUsd: stepCostUsd,
6443
+ responseChars: responseText.length,
6444
+ thoughtChars: reasoningSummaryText.length,
6445
+ toolCalls: toolCalls.length,
6446
+ finalStep: false
5628
6447
  });
5629
- return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
5630
- });
5631
- for (const entry of callInputs) {
5632
- request.onEvent?.({
5633
- type: "tool_call",
5634
- phase: "started",
5635
- turn: entry.turn,
5636
- toolIndex: entry.toolIndex,
5637
- toolName: entry.toolName,
5638
- toolId: entry.toolId,
5639
- callKind: entry.call.kind,
5640
- callId: entry.ids.callId,
5641
- input: entry.value
6448
+ input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
6449
+ } catch (error) {
6450
+ stepCallLogger?.fail(error, {
6451
+ provider: "chatgpt",
6452
+ model: request.model,
6453
+ modelVersion,
6454
+ step: turn,
6455
+ usage: usageTokens
5642
6456
  });
6457
+ throw error;
5643
6458
  }
5644
- const callResults = await Promise.all(
5645
- callInputs.map(async (entry) => {
5646
- return await toolCallContextStorage.run(
5647
- {
5648
- toolName: entry.toolName,
5649
- toolId: entry.toolId,
5650
- turn: entry.turn,
5651
- toolIndex: entry.toolIndex
5652
- },
5653
- async () => {
5654
- const { result, outputPayload } = await executeToolCall({
5655
- callKind: entry.call.kind,
5656
- toolName: entry.toolName,
5657
- tool: request.tools[entry.toolName],
5658
- rawInput: entry.value,
5659
- parseError: entry.parseError
5660
- });
5661
- return { entry, result, outputPayload };
5662
- }
5663
- );
5664
- })
6459
+ }
6460
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
6461
+ }
6462
+ if (providerInfo.provider === "fireworks") {
6463
+ if (request.modelTools && request.modelTools.length > 0) {
6464
+ throw new Error(
6465
+ "Fireworks provider does not support provider-native modelTools in runToolLoop."
5665
6466
  );
5666
- let toolExecutionMs = 0;
5667
- let waitToolMs = 0;
5668
- for (const { entry, result, outputPayload } of callResults) {
5669
- toolCalls.push({ ...result, callId: entry.ids.callId });
5670
- const callDurationMs = toToolResultDuration(result);
5671
- toolExecutionMs += callDurationMs;
5672
- if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5673
- waitToolMs += callDurationMs;
6467
+ }
6468
+ const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
6469
+ const messages = toFireworksMessages(contents);
6470
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
6471
+ const turn = stepIndex + 1;
6472
+ const stepStartedAtMs = Date.now();
6473
+ let schedulerMetrics;
6474
+ let modelVersion = request.model;
6475
+ let usageTokens;
6476
+ let responseText = "";
6477
+ let blocked = false;
6478
+ const stepRequestPayload = {
6479
+ model: providerInfo.model,
6480
+ messages,
6481
+ tools: fireworksTools,
6482
+ tool_choice: "auto",
6483
+ parallel_tool_calls: true
6484
+ };
6485
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6486
+ provider: "fireworks",
6487
+ modelId: request.model,
6488
+ requestPayload: stepRequestPayload,
6489
+ step: turn
6490
+ });
6491
+ try {
6492
+ const response = await runFireworksCall(
6493
+ async (client) => {
6494
+ return await client.chat.completions.create(
6495
+ {
6496
+ model: providerInfo.model,
6497
+ messages,
6498
+ tools: fireworksTools,
6499
+ tool_choice: "auto",
6500
+ parallel_tool_calls: true
6501
+ },
6502
+ { signal: request.signal }
6503
+ );
6504
+ },
6505
+ providerInfo.model,
6506
+ {
6507
+ onSettled: (metrics) => {
6508
+ schedulerMetrics = metrics;
6509
+ }
6510
+ }
6511
+ );
6512
+ const modelCompletedAtMs = Date.now();
6513
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
6514
+ request.onEvent?.({ type: "model", modelVersion });
6515
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
6516
+ if (choice?.finish_reason === "content_filter") {
6517
+ blocked = true;
6518
+ request.onEvent?.({ type: "blocked" });
5674
6519
  }
5675
- request.onEvent?.({
5676
- type: "tool_call",
5677
- phase: "completed",
5678
- turn: entry.turn,
5679
- toolIndex: entry.toolIndex,
5680
- toolName: entry.toolName,
5681
- toolId: entry.toolId,
5682
- callKind: entry.call.kind,
5683
- callId: entry.ids.callId,
5684
- input: entry.value,
5685
- output: result.output,
5686
- error: result.error,
5687
- durationMs: result.durationMs
6520
+ const message = choice?.message;
6521
+ responseText = extractFireworksMessageText(message).trim();
6522
+ if (responseText.length > 0) {
6523
+ stepCallLogger?.appendResponseDelta(responseText);
6524
+ request.onEvent?.({ type: "delta", channel: "response", text: responseText });
6525
+ }
6526
+ usageTokens = extractFireworksUsageTokens(response.usage);
6527
+ const stepCostUsd = estimateCallCostUsd({
6528
+ modelId: modelVersion,
6529
+ tokens: usageTokens,
6530
+ responseImages: 0
5688
6531
  });
5689
- if (entry.call.kind === "custom") {
5690
- toolOutputs.push({
5691
- type: "custom_tool_call",
5692
- id: entry.ids.itemId,
5693
- call_id: entry.ids.callId,
5694
- name: entry.toolName,
5695
- input: entry.call.input,
5696
- status: "completed"
6532
+ totalCostUsd += stepCostUsd;
6533
+ if (usageTokens) {
6534
+ request.onEvent?.({
6535
+ type: "usage",
6536
+ usage: usageTokens,
6537
+ costUsd: stepCostUsd,
6538
+ modelVersion
5697
6539
  });
5698
- toolOutputs.push({
5699
- type: "custom_tool_call_output",
5700
- call_id: entry.ids.callId,
5701
- output: toOpenAiToolOutput(outputPayload)
6540
+ }
6541
+ const responseToolCalls = extractFireworksToolCalls(message);
6542
+ if (responseToolCalls.length === 0) {
6543
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6544
+ const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
6545
+ finalText = responseText;
6546
+ finalThoughts = "";
6547
+ const stepCompletedAtMs2 = Date.now();
6548
+ const timing2 = buildStepTiming({
6549
+ stepStartedAtMs,
6550
+ stepCompletedAtMs: stepCompletedAtMs2,
6551
+ modelCompletedAtMs,
6552
+ schedulerMetrics,
6553
+ toolExecutionMs: 0,
6554
+ waitToolMs: 0
5702
6555
  });
5703
- } else {
5704
- toolOutputs.push({
5705
- type: "function_call",
5706
- id: entry.ids.itemId,
5707
- call_id: entry.ids.callId,
5708
- name: entry.toolName,
5709
- arguments: entry.call.arguments,
5710
- status: "completed"
6556
+ steps.push({
6557
+ step: steps.length + 1,
6558
+ modelVersion,
6559
+ text: responseText || void 0,
6560
+ thoughts: void 0,
6561
+ toolCalls: [],
6562
+ usage: usageTokens,
6563
+ costUsd: stepCostUsd,
6564
+ timing: timing2
5711
6565
  });
5712
- toolOutputs.push({
5713
- type: "function_call_output",
5714
- call_id: entry.ids.callId,
5715
- output: toOpenAiToolOutput(outputPayload)
6566
+ stepCallLogger?.complete({
6567
+ provider: "fireworks",
6568
+ model: request.model,
6569
+ modelVersion,
6570
+ step: turn,
6571
+ usage: usageTokens,
6572
+ costUsd: stepCostUsd,
6573
+ blocked,
6574
+ responseChars: responseText.length,
6575
+ thoughtChars: 0,
6576
+ toolCalls: 0,
6577
+ finalStep: steeringMessages.length === 0
5716
6578
  });
6579
+ if (steeringMessages.length === 0) {
6580
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
6581
+ }
6582
+ if (responseText.length > 0) {
6583
+ messages.push({ role: "assistant", content: responseText });
6584
+ }
6585
+ messages.push(...steeringMessages);
6586
+ continue;
5717
6587
  }
5718
- }
5719
- const stepCompletedAtMs = Date.now();
5720
- const timing = buildStepTiming({
5721
- stepStartedAtMs,
5722
- stepCompletedAtMs,
5723
- modelCompletedAtMs,
5724
- firstModelEventAtMs,
5725
- toolExecutionMs,
5726
- waitToolMs
5727
- });
5728
- steps.push({
5729
- step: steps.length + 1,
5730
- modelVersion,
5731
- text: responseText || void 0,
5732
- thoughts: reasoningSummaryText || void 0,
5733
- toolCalls,
5734
- usage: usageTokens,
5735
- costUsd: stepCostUsd,
5736
- timing
5737
- });
5738
- const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5739
- const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
5740
- input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
6588
+ const stepToolCalls = [];
6589
+ const callInputs = responseToolCalls.map((call, index) => {
6590
+ const toolIndex = index + 1;
6591
+ const toolId = buildToolLogId(turn, toolIndex);
6592
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
6593
+ return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
6594
+ });
6595
+ for (const entry of callInputs) {
6596
+ request.onEvent?.({
6597
+ type: "tool_call",
6598
+ phase: "started",
6599
+ turn: entry.turn,
6600
+ toolIndex: entry.toolIndex,
6601
+ toolName: entry.toolName,
6602
+ toolId: entry.toolId,
6603
+ callKind: "function",
6604
+ callId: entry.call.id,
6605
+ input: entry.value
6606
+ });
6607
+ }
6608
+ const callResults = await Promise.all(
6609
+ callInputs.map(async (entry) => {
6610
+ return await toolCallContextStorage.run(
6611
+ {
6612
+ toolName: entry.toolName,
6613
+ toolId: entry.toolId,
6614
+ turn: entry.turn,
6615
+ toolIndex: entry.toolIndex
6616
+ },
6617
+ async () => {
6618
+ const { result, outputPayload } = await executeToolCall({
6619
+ callKind: "function",
6620
+ toolName: entry.toolName,
6621
+ tool: request.tools[entry.toolName],
6622
+ rawInput: entry.value,
6623
+ parseError: entry.parseError
6624
+ });
6625
+ return { entry, result, outputPayload };
6626
+ }
6627
+ );
6628
+ })
6629
+ );
6630
+ const assistantToolCalls = [];
6631
+ const toolMessages = [];
6632
+ let toolExecutionMs = 0;
6633
+ let waitToolMs = 0;
6634
+ for (const { entry, result, outputPayload } of callResults) {
6635
+ stepToolCalls.push({ ...result, callId: entry.call.id });
6636
+ const callDurationMs = toToolResultDuration(result);
6637
+ toolExecutionMs += callDurationMs;
6638
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6639
+ waitToolMs += callDurationMs;
6640
+ }
6641
+ request.onEvent?.({
6642
+ type: "tool_call",
6643
+ phase: "completed",
6644
+ turn: entry.turn,
6645
+ toolIndex: entry.toolIndex,
6646
+ toolName: entry.toolName,
6647
+ toolId: entry.toolId,
6648
+ callKind: "function",
6649
+ callId: entry.call.id,
6650
+ input: entry.value,
6651
+ output: result.output,
6652
+ error: result.error,
6653
+ durationMs: result.durationMs
6654
+ });
6655
+ assistantToolCalls.push({
6656
+ id: entry.call.id,
6657
+ type: "function",
6658
+ function: {
6659
+ name: entry.toolName,
6660
+ arguments: entry.call.arguments
6661
+ }
6662
+ });
6663
+ toolMessages.push({
6664
+ role: "tool",
6665
+ tool_call_id: entry.call.id,
6666
+ content: mergeToolOutput(outputPayload)
6667
+ });
6668
+ }
6669
+ const stepCompletedAtMs = Date.now();
6670
+ const timing = buildStepTiming({
6671
+ stepStartedAtMs,
6672
+ stepCompletedAtMs,
6673
+ modelCompletedAtMs,
6674
+ schedulerMetrics,
6675
+ toolExecutionMs,
6676
+ waitToolMs
6677
+ });
6678
+ steps.push({
6679
+ step: steps.length + 1,
6680
+ modelVersion,
6681
+ text: responseText || void 0,
6682
+ thoughts: void 0,
6683
+ toolCalls: stepToolCalls,
6684
+ usage: usageTokens,
6685
+ costUsd: stepCostUsd,
6686
+ timing
6687
+ });
6688
+ stepCallLogger?.complete({
6689
+ provider: "fireworks",
6690
+ model: request.model,
6691
+ modelVersion,
6692
+ step: turn,
6693
+ usage: usageTokens,
6694
+ costUsd: stepCostUsd,
6695
+ blocked,
6696
+ responseChars: responseText.length,
6697
+ thoughtChars: 0,
6698
+ toolCalls: stepToolCalls.length,
6699
+ finalStep: false
6700
+ });
6701
+ messages.push({
6702
+ role: "assistant",
6703
+ ...responseText.length > 0 ? { content: responseText } : {},
6704
+ tool_calls: assistantToolCalls
6705
+ });
6706
+ messages.push(...toolMessages);
6707
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6708
+ if (steeringInput.length > 0) {
6709
+ messages.push(...toFireworksMessages(steeringInput));
6710
+ }
6711
+ } catch (error) {
6712
+ stepCallLogger?.fail(error, {
6713
+ provider: "fireworks",
6714
+ model: request.model,
6715
+ modelVersion,
6716
+ step: turn,
6717
+ usage: usageTokens,
6718
+ blocked
6719
+ });
6720
+ throw error;
6721
+ }
5741
6722
  }
5742
6723
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5743
6724
  }
5744
- if (providerInfo.provider === "fireworks") {
5745
- if (request.modelTools && request.modelTools.length > 0) {
5746
- throw new Error(
5747
- "Fireworks provider does not support provider-native modelTools in runToolLoop."
5748
- );
5749
- }
5750
- const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
5751
- const messages = toFireworksMessages(contents);
5752
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5753
- const turn = stepIndex + 1;
5754
- const stepStartedAtMs = Date.now();
5755
- let schedulerMetrics;
5756
- const response = await runFireworksCall(
6725
+ const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
6726
+ const geminiNativeTools = toGeminiTools(request.modelTools);
6727
+ const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
6728
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
6729
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
6730
+ const turn = stepIndex + 1;
6731
+ const stepStartedAtMs = Date.now();
6732
+ let firstModelEventAtMs;
6733
+ let schedulerMetrics;
6734
+ let modelVersion = request.model;
6735
+ let usageTokens;
6736
+ let responseText = "";
6737
+ let thoughtsText = "";
6738
+ const markFirstModelEvent = () => {
6739
+ if (firstModelEventAtMs === void 0) {
6740
+ firstModelEventAtMs = Date.now();
6741
+ }
6742
+ };
6743
+ const thinkingConfig = resolveGeminiThinkingConfig(request.model, request.thinkingLevel);
6744
+ const config = {
6745
+ maxOutputTokens: 32e3,
6746
+ tools: geminiTools,
6747
+ toolConfig: {
6748
+ functionCallingConfig: {
6749
+ mode: import_genai2.FunctionCallingConfigMode.VALIDATED
6750
+ }
6751
+ },
6752
+ ...thinkingConfig ? { thinkingConfig } : {}
6753
+ };
6754
+ const onEvent = request.onEvent;
6755
+ const stepRequestPayload = {
6756
+ model: request.model,
6757
+ contents: geminiContents,
6758
+ config
6759
+ };
6760
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6761
+ provider: "gemini",
6762
+ modelId: request.model,
6763
+ requestPayload: stepRequestPayload,
6764
+ step: turn
6765
+ });
6766
+ try {
6767
+ const response = await runGeminiCall(
5757
6768
  async (client) => {
5758
- return await client.chat.completions.create(
5759
- {
5760
- model: providerInfo.model,
5761
- messages,
5762
- tools: fireworksTools,
5763
- tool_choice: "auto",
5764
- parallel_tool_calls: true
5765
- },
5766
- { signal: request.signal }
5767
- );
6769
+ const stream = await client.models.generateContentStream({
6770
+ model: request.model,
6771
+ contents: geminiContents,
6772
+ config
6773
+ });
6774
+ let responseText2 = "";
6775
+ let thoughtsText2 = "";
6776
+ const modelParts = [];
6777
+ const functionCalls = [];
6778
+ const seenFunctionCallIds = /* @__PURE__ */ new Set();
6779
+ const seenFunctionCallKeys = /* @__PURE__ */ new Set();
6780
+ let latestUsageMetadata;
6781
+ let resolvedModelVersion;
6782
+ for await (const chunk of stream) {
6783
+ markFirstModelEvent();
6784
+ if (chunk.modelVersion) {
6785
+ resolvedModelVersion = chunk.modelVersion;
6786
+ onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
6787
+ }
6788
+ if (chunk.usageMetadata) {
6789
+ latestUsageMetadata = chunk.usageMetadata;
6790
+ }
6791
+ const candidates = chunk.candidates;
6792
+ if (!candidates || candidates.length === 0) {
6793
+ continue;
6794
+ }
6795
+ const primary = candidates[0];
6796
+ const parts = primary?.content?.parts;
6797
+ if (!parts || parts.length === 0) {
6798
+ continue;
6799
+ }
6800
+ for (const part of parts) {
6801
+ modelParts.push(part);
6802
+ const call = part.functionCall;
6803
+ if (call) {
6804
+ const id = typeof call.id === "string" ? call.id : "";
6805
+ const shouldAdd = (() => {
6806
+ if (id.length > 0) {
6807
+ if (seenFunctionCallIds.has(id)) {
6808
+ return false;
6809
+ }
6810
+ seenFunctionCallIds.add(id);
6811
+ return true;
6812
+ }
6813
+ const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
6814
+ if (seenFunctionCallKeys.has(key)) {
6815
+ return false;
6816
+ }
6817
+ seenFunctionCallKeys.add(key);
6818
+ return true;
6819
+ })();
6820
+ if (shouldAdd) {
6821
+ functionCalls.push(call);
6822
+ }
6823
+ }
6824
+ if (typeof part.text === "string" && part.text.length > 0) {
6825
+ if (part.thought) {
6826
+ thoughtsText2 += part.text;
6827
+ stepCallLogger?.appendThoughtDelta(part.text);
6828
+ onEvent?.({ type: "delta", channel: "thought", text: part.text });
6829
+ } else {
6830
+ responseText2 += part.text;
6831
+ stepCallLogger?.appendResponseDelta(part.text);
6832
+ onEvent?.({ type: "delta", channel: "response", text: part.text });
6833
+ }
6834
+ }
6835
+ }
6836
+ }
6837
+ return {
6838
+ responseText: responseText2,
6839
+ thoughtsText: thoughtsText2,
6840
+ functionCalls,
6841
+ modelParts,
6842
+ usageMetadata: latestUsageMetadata,
6843
+ modelVersion: resolvedModelVersion ?? request.model
6844
+ };
5768
6845
  },
5769
- providerInfo.model,
6846
+ request.model,
5770
6847
  {
5771
6848
  onSettled: (metrics) => {
5772
6849
  schedulerMetrics = metrics;
@@ -5774,43 +6851,26 @@ async function runToolLoop(request) {
5774
6851
  }
5775
6852
  );
5776
6853
  const modelCompletedAtMs = Date.now();
5777
- const modelVersion = typeof response.model === "string" ? response.model : request.model;
5778
- request.onEvent?.({ type: "model", modelVersion });
5779
- const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5780
- if (choice?.finish_reason === "content_filter") {
5781
- request.onEvent?.({ type: "blocked" });
5782
- }
5783
- const message = choice?.message;
5784
- const responseText = extractFireworksMessageText(message).trim();
5785
- if (responseText.length > 0) {
5786
- request.onEvent?.({ type: "delta", channel: "response", text: responseText });
5787
- }
5788
- const usageTokens = extractFireworksUsageTokens(response.usage);
6854
+ usageTokens = extractGeminiUsageTokens(response.usageMetadata);
6855
+ modelVersion = response.modelVersion ?? request.model;
6856
+ responseText = response.responseText.trim();
6857
+ thoughtsText = response.thoughtsText.trim();
5789
6858
  const stepCostUsd = estimateCallCostUsd({
5790
6859
  modelId: modelVersion,
5791
6860
  tokens: usageTokens,
5792
6861
  responseImages: 0
5793
6862
  });
5794
6863
  totalCostUsd += stepCostUsd;
5795
- if (usageTokens) {
5796
- request.onEvent?.({
5797
- type: "usage",
5798
- usage: usageTokens,
5799
- costUsd: stepCostUsd,
5800
- modelVersion
5801
- });
5802
- }
5803
- const responseToolCalls = extractFireworksToolCalls(message);
5804
- if (responseToolCalls.length === 0) {
6864
+ if (response.functionCalls.length === 0) {
5805
6865
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5806
- const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
5807
6866
  finalText = responseText;
5808
- finalThoughts = "";
6867
+ finalThoughts = thoughtsText;
5809
6868
  const stepCompletedAtMs2 = Date.now();
5810
6869
  const timing2 = buildStepTiming({
5811
6870
  stepStartedAtMs,
5812
6871
  stepCompletedAtMs: stepCompletedAtMs2,
5813
6872
  modelCompletedAtMs,
6873
+ firstModelEventAtMs,
5814
6874
  schedulerMetrics,
5815
6875
  toolExecutionMs: 0,
5816
6876
  waitToolMs: 0
@@ -5818,31 +6878,65 @@ async function runToolLoop(request) {
5818
6878
  steps.push({
5819
6879
  step: steps.length + 1,
5820
6880
  modelVersion,
5821
- text: responseText || void 0,
5822
- thoughts: void 0,
6881
+ text: finalText || void 0,
6882
+ thoughts: finalThoughts || void 0,
5823
6883
  toolCalls: [],
5824
6884
  usage: usageTokens,
5825
6885
  costUsd: stepCostUsd,
5826
6886
  timing: timing2
5827
6887
  });
5828
- if (steeringMessages.length === 0) {
6888
+ stepCallLogger?.complete({
6889
+ provider: "gemini",
6890
+ model: request.model,
6891
+ modelVersion,
6892
+ step: turn,
6893
+ usage: usageTokens,
6894
+ costUsd: stepCostUsd,
6895
+ responseChars: responseText.length,
6896
+ thoughtChars: thoughtsText.length,
6897
+ toolCalls: 0,
6898
+ finalStep: steeringInput2.length === 0
6899
+ });
6900
+ if (steeringInput2.length === 0) {
5829
6901
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5830
6902
  }
5831
- if (responseText.length > 0) {
5832
- messages.push({ role: "assistant", content: responseText });
6903
+ const modelPartsForHistory2 = response.modelParts.filter(
6904
+ (part) => !(typeof part.text === "string" && part.thought === true)
6905
+ );
6906
+ if (modelPartsForHistory2.length > 0) {
6907
+ geminiContents.push({ role: "model", parts: modelPartsForHistory2 });
6908
+ } else if (response.responseText.length > 0) {
6909
+ geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
5833
6910
  }
5834
- messages.push(...steeringMessages);
6911
+ geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
5835
6912
  continue;
5836
6913
  }
5837
- const stepToolCalls = [];
5838
- const callInputs = responseToolCalls.map((call, index) => {
6914
+ const toolCalls = [];
6915
+ const modelPartsForHistory = response.modelParts.filter(
6916
+ (part) => !(typeof part.text === "string" && part.thought === true)
6917
+ );
6918
+ if (modelPartsForHistory.length > 0) {
6919
+ geminiContents.push({ role: "model", parts: modelPartsForHistory });
6920
+ } else {
6921
+ const parts = [];
6922
+ if (response.responseText) {
6923
+ parts.push({ text: response.responseText });
6924
+ }
6925
+ for (const call of response.functionCalls) {
6926
+ parts.push({ functionCall: call });
6927
+ }
6928
+ geminiContents.push({ role: "model", parts });
6929
+ }
6930
+ const responseParts = [];
6931
+ const callInputs = response.functionCalls.map((call, index) => {
5839
6932
  const toolIndex = index + 1;
5840
6933
  const toolId = buildToolLogId(turn, toolIndex);
5841
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5842
- return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
6934
+ const toolName = call.name ?? "unknown";
6935
+ const rawInput = call.args ?? {};
6936
+ return { call, toolName, rawInput, toolId, turn, toolIndex };
5843
6937
  });
5844
6938
  for (const entry of callInputs) {
5845
- request.onEvent?.({
6939
+ onEvent?.({
5846
6940
  type: "tool_call",
5847
6941
  phase: "started",
5848
6942
  turn: entry.turn,
@@ -5851,7 +6945,7 @@ async function runToolLoop(request) {
5851
6945
  toolId: entry.toolId,
5852
6946
  callKind: "function",
5853
6947
  callId: entry.call.id,
5854
- input: entry.value
6948
+ input: entry.rawInput
5855
6949
  });
5856
6950
  }
5857
6951
  const callResults = await Promise.all(
@@ -5868,26 +6962,23 @@ async function runToolLoop(request) {
5868
6962
  callKind: "function",
5869
6963
  toolName: entry.toolName,
5870
6964
  tool: request.tools[entry.toolName],
5871
- rawInput: entry.value,
5872
- parseError: entry.parseError
6965
+ rawInput: entry.rawInput
5873
6966
  });
5874
6967
  return { entry, result, outputPayload };
5875
6968
  }
5876
6969
  );
5877
6970
  })
5878
6971
  );
5879
- const assistantToolCalls = [];
5880
- const toolMessages = [];
5881
6972
  let toolExecutionMs = 0;
5882
6973
  let waitToolMs = 0;
5883
6974
  for (const { entry, result, outputPayload } of callResults) {
5884
- stepToolCalls.push({ ...result, callId: entry.call.id });
6975
+ toolCalls.push({ ...result, callId: entry.call.id });
5885
6976
  const callDurationMs = toToolResultDuration(result);
5886
6977
  toolExecutionMs += callDurationMs;
5887
6978
  if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5888
6979
  waitToolMs += callDurationMs;
5889
6980
  }
5890
- request.onEvent?.({
6981
+ onEvent?.({
5891
6982
  type: "tool_call",
5892
6983
  phase: "completed",
5893
6984
  turn: entry.turn,
@@ -5896,30 +6987,26 @@ async function runToolLoop(request) {
5896
6987
  toolId: entry.toolId,
5897
6988
  callKind: "function",
5898
6989
  callId: entry.call.id,
5899
- input: entry.value,
6990
+ input: entry.rawInput,
5900
6991
  output: result.output,
5901
6992
  error: result.error,
5902
6993
  durationMs: result.durationMs
5903
6994
  });
5904
- assistantToolCalls.push({
5905
- id: entry.call.id,
5906
- type: "function",
5907
- function: {
6995
+ const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
6996
+ responseParts.push({
6997
+ functionResponse: {
5908
6998
  name: entry.toolName,
5909
- arguments: entry.call.arguments
6999
+ response: responsePayload,
7000
+ ...entry.call.id ? { id: entry.call.id } : {}
5910
7001
  }
5911
7002
  });
5912
- toolMessages.push({
5913
- role: "tool",
5914
- tool_call_id: entry.call.id,
5915
- content: mergeToolOutput(outputPayload)
5916
- });
5917
7003
  }
5918
7004
  const stepCompletedAtMs = Date.now();
5919
7005
  const timing = buildStepTiming({
5920
7006
  stepStartedAtMs,
5921
7007
  stepCompletedAtMs,
5922
7008
  modelCompletedAtMs,
7009
+ firstModelEventAtMs,
5923
7010
  schedulerMetrics,
5924
7011
  toolExecutionMs,
5925
7012
  waitToolMs
@@ -5928,296 +7015,40 @@ async function runToolLoop(request) {
5928
7015
  step: steps.length + 1,
5929
7016
  modelVersion,
5930
7017
  text: responseText || void 0,
5931
- thoughts: void 0,
5932
- toolCalls: stepToolCalls,
7018
+ thoughts: thoughtsText || void 0,
7019
+ toolCalls,
5933
7020
  usage: usageTokens,
5934
7021
  costUsd: stepCostUsd,
5935
7022
  timing
5936
7023
  });
5937
- messages.push({
5938
- role: "assistant",
5939
- ...responseText.length > 0 ? { content: responseText } : {},
5940
- tool_calls: assistantToolCalls
7024
+ stepCallLogger?.complete({
7025
+ provider: "gemini",
7026
+ model: request.model,
7027
+ modelVersion,
7028
+ step: turn,
7029
+ usage: usageTokens,
7030
+ costUsd: stepCostUsd,
7031
+ responseChars: responseText.length,
7032
+ thoughtChars: thoughtsText.length,
7033
+ toolCalls: toolCalls.length,
7034
+ finalStep: false
5941
7035
  });
5942
- messages.push(...toolMessages);
7036
+ geminiContents.push({ role: "user", parts: responseParts });
5943
7037
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5944
7038
  if (steeringInput.length > 0) {
5945
- messages.push(...toFireworksMessages(steeringInput));
5946
- }
5947
- }
5948
- throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5949
- }
5950
- const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
5951
- const geminiNativeTools = toGeminiTools(request.modelTools);
5952
- const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
5953
- const geminiContents = contents.map(convertLlmContentToGeminiContent);
5954
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5955
- const stepStartedAtMs = Date.now();
5956
- let firstModelEventAtMs;
5957
- let schedulerMetrics;
5958
- const markFirstModelEvent = () => {
5959
- if (firstModelEventAtMs === void 0) {
5960
- firstModelEventAtMs = Date.now();
5961
- }
5962
- };
5963
- const thinkingConfig = resolveGeminiThinkingConfig(request.model, request.thinkingLevel);
5964
- const config = {
5965
- maxOutputTokens: 32e3,
5966
- tools: geminiTools,
5967
- toolConfig: {
5968
- functionCallingConfig: {
5969
- mode: import_genai2.FunctionCallingConfigMode.VALIDATED
5970
- }
5971
- },
5972
- ...thinkingConfig ? { thinkingConfig } : {}
5973
- };
5974
- const onEvent = request.onEvent;
5975
- const response = await runGeminiCall(
5976
- async (client) => {
5977
- const stream = await client.models.generateContentStream({
5978
- model: request.model,
5979
- contents: geminiContents,
5980
- config
5981
- });
5982
- let responseText = "";
5983
- let thoughtsText = "";
5984
- const modelParts = [];
5985
- const functionCalls = [];
5986
- const seenFunctionCallIds = /* @__PURE__ */ new Set();
5987
- const seenFunctionCallKeys = /* @__PURE__ */ new Set();
5988
- let latestUsageMetadata;
5989
- let resolvedModelVersion;
5990
- for await (const chunk of stream) {
5991
- markFirstModelEvent();
5992
- if (chunk.modelVersion) {
5993
- resolvedModelVersion = chunk.modelVersion;
5994
- onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
5995
- }
5996
- if (chunk.usageMetadata) {
5997
- latestUsageMetadata = chunk.usageMetadata;
5998
- }
5999
- const candidates = chunk.candidates;
6000
- if (!candidates || candidates.length === 0) {
6001
- continue;
6002
- }
6003
- const primary = candidates[0];
6004
- const parts = primary?.content?.parts;
6005
- if (!parts || parts.length === 0) {
6006
- continue;
6007
- }
6008
- for (const part of parts) {
6009
- modelParts.push(part);
6010
- const call = part.functionCall;
6011
- if (call) {
6012
- const id = typeof call.id === "string" ? call.id : "";
6013
- const shouldAdd = (() => {
6014
- if (id.length > 0) {
6015
- if (seenFunctionCallIds.has(id)) {
6016
- return false;
6017
- }
6018
- seenFunctionCallIds.add(id);
6019
- return true;
6020
- }
6021
- const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
6022
- if (seenFunctionCallKeys.has(key)) {
6023
- return false;
6024
- }
6025
- seenFunctionCallKeys.add(key);
6026
- return true;
6027
- })();
6028
- if (shouldAdd) {
6029
- functionCalls.push(call);
6030
- }
6031
- }
6032
- if (typeof part.text === "string" && part.text.length > 0) {
6033
- if (part.thought) {
6034
- thoughtsText += part.text;
6035
- onEvent?.({ type: "delta", channel: "thought", text: part.text });
6036
- } else {
6037
- responseText += part.text;
6038
- onEvent?.({ type: "delta", channel: "response", text: part.text });
6039
- }
6040
- }
6041
- }
6042
- }
6043
- return {
6044
- responseText,
6045
- thoughtsText,
6046
- functionCalls,
6047
- modelParts,
6048
- usageMetadata: latestUsageMetadata,
6049
- modelVersion: resolvedModelVersion ?? request.model
6050
- };
6051
- },
6052
- request.model,
6053
- {
6054
- onSettled: (metrics) => {
6055
- schedulerMetrics = metrics;
6056
- }
7039
+ geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
6057
7040
  }
6058
- );
6059
- const modelCompletedAtMs = Date.now();
6060
- const usageTokens = extractGeminiUsageTokens(response.usageMetadata);
6061
- const modelVersion = response.modelVersion ?? request.model;
6062
- const stepCostUsd = estimateCallCostUsd({
6063
- modelId: modelVersion,
6064
- tokens: usageTokens,
6065
- responseImages: 0
6066
- });
6067
- totalCostUsd += stepCostUsd;
6068
- if (response.functionCalls.length === 0) {
6069
- const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6070
- finalText = response.responseText.trim();
6071
- finalThoughts = response.thoughtsText.trim();
6072
- const stepCompletedAtMs2 = Date.now();
6073
- const timing2 = buildStepTiming({
6074
- stepStartedAtMs,
6075
- stepCompletedAtMs: stepCompletedAtMs2,
6076
- modelCompletedAtMs,
6077
- firstModelEventAtMs,
6078
- schedulerMetrics,
6079
- toolExecutionMs: 0,
6080
- waitToolMs: 0
6081
- });
6082
- steps.push({
6083
- step: steps.length + 1,
7041
+ } catch (error) {
7042
+ stepCallLogger?.fail(error, {
7043
+ provider: "gemini",
7044
+ model: request.model,
6084
7045
  modelVersion,
6085
- text: finalText || void 0,
6086
- thoughts: finalThoughts || void 0,
6087
- toolCalls: [],
7046
+ step: turn,
6088
7047
  usage: usageTokens,
6089
- costUsd: stepCostUsd,
6090
- timing: timing2
6091
- });
6092
- if (steeringInput2.length === 0) {
6093
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
6094
- }
6095
- const modelPartsForHistory2 = response.modelParts.filter(
6096
- (part) => !(typeof part.text === "string" && part.thought === true)
6097
- );
6098
- if (modelPartsForHistory2.length > 0) {
6099
- geminiContents.push({ role: "model", parts: modelPartsForHistory2 });
6100
- } else if (response.responseText.length > 0) {
6101
- geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
6102
- }
6103
- geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
6104
- continue;
6105
- }
6106
- const toolCalls = [];
6107
- const modelPartsForHistory = response.modelParts.filter(
6108
- (part) => !(typeof part.text === "string" && part.thought === true)
6109
- );
6110
- if (modelPartsForHistory.length > 0) {
6111
- geminiContents.push({ role: "model", parts: modelPartsForHistory });
6112
- } else {
6113
- const parts = [];
6114
- if (response.responseText) {
6115
- parts.push({ text: response.responseText });
6116
- }
6117
- for (const call of response.functionCalls) {
6118
- parts.push({ functionCall: call });
6119
- }
6120
- geminiContents.push({ role: "model", parts });
6121
- }
6122
- const responseParts = [];
6123
- const callInputs = response.functionCalls.map((call, index) => {
6124
- const turn = stepIndex + 1;
6125
- const toolIndex = index + 1;
6126
- const toolId = buildToolLogId(turn, toolIndex);
6127
- const toolName = call.name ?? "unknown";
6128
- const rawInput = call.args ?? {};
6129
- return { call, toolName, rawInput, toolId, turn, toolIndex };
6130
- });
6131
- for (const entry of callInputs) {
6132
- onEvent?.({
6133
- type: "tool_call",
6134
- phase: "started",
6135
- turn: entry.turn,
6136
- toolIndex: entry.toolIndex,
6137
- toolName: entry.toolName,
6138
- toolId: entry.toolId,
6139
- callKind: "function",
6140
- callId: entry.call.id,
6141
- input: entry.rawInput
6142
- });
6143
- }
6144
- const callResults = await Promise.all(
6145
- callInputs.map(async (entry) => {
6146
- return await toolCallContextStorage.run(
6147
- {
6148
- toolName: entry.toolName,
6149
- toolId: entry.toolId,
6150
- turn: entry.turn,
6151
- toolIndex: entry.toolIndex
6152
- },
6153
- async () => {
6154
- const { result, outputPayload } = await executeToolCall({
6155
- callKind: "function",
6156
- toolName: entry.toolName,
6157
- tool: request.tools[entry.toolName],
6158
- rawInput: entry.rawInput
6159
- });
6160
- return { entry, result, outputPayload };
6161
- }
6162
- );
6163
- })
6164
- );
6165
- let toolExecutionMs = 0;
6166
- let waitToolMs = 0;
6167
- for (const { entry, result, outputPayload } of callResults) {
6168
- toolCalls.push({ ...result, callId: entry.call.id });
6169
- const callDurationMs = toToolResultDuration(result);
6170
- toolExecutionMs += callDurationMs;
6171
- if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6172
- waitToolMs += callDurationMs;
6173
- }
6174
- onEvent?.({
6175
- type: "tool_call",
6176
- phase: "completed",
6177
- turn: entry.turn,
6178
- toolIndex: entry.toolIndex,
6179
- toolName: entry.toolName,
6180
- toolId: entry.toolId,
6181
- callKind: "function",
6182
- callId: entry.call.id,
6183
- input: entry.rawInput,
6184
- output: result.output,
6185
- error: result.error,
6186
- durationMs: result.durationMs
6187
- });
6188
- const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
6189
- responseParts.push({
6190
- functionResponse: {
6191
- name: entry.toolName,
6192
- response: responsePayload,
6193
- ...entry.call.id ? { id: entry.call.id } : {}
6194
- }
7048
+ responseChars: responseText.length,
7049
+ thoughtChars: thoughtsText.length
6195
7050
  });
6196
- }
6197
- const stepCompletedAtMs = Date.now();
6198
- const timing = buildStepTiming({
6199
- stepStartedAtMs,
6200
- stepCompletedAtMs,
6201
- modelCompletedAtMs,
6202
- firstModelEventAtMs,
6203
- schedulerMetrics,
6204
- toolExecutionMs,
6205
- waitToolMs
6206
- });
6207
- steps.push({
6208
- step: steps.length + 1,
6209
- modelVersion,
6210
- text: response.responseText.trim() || void 0,
6211
- thoughts: response.thoughtsText.trim() || void 0,
6212
- toolCalls,
6213
- usage: usageTokens,
6214
- costUsd: stepCostUsd,
6215
- timing
6216
- });
6217
- geminiContents.push({ role: "user", parts: responseParts });
6218
- const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6219
- if (steeringInput.length > 0) {
6220
- geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
7051
+ throw error;
6221
7052
  }
6222
7053
  }
6223
7054
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
@@ -6531,6 +7362,7 @@ ${lines}`;
6531
7362
 
6532
7363
  // src/agent.ts
6533
7364
  var import_node_crypto3 = require("crypto");
7365
+ var import_node_path7 = __toESM(require("path"), 1);
6534
7366
 
6535
7367
  // src/agent/subagents.ts
6536
7368
  var import_node_crypto2 = require("crypto");
@@ -7005,26 +7837,26 @@ function resolveInputItemsText(items) {
7005
7837
  }
7006
7838
  const itemType = typeof item.type === "string" ? item.type.trim() : "";
7007
7839
  const name = typeof item.name === "string" ? item.name.trim() : "";
7008
- const path6 = typeof item.path === "string" ? item.path.trim() : "";
7840
+ const path8 = typeof item.path === "string" ? item.path.trim() : "";
7009
7841
  const imageUrl = typeof item.image_url === "string" ? item.image_url.trim() : "";
7010
7842
  if (itemType === "image") {
7011
7843
  lines.push("[image]");
7012
7844
  continue;
7013
7845
  }
7014
- if (itemType === "local_image" && path6) {
7015
- lines.push(`[local_image:${path6}]`);
7846
+ if (itemType === "local_image" && path8) {
7847
+ lines.push(`[local_image:${path8}]`);
7016
7848
  continue;
7017
7849
  }
7018
- if (itemType === "skill" && name && path6) {
7019
- lines.push(`[skill:$${name}](${path6})`);
7850
+ if (itemType === "skill" && name && path8) {
7851
+ lines.push(`[skill:$${name}](${path8})`);
7020
7852
  continue;
7021
7853
  }
7022
- if (itemType === "mention" && name && path6) {
7023
- lines.push(`[mention:$${name}](${path6})`);
7854
+ if (itemType === "mention" && name && path8) {
7855
+ lines.push(`[mention:$${name}](${path8})`);
7024
7856
  continue;
7025
7857
  }
7026
- if (path6 || imageUrl) {
7027
- lines.push(`[${itemType || "input"}:${path6 || imageUrl}]`);
7858
+ if (path8 || imageUrl) {
7859
+ lines.push(`[${itemType || "input"}:${path8 || imageUrl}]`);
7028
7860
  continue;
7029
7861
  }
7030
7862
  if (name) {
@@ -7149,7 +7981,7 @@ function startRun(agent, options) {
7149
7981
  setLifecycle(agent, "idle", "input_queued", `Subagent ${agent.id} run interrupted.`);
7150
7982
  return;
7151
7983
  }
7152
- const message = toErrorMessage(error);
7984
+ const message = toErrorMessage2(error);
7153
7985
  agent.lastError = message;
7154
7986
  setLifecycle(agent, "failed", "run_failed", `Subagent ${agent.id} failed: ${message}`);
7155
7987
  emitBackgroundNotification(agent, options);
@@ -7329,7 +8161,7 @@ function trimToUndefined(value) {
7329
8161
  const trimmed = value?.trim();
7330
8162
  return trimmed && trimmed.length > 0 ? trimmed : void 0;
7331
8163
  }
7332
- function toErrorMessage(error) {
8164
+ function toErrorMessage2(error) {
7333
8165
  if (error instanceof Error) {
7334
8166
  return error.message;
7335
8167
  }
@@ -7342,27 +8174,27 @@ function sleep2(ms) {
7342
8174
  }
7343
8175
 
7344
8176
  // src/tools/filesystemTools.ts
7345
- var import_node_path5 = __toESM(require("path"), 1);
7346
- var import_node_buffer3 = require("buffer");
8177
+ var import_node_path6 = __toESM(require("path"), 1);
8178
+ var import_node_buffer4 = require("buffer");
7347
8179
  var import_zod6 = require("zod");
7348
8180
 
7349
8181
  // src/tools/applyPatch.ts
7350
- var import_node_path4 = __toESM(require("path"), 1);
8182
+ var import_node_path5 = __toESM(require("path"), 1);
7351
8183
  var import_zod5 = require("zod");
7352
8184
 
7353
8185
  // src/tools/filesystem.ts
7354
8186
  var import_node_fs3 = require("fs");
7355
- var import_node_path3 = __toESM(require("path"), 1);
8187
+ var import_node_path4 = __toESM(require("path"), 1);
7356
8188
  var InMemoryAgentFilesystem = class {
7357
8189
  #files = /* @__PURE__ */ new Map();
7358
8190
  #dirs = /* @__PURE__ */ new Map();
7359
8191
  #clock = 0;
7360
8192
  constructor(initialFiles = {}) {
7361
- const root = import_node_path3.default.resolve("/");
8193
+ const root = import_node_path4.default.resolve("/");
7362
8194
  this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
7363
8195
  for (const [filePath, content] of Object.entries(initialFiles)) {
7364
- const absolutePath = import_node_path3.default.resolve(filePath);
7365
- this.#ensureDirSync(import_node_path3.default.dirname(absolutePath));
8196
+ const absolutePath = import_node_path4.default.resolve(filePath);
8197
+ this.#ensureDirSync(import_node_path4.default.dirname(absolutePath));
7366
8198
  this.#files.set(absolutePath, {
7367
8199
  content,
7368
8200
  mtimeMs: this.#nextMtime()
@@ -7370,7 +8202,7 @@ var InMemoryAgentFilesystem = class {
7370
8202
  }
7371
8203
  }
7372
8204
  async readTextFile(filePath) {
7373
- const absolutePath = import_node_path3.default.resolve(filePath);
8205
+ const absolutePath = import_node_path4.default.resolve(filePath);
7374
8206
  const file = this.#files.get(absolutePath);
7375
8207
  if (!file) {
7376
8208
  throw createNoSuchFileError("open", absolutePath);
@@ -7382,24 +8214,24 @@ var InMemoryAgentFilesystem = class {
7382
8214
  return Buffer.from(content, "utf8");
7383
8215
  }
7384
8216
  async writeTextFile(filePath, content) {
7385
- const absolutePath = import_node_path3.default.resolve(filePath);
7386
- const parentPath = import_node_path3.default.dirname(absolutePath);
8217
+ const absolutePath = import_node_path4.default.resolve(filePath);
8218
+ const parentPath = import_node_path4.default.dirname(absolutePath);
7387
8219
  if (!this.#dirs.has(parentPath)) {
7388
8220
  throw createNoSuchFileError("open", parentPath);
7389
8221
  }
7390
8222
  this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
7391
8223
  }
7392
8224
  async deleteFile(filePath) {
7393
- const absolutePath = import_node_path3.default.resolve(filePath);
8225
+ const absolutePath = import_node_path4.default.resolve(filePath);
7394
8226
  if (!this.#files.delete(absolutePath)) {
7395
8227
  throw createNoSuchFileError("unlink", absolutePath);
7396
8228
  }
7397
8229
  }
7398
8230
  async ensureDir(directoryPath) {
7399
- this.#ensureDirSync(import_node_path3.default.resolve(directoryPath));
8231
+ this.#ensureDirSync(import_node_path4.default.resolve(directoryPath));
7400
8232
  }
7401
8233
  async readDir(directoryPath) {
7402
- const absolutePath = import_node_path3.default.resolve(directoryPath);
8234
+ const absolutePath = import_node_path4.default.resolve(directoryPath);
7403
8235
  const directory = this.#dirs.get(absolutePath);
7404
8236
  if (!directory) {
7405
8237
  throw createNoSuchFileError("scandir", absolutePath);
@@ -7410,10 +8242,10 @@ var InMemoryAgentFilesystem = class {
7410
8242
  if (dirPath === absolutePath) {
7411
8243
  continue;
7412
8244
  }
7413
- if (import_node_path3.default.dirname(dirPath) !== absolutePath) {
8245
+ if (import_node_path4.default.dirname(dirPath) !== absolutePath) {
7414
8246
  continue;
7415
8247
  }
7416
- const name = import_node_path3.default.basename(dirPath);
8248
+ const name = import_node_path4.default.basename(dirPath);
7417
8249
  if (seenNames.has(name)) {
7418
8250
  continue;
7419
8251
  }
@@ -7426,10 +8258,10 @@ var InMemoryAgentFilesystem = class {
7426
8258
  });
7427
8259
  }
7428
8260
  for (const [filePath, fileRecord] of this.#files.entries()) {
7429
- if (import_node_path3.default.dirname(filePath) !== absolutePath) {
8261
+ if (import_node_path4.default.dirname(filePath) !== absolutePath) {
7430
8262
  continue;
7431
8263
  }
7432
- const name = import_node_path3.default.basename(filePath);
8264
+ const name = import_node_path4.default.basename(filePath);
7433
8265
  if (seenNames.has(name)) {
7434
8266
  continue;
7435
8267
  }
@@ -7445,7 +8277,7 @@ var InMemoryAgentFilesystem = class {
7445
8277
  return entries;
7446
8278
  }
7447
8279
  async stat(entryPath) {
7448
- const absolutePath = import_node_path3.default.resolve(entryPath);
8280
+ const absolutePath = import_node_path4.default.resolve(entryPath);
7449
8281
  const file = this.#files.get(absolutePath);
7450
8282
  if (file) {
7451
8283
  return { kind: "file", mtimeMs: file.mtimeMs };
@@ -7461,7 +8293,7 @@ var InMemoryAgentFilesystem = class {
7461
8293
  return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
7462
8294
  }
7463
8295
  #ensureDirSync(directoryPath) {
7464
- const absolutePath = import_node_path3.default.resolve(directoryPath);
8296
+ const absolutePath = import_node_path4.default.resolve(directoryPath);
7465
8297
  const parts = [];
7466
8298
  let cursor = absolutePath;
7467
8299
  for (; ; ) {
@@ -7469,7 +8301,7 @@ var InMemoryAgentFilesystem = class {
7469
8301
  break;
7470
8302
  }
7471
8303
  parts.push(cursor);
7472
- const parent = import_node_path3.default.dirname(cursor);
8304
+ const parent = import_node_path4.default.dirname(cursor);
7473
8305
  if (parent === cursor) {
7474
8306
  break;
7475
8307
  }
@@ -7503,7 +8335,7 @@ function createNodeAgentFilesystem() {
7503
8335
  const entries = await import_node_fs3.promises.readdir(directoryPath, { withFileTypes: true });
7504
8336
  const result = [];
7505
8337
  for (const entry of entries) {
7506
- const entryPath = import_node_path3.default.resolve(directoryPath, entry.name);
8338
+ const entryPath = import_node_path4.default.resolve(directoryPath, entry.name);
7507
8339
  const stats = await import_node_fs3.promises.lstat(entryPath);
7508
8340
  result.push({
7509
8341
  name: entry.name,
@@ -7667,7 +8499,7 @@ function createApplyPatchTool(options = {}) {
7667
8499
  });
7668
8500
  }
7669
8501
  async function applyPatch(request) {
7670
- const cwd = import_node_path4.default.resolve(request.cwd ?? process.cwd());
8502
+ const cwd = import_node_path5.default.resolve(request.cwd ?? process.cwd());
7671
8503
  const adapter = request.fs ?? createNodeAgentFilesystem();
7672
8504
  const allowOutsideCwd = request.allowOutsideCwd === true;
7673
8505
  const patchBytes = Buffer.byteLength(request.patch, "utf8");
@@ -7689,7 +8521,7 @@ async function applyPatch(request) {
7689
8521
  kind: "add",
7690
8522
  path: absolutePath2
7691
8523
  });
7692
- await adapter.ensureDir(import_node_path4.default.dirname(absolutePath2));
8524
+ await adapter.ensureDir(import_node_path5.default.dirname(absolutePath2));
7693
8525
  await adapter.writeTextFile(absolutePath2, operation.content);
7694
8526
  added.push(toDisplayPath(absolutePath2, cwd));
7695
8527
  continue;
@@ -7723,7 +8555,7 @@ async function applyPatch(request) {
7723
8555
  fromPath: absolutePath,
7724
8556
  toPath: destinationPath
7725
8557
  });
7726
- await adapter.ensureDir(import_node_path4.default.dirname(destinationPath));
8558
+ await adapter.ensureDir(import_node_path5.default.dirname(destinationPath));
7727
8559
  await adapter.writeTextFile(destinationPath, next);
7728
8560
  await adapter.deleteFile(absolutePath);
7729
8561
  modified.push(toDisplayPath(destinationPath, cwd));
@@ -7754,22 +8586,22 @@ function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
7754
8586
  if (trimmed.length === 0) {
7755
8587
  throw new Error("apply_patch failed: empty file path");
7756
8588
  }
7757
- const absolutePath = import_node_path4.default.isAbsolute(trimmed) ? import_node_path4.default.resolve(trimmed) : import_node_path4.default.resolve(cwd, trimmed);
8589
+ const absolutePath = import_node_path5.default.isAbsolute(trimmed) ? import_node_path5.default.resolve(trimmed) : import_node_path5.default.resolve(cwd, trimmed);
7758
8590
  if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
7759
8591
  throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
7760
8592
  }
7761
8593
  return absolutePath;
7762
8594
  }
7763
8595
  function isPathInsideCwd(candidatePath, cwd) {
7764
- const relative = import_node_path4.default.relative(cwd, candidatePath);
7765
- return relative === "" || !relative.startsWith("..") && !import_node_path4.default.isAbsolute(relative);
8596
+ const relative = import_node_path5.default.relative(cwd, candidatePath);
8597
+ return relative === "" || !relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative);
7766
8598
  }
7767
8599
  function toDisplayPath(absolutePath, cwd) {
7768
- const relative = import_node_path4.default.relative(cwd, absolutePath);
8600
+ const relative = import_node_path5.default.relative(cwd, absolutePath);
7769
8601
  if (relative === "") {
7770
8602
  return ".";
7771
8603
  }
7772
- if (!relative.startsWith("..") && !import_node_path4.default.isAbsolute(relative)) {
8604
+ if (!relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative)) {
7773
8605
  return relative;
7774
8606
  }
7775
8607
  return absolutePath;
@@ -8536,7 +9368,7 @@ async function readBinaryFile(filesystem, filePath) {
8536
9368
  return await filesystem.readBinaryFile(filePath);
8537
9369
  }
8538
9370
  const text = await filesystem.readTextFile(filePath);
8539
- return import_node_buffer3.Buffer.from(text, "utf8");
9371
+ return import_node_buffer4.Buffer.from(text, "utf8");
8540
9372
  }
8541
9373
  function detectImageMimeType(buffer, filePath) {
8542
9374
  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) {
@@ -8554,7 +9386,7 @@ function detectImageMimeType(buffer, filePath) {
8554
9386
  if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
8555
9387
  return "image/webp";
8556
9388
  }
8557
- const fromExtension = IMAGE_MIME_BY_EXTENSION[import_node_path5.default.extname(filePath).toLowerCase()];
9389
+ const fromExtension = IMAGE_MIME_BY_EXTENSION[import_node_path6.default.extname(filePath).toLowerCase()];
8558
9390
  if (fromExtension && SUPPORTED_IMAGE_MIME_TYPES.has(fromExtension)) {
8559
9391
  return fromExtension;
8560
9392
  }
@@ -8564,13 +9396,13 @@ function isPdfFile(buffer, filePath) {
8564
9396
  if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
8565
9397
  return true;
8566
9398
  }
8567
- return import_node_path5.default.extname(filePath).toLowerCase() === ".pdf";
9399
+ return import_node_path6.default.extname(filePath).toLowerCase() === ".pdf";
8568
9400
  }
8569
9401
  function isValidUtf8(buffer) {
8570
9402
  if (buffer.length === 0) {
8571
9403
  return true;
8572
9404
  }
8573
- return import_node_buffer3.Buffer.from(buffer.toString("utf8"), "utf8").equals(buffer);
9405
+ return import_node_buffer4.Buffer.from(buffer.toString("utf8"), "utf8").equals(buffer);
8574
9406
  }
8575
9407
  async function readFileGemini(input, options) {
8576
9408
  const runtime = resolveRuntime(options);
@@ -8602,7 +9434,7 @@ async function writeFileGemini(input, options) {
8602
9434
  action: "write",
8603
9435
  path: filePath
8604
9436
  });
8605
- await runtime.filesystem.ensureDir(import_node_path5.default.dirname(filePath));
9437
+ await runtime.filesystem.ensureDir(import_node_path6.default.dirname(filePath));
8606
9438
  await runtime.filesystem.writeTextFile(filePath, input.content);
8607
9439
  return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
8608
9440
  }
@@ -8623,7 +9455,7 @@ async function replaceFileContentGemini(input, options) {
8623
9455
  originalContent = await runtime.filesystem.readTextFile(filePath);
8624
9456
  } catch (error) {
8625
9457
  if (isNoEntError(error) && oldValue.length === 0) {
8626
- await runtime.filesystem.ensureDir(import_node_path5.default.dirname(filePath));
9458
+ await runtime.filesystem.ensureDir(import_node_path6.default.dirname(filePath));
8627
9459
  await runtime.filesystem.writeTextFile(filePath, newValue);
8628
9460
  return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
8629
9461
  }
@@ -8801,7 +9633,7 @@ async function globFilesGemini(input, options) {
8801
9633
  });
8802
9634
  const matched = [];
8803
9635
  for (const filePath of files) {
8804
- const relativePath = normalizeSlashes(import_node_path5.default.relative(dirPath, filePath));
9636
+ const relativePath = normalizeSlashes(import_node_path6.default.relative(dirPath, filePath));
8805
9637
  if (!matcher(relativePath)) {
8806
9638
  continue;
8807
9639
  }
@@ -8819,7 +9651,7 @@ async function globFilesGemini(input, options) {
8819
9651
  }
8820
9652
  function resolveRuntime(options) {
8821
9653
  return {
8822
- cwd: import_node_path5.default.resolve(options.cwd ?? process.cwd()),
9654
+ cwd: import_node_path6.default.resolve(options.cwd ?? process.cwd()),
8823
9655
  filesystem: options.fs ?? createNodeAgentFilesystem(),
8824
9656
  allowOutsideCwd: options.allowOutsideCwd === true,
8825
9657
  checkAccess: options.checkAccess,
@@ -8850,13 +9682,13 @@ function mapApplyPatchAction(action) {
8850
9682
  return "move";
8851
9683
  }
8852
9684
  function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
8853
- const absolutePath = import_node_path5.default.isAbsolute(inputPath) ? import_node_path5.default.resolve(inputPath) : import_node_path5.default.resolve(cwd, inputPath);
9685
+ const absolutePath = import_node_path6.default.isAbsolute(inputPath) ? import_node_path6.default.resolve(inputPath) : import_node_path6.default.resolve(cwd, inputPath);
8854
9686
  if (allowOutsideCwd || isPathInsideCwd2(absolutePath, cwd)) {
8855
9687
  return absolutePath;
8856
9688
  }
8857
- if (import_node_path5.default.isAbsolute(inputPath)) {
9689
+ if (import_node_path6.default.isAbsolute(inputPath)) {
8858
9690
  const sandboxRelativePath = inputPath.replace(/^[/\\]+/, "");
8859
- const sandboxRootedPath = import_node_path5.default.resolve(cwd, sandboxRelativePath);
9691
+ const sandboxRootedPath = import_node_path6.default.resolve(cwd, sandboxRelativePath);
8860
9692
  if (isPathInsideCwd2(sandboxRootedPath, cwd)) {
8861
9693
  return sandboxRootedPath;
8862
9694
  }
@@ -8864,25 +9696,25 @@ function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
8864
9696
  throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
8865
9697
  }
8866
9698
  function isPathInsideCwd2(candidatePath, cwd) {
8867
- const relative = import_node_path5.default.relative(cwd, candidatePath);
8868
- return relative === "" || !relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative);
9699
+ const relative = import_node_path6.default.relative(cwd, candidatePath);
9700
+ return relative === "" || !relative.startsWith("..") && !import_node_path6.default.isAbsolute(relative);
8869
9701
  }
8870
9702
  function toDisplayPath2(absolutePath, cwd) {
8871
- const relative = import_node_path5.default.relative(cwd, absolutePath);
9703
+ const relative = import_node_path6.default.relative(cwd, absolutePath);
8872
9704
  if (relative === "") {
8873
9705
  return ".";
8874
9706
  }
8875
- if (!relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative)) {
9707
+ if (!relative.startsWith("..") && !import_node_path6.default.isAbsolute(relative)) {
8876
9708
  return relative;
8877
9709
  }
8878
9710
  return absolutePath;
8879
9711
  }
8880
9712
  function toSandboxDisplayPath(absolutePath, cwd) {
8881
- const relative = import_node_path5.default.relative(cwd, absolutePath);
9713
+ const relative = import_node_path6.default.relative(cwd, absolutePath);
8882
9714
  if (relative === "") {
8883
9715
  return "/";
8884
9716
  }
8885
- if (!relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative)) {
9717
+ if (!relative.startsWith("..") && !import_node_path6.default.isAbsolute(relative)) {
8886
9718
  return `/${normalizeSlashes(relative)}`;
8887
9719
  }
8888
9720
  return normalizeSlashes(absolutePath);
@@ -8998,7 +9830,7 @@ function createGlobMatcher(pattern, caseSensitive = false) {
8998
9830
  }));
8999
9831
  return (candidatePath) => {
9000
9832
  const normalizedPath = normalizeSlashes(candidatePath);
9001
- const basename = import_node_path5.default.posix.basename(normalizedPath);
9833
+ const basename = import_node_path6.default.posix.basename(normalizedPath);
9002
9834
  return compiled.some(
9003
9835
  (entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
9004
9836
  );
@@ -9114,10 +9946,18 @@ function isNoEntError(error) {
9114
9946
  // src/agent.ts
9115
9947
  async function runAgentLoop(request) {
9116
9948
  const telemetry = createAgentTelemetrySession(request.telemetry);
9949
+ const logging = createRootAgentLoggingSession(request);
9117
9950
  try {
9118
- return await runAgentLoopInternal(request, { depth: 0, telemetry });
9951
+ return await runWithAgentLoggingSession(logging, async () => {
9952
+ return await runAgentLoopInternal(request, {
9953
+ depth: 0,
9954
+ telemetry,
9955
+ logging
9956
+ });
9957
+ });
9119
9958
  } finally {
9120
9959
  await telemetry?.flush();
9960
+ await logging?.flush();
9121
9961
  }
9122
9962
  }
9123
9963
  function mergeAbortSignals2(first, second) {
@@ -9188,9 +10028,11 @@ async function runAgentLoopInternal(request, context) {
9188
10028
  subagent_tool,
9189
10029
  subagents,
9190
10030
  telemetry,
10031
+ logging: _logging,
9191
10032
  ...toolLoopRequest
9192
10033
  } = request;
9193
10034
  const telemetrySession = context.telemetry ?? createAgentTelemetrySession(telemetry);
10035
+ const loggingSession = context.logging;
9194
10036
  const runId = randomRunId();
9195
10037
  const startedAtMs = Date.now();
9196
10038
  const steeringChannel = toolLoopRequest.steering ?? createToolLoopSteeringChannel();
@@ -9204,6 +10046,7 @@ async function runAgentLoopInternal(request, context) {
9204
10046
  model: request.model,
9205
10047
  depth: context.depth,
9206
10048
  telemetry: telemetrySession,
10049
+ logging: loggingSession,
9207
10050
  customTools: customTools ?? {},
9208
10051
  filesystemSelection,
9209
10052
  subagentSelection,
@@ -9240,6 +10083,16 @@ async function runAgentLoopInternal(request, context) {
9240
10083
  filesystemToolsEnabled: Object.keys(filesystemTools).length > 0,
9241
10084
  subagentToolsEnabled: resolvedSubagentConfig.enabled
9242
10085
  });
10086
+ loggingSession?.logLine(
10087
+ [
10088
+ `[agent:${runId}] run_started`,
10089
+ `depth=${context.depth.toString()}`,
10090
+ `model=${request.model}`,
10091
+ `tools=${Object.keys(mergedTools).length.toString()}`,
10092
+ `filesystemTools=${Object.keys(filesystemTools).length > 0 ? "true" : "false"}`,
10093
+ `subagentTools=${resolvedSubagentConfig.enabled ? "true" : "false"}`
10094
+ ].join(" ")
10095
+ );
9243
10096
  const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
9244
10097
  const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
9245
10098
  const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
@@ -9247,6 +10100,14 @@ async function runAgentLoopInternal(request, context) {
9247
10100
  if (includeLlmStreamEvents) {
9248
10101
  emitTelemetry({ type: "agent.run.stream", event });
9249
10102
  }
10103
+ if (loggingSession) {
10104
+ appendAgentStreamEventLog({
10105
+ event,
10106
+ append: (line) => {
10107
+ loggingSession.logLine(`[agent:${runId}] ${line}`);
10108
+ }
10109
+ });
10110
+ }
9250
10111
  } : void 0;
9251
10112
  try {
9252
10113
  const result = await runToolLoop({
@@ -9264,14 +10125,43 @@ async function runAgentLoopInternal(request, context) {
9264
10125
  totalCostUsd: result.totalCostUsd,
9265
10126
  usage: summarizeResultUsage(result)
9266
10127
  });
10128
+ loggingSession?.logLine(
10129
+ [
10130
+ `[agent:${runId}] run_completed`,
10131
+ `status=ok`,
10132
+ `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
10133
+ `steps=${result.steps.length.toString()}`,
10134
+ `toolCalls=${countToolCalls(result).toString()}`,
10135
+ `totalCostUsd=${(result.totalCostUsd ?? 0).toFixed(6)}`
10136
+ ].join(" ")
10137
+ );
10138
+ for (const step of result.steps) {
10139
+ loggingSession?.logLine(
10140
+ [
10141
+ `[agent:${runId}] step_completed`,
10142
+ `step=${step.step.toString()}`,
10143
+ `modelVersion=${step.modelVersion}`,
10144
+ `toolCalls=${step.toolCalls.length.toString()}`,
10145
+ `costUsd=${(step.costUsd ?? 0).toFixed(6)}`
10146
+ ].join(" ")
10147
+ );
10148
+ }
9267
10149
  return result;
9268
10150
  } catch (error) {
9269
10151
  emitTelemetry({
9270
10152
  type: "agent.run.completed",
9271
10153
  success: false,
9272
10154
  durationMs: Math.max(0, Date.now() - startedAtMs),
9273
- error: toErrorMessage2(error)
10155
+ error: toErrorMessage3(error)
9274
10156
  });
10157
+ loggingSession?.logLine(
10158
+ [
10159
+ `[agent:${runId}] run_completed`,
10160
+ `status=error`,
10161
+ `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
10162
+ `error=${toErrorMessage3(error)}`
10163
+ ].join(" ")
10164
+ );
9275
10165
  throw error;
9276
10166
  } finally {
9277
10167
  await subagentController?.closeAll();
@@ -9342,7 +10232,8 @@ function createSubagentController(params) {
9342
10232
  {
9343
10233
  depth: params.depth + 1,
9344
10234
  parentRunId: params.runId,
9345
- telemetry: params.telemetry
10235
+ telemetry: params.telemetry,
10236
+ logging: params.logging
9346
10237
  }
9347
10238
  );
9348
10239
  }
@@ -9406,10 +10297,10 @@ function trimToUndefined2(value) {
9406
10297
  function randomRunId() {
9407
10298
  return (0, import_node_crypto3.randomBytes)(8).toString("hex");
9408
10299
  }
9409
- function toIsoNow() {
10300
+ function toIsoNow2() {
9410
10301
  return (/* @__PURE__ */ new Date()).toISOString();
9411
10302
  }
9412
- function toErrorMessage2(error) {
10303
+ function toErrorMessage3(error) {
9413
10304
  if (error instanceof Error && error.message) {
9414
10305
  return error.message;
9415
10306
  }
@@ -9454,9 +10345,41 @@ function summarizeResultUsage(result) {
9454
10345
  }
9455
10346
  return summary;
9456
10347
  }
9457
- function isPromiseLike(value) {
10348
+ function isPromiseLike2(value) {
9458
10349
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
9459
10350
  }
10351
+ function resolveAgentLoggingSelection(value) {
10352
+ if (value === false) {
10353
+ return void 0;
10354
+ }
10355
+ if (value === void 0 || value === true) {
10356
+ return {
10357
+ mirrorToConsole: true
10358
+ };
10359
+ }
10360
+ return value;
10361
+ }
10362
+ function resolveWorkspaceDirForLogging(request) {
10363
+ const explicitSelection = request.filesystemTool ?? request.filesystem_tool;
10364
+ if (explicitSelection && typeof explicitSelection === "object" && !Array.isArray(explicitSelection)) {
10365
+ const cwd = explicitSelection.options?.cwd;
10366
+ if (typeof cwd === "string" && cwd.trim().length > 0) {
10367
+ return import_node_path7.default.resolve(cwd);
10368
+ }
10369
+ }
10370
+ return process.cwd();
10371
+ }
10372
+ function createRootAgentLoggingSession(request) {
10373
+ const selected = resolveAgentLoggingSelection(request.logging);
10374
+ if (!selected) {
10375
+ return void 0;
10376
+ }
10377
+ return createAgentLoggingSession({
10378
+ ...selected,
10379
+ workspaceDir: typeof selected.workspaceDir === "string" && selected.workspaceDir.trim().length > 0 ? import_node_path7.default.resolve(selected.workspaceDir) : resolveWorkspaceDirForLogging(request),
10380
+ mirrorToConsole: selected.mirrorToConsole !== false
10381
+ });
10382
+ }
9460
10383
  function isAgentTelemetrySink(value) {
9461
10384
  return typeof value === "object" && value !== null && typeof value.emit === "function";
9462
10385
  }
@@ -9487,7 +10410,7 @@ function createAgentTelemetrySession(telemetry) {
9487
10410
  const emit = (event) => {
9488
10411
  try {
9489
10412
  const output = config.sink.emit(event);
9490
- if (isPromiseLike(output)) {
10413
+ if (isPromiseLike2(output)) {
9491
10414
  const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
9492
10415
  trackPromise(task);
9493
10416
  }
@@ -9518,7 +10441,7 @@ function createAgentTelemetryEmitter(params) {
9518
10441
  }
9519
10442
  params.session.emit({
9520
10443
  ...event,
9521
- timestamp: toIsoNow(),
10444
+ timestamp: toIsoNow2(),
9522
10445
  runId: params.runId,
9523
10446
  ...params.parentRunId ? { parentRunId: params.parentRunId } : {},
9524
10447
  depth: params.depth,