@ljoukov/llm 4.0.1 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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");
@@ -317,6 +317,16 @@ var OPENAI_GPT_52_PRICING = {
317
317
  cachedRate: 0.175 / 1e6,
318
318
  outputRate: 14 / 1e6
319
319
  };
320
+ var OPENAI_GPT_54_PRICING = {
321
+ inputRate: 2.5 / 1e6,
322
+ cachedRate: 0.25 / 1e6,
323
+ outputRate: 15 / 1e6
324
+ };
325
+ var OPENAI_GPT_54_PRIORITY_PRICING = {
326
+ inputRate: 5 / 1e6,
327
+ cachedRate: 0.5 / 1e6,
328
+ outputRate: 30 / 1e6
329
+ };
320
330
  var OPENAI_GPT_53_CODEX_PRICING = {
321
331
  inputRate: 1.25 / 1e6,
322
332
  cachedRate: 0.125 / 1e6,
@@ -328,6 +338,12 @@ var OPENAI_GPT_5_MINI_PRICING = {
328
338
  outputRate: 2 / 1e6
329
339
  };
330
340
  function getOpenAiPricing(modelId) {
341
+ if (modelId.includes("gpt-5.4-fast")) {
342
+ return OPENAI_GPT_54_PRIORITY_PRICING;
343
+ }
344
+ if (modelId.includes("gpt-5.4")) {
345
+ return OPENAI_GPT_54_PRICING;
346
+ }
331
347
  if (modelId.includes("gpt-5.3-codex-spark")) {
332
348
  return OPENAI_GPT_5_MINI_PRICING;
333
349
  }
@@ -2738,11 +2754,18 @@ async function runOpenAiCall(fn, modelId, runOptions) {
2738
2754
  }
2739
2755
 
2740
2756
  // src/openai/models.ts
2741
- var OPENAI_MODEL_IDS = ["gpt-5.3-codex", "gpt-5.2", "gpt-5.1-codex-mini"];
2757
+ var OPENAI_MODEL_IDS = [
2758
+ "gpt-5.4",
2759
+ "gpt-5.3-codex",
2760
+ "gpt-5.2",
2761
+ "gpt-5.1-codex-mini"
2762
+ ];
2742
2763
  function isOpenAiModelId(value) {
2743
2764
  return OPENAI_MODEL_IDS.includes(value);
2744
2765
  }
2745
2766
  var CHATGPT_MODEL_IDS = [
2767
+ "chatgpt-gpt-5.4",
2768
+ "chatgpt-gpt-5.4-fast",
2746
2769
  "chatgpt-gpt-5.3-codex",
2747
2770
  "chatgpt-gpt-5.3-codex-spark",
2748
2771
  "chatgpt-gpt-5.2",
@@ -2754,9 +2777,387 @@ function isChatGptModelId(value) {
2754
2777
  function stripChatGptPrefix(model) {
2755
2778
  return model.slice("chatgpt-".length);
2756
2779
  }
2780
+ function resolveChatGptProviderModel(model) {
2781
+ switch (model) {
2782
+ case "chatgpt-gpt-5.4-fast":
2783
+ return "gpt-5.4";
2784
+ default:
2785
+ return stripChatGptPrefix(model);
2786
+ }
2787
+ }
2788
+ function resolveChatGptServiceTier(model) {
2789
+ return model === "chatgpt-gpt-5.4-fast" ? "priority" : void 0;
2790
+ }
2791
+
2792
+ // src/agentLogging.ts
2793
+ var import_node_async_hooks = require("async_hooks");
2794
+ var import_node_buffer2 = require("buffer");
2795
+ var import_promises = require("fs/promises");
2796
+ var import_node_path3 = __toESM(require("path"), 1);
2797
+ function toIsoNow() {
2798
+ return (/* @__PURE__ */ new Date()).toISOString();
2799
+ }
2800
+ function toErrorMessage(error) {
2801
+ if (error instanceof Error && error.message) {
2802
+ return error.message;
2803
+ }
2804
+ if (typeof error === "string") {
2805
+ return error;
2806
+ }
2807
+ return String(error);
2808
+ }
2809
+ function isPromiseLike(value) {
2810
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
2811
+ }
2812
+ function normalisePathSegment(value) {
2813
+ const cleaned = value.trim().replace(/[^a-z0-9._-]+/giu, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
2814
+ return cleaned.length > 0 ? cleaned : "segment";
2815
+ }
2816
+ function ensureTrailingNewline(value) {
2817
+ return value.endsWith("\n") ? value : `${value}
2818
+ `;
2819
+ }
2820
+ function redactDataUrlPayload(value) {
2821
+ if (!value.toLowerCase().startsWith("data:")) {
2822
+ return value;
2823
+ }
2824
+ const commaIndex = value.indexOf(",");
2825
+ if (commaIndex < 0) {
2826
+ return value;
2827
+ }
2828
+ return `${value.slice(0, commaIndex + 1)}...`;
2829
+ }
2830
+ function sanitiseLogValue(value, seen = /* @__PURE__ */ new WeakSet()) {
2831
+ if (typeof value === "string") {
2832
+ return redactDataUrlPayload(value);
2833
+ }
2834
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) {
2835
+ return value;
2836
+ }
2837
+ if (Array.isArray(value)) {
2838
+ return value.map((entry) => sanitiseLogValue(entry, seen));
2839
+ }
2840
+ if (typeof value !== "object") {
2841
+ return String(value);
2842
+ }
2843
+ if (seen.has(value)) {
2844
+ return "[circular]";
2845
+ }
2846
+ seen.add(value);
2847
+ const record = value;
2848
+ const output = {};
2849
+ const hasInlineMime = typeof record.mimeType === "string" && record.mimeType.trim().length > 0 || typeof record.mime_type === "string" && record.mime_type.trim().length > 0;
2850
+ for (const [key, entryValue] of Object.entries(record)) {
2851
+ if (key === "image_url") {
2852
+ if (typeof entryValue === "string") {
2853
+ output[key] = redactDataUrlPayload(entryValue);
2854
+ continue;
2855
+ }
2856
+ if (entryValue && typeof entryValue === "object") {
2857
+ const nested = entryValue;
2858
+ if (typeof nested.url === "string") {
2859
+ output[key] = {
2860
+ ...nested,
2861
+ url: redactDataUrlPayload(nested.url)
2862
+ };
2863
+ continue;
2864
+ }
2865
+ }
2866
+ }
2867
+ if (key === "data" && hasInlineMime && typeof entryValue === "string") {
2868
+ output[key] = `[omitted:${import_node_buffer2.Buffer.byteLength(entryValue, "utf8")}b]`;
2869
+ continue;
2870
+ }
2871
+ output[key] = sanitiseLogValue(entryValue, seen);
2872
+ }
2873
+ return output;
2874
+ }
2875
+ function serialiseForSnippet(value) {
2876
+ if (typeof value === "string") {
2877
+ return value;
2878
+ }
2879
+ try {
2880
+ return JSON.stringify(sanitiseLogValue(value));
2881
+ } catch {
2882
+ return String(value);
2883
+ }
2884
+ }
2885
+ function formatToolLogSnippet(value) {
2886
+ const compact = serialiseForSnippet(value).replace(/\s+/gu, " ").trim();
2887
+ if (compact.length === 0) {
2888
+ return "<empty>";
2889
+ }
2890
+ const max = 600;
2891
+ if (compact.length <= max) {
2892
+ return compact;
2893
+ }
2894
+ return `${compact.slice(0, max)}...`;
2895
+ }
2896
+ function formatUsd(value) {
2897
+ const amount = typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
2898
+ return amount.toFixed(6);
2899
+ }
2900
+ function appendToolCallStreamLog(options) {
2901
+ const event = options.event;
2902
+ if (event.type !== "tool_call") {
2903
+ return;
2904
+ }
2905
+ const callIdSegment = typeof event.callId === "string" && event.callId.trim().length > 0 ? ` callId=${event.callId}` : "";
2906
+ const prefix = [
2907
+ `tool_call_${event.phase}:`,
2908
+ `turn=${event.turn.toString()}`,
2909
+ `index=${event.toolIndex.toString()}`,
2910
+ `tool=${event.toolName}${callIdSegment}`
2911
+ ].join(" ");
2912
+ if (event.phase === "started") {
2913
+ options.append(prefix);
2914
+ options.append(`tool_call_input: ${formatToolLogSnippet(sanitiseLogValue(event.input))}`);
2915
+ return;
2916
+ }
2917
+ const durationSegment = typeof event.durationMs === "number" && Number.isFinite(event.durationMs) ? ` durationMs=${Math.max(0, Math.round(event.durationMs)).toString()}` : "";
2918
+ options.append(`${prefix} status=${event.error ? "error" : "ok"}${durationSegment}`);
2919
+ options.append(`tool_call_output: ${formatToolLogSnippet(sanitiseLogValue(event.output))}`);
2920
+ if (typeof event.error === "string" && event.error.trim().length > 0) {
2921
+ options.append(`tool_call_error: ${event.error.trim()}`);
2922
+ }
2923
+ }
2924
+ function appendAgentStreamEventLog(options) {
2925
+ const event = options.event;
2926
+ switch (event.type) {
2927
+ case "delta": {
2928
+ const channelPrefix = event.channel === "thought" ? "thought_delta" : "response_delta";
2929
+ options.append(`${channelPrefix}: ${event.text}`);
2930
+ return;
2931
+ }
2932
+ case "model": {
2933
+ options.append(`model: ${event.modelVersion}`);
2934
+ return;
2935
+ }
2936
+ case "usage": {
2937
+ options.append(
2938
+ [
2939
+ "usage:",
2940
+ `modelVersion=${event.modelVersion}`,
2941
+ `costUsd=${formatUsd(event.costUsd)}`,
2942
+ `tokens=${formatToolLogSnippet(sanitiseLogValue(event.usage))}`
2943
+ ].join(" ")
2944
+ );
2945
+ return;
2946
+ }
2947
+ case "blocked": {
2948
+ options.append("blocked");
2949
+ return;
2950
+ }
2951
+ case "tool_call": {
2952
+ appendToolCallStreamLog({
2953
+ event,
2954
+ append: options.append
2955
+ });
2956
+ return;
2957
+ }
2958
+ }
2959
+ }
2960
+ var AgentLoggingSessionImpl = class {
2961
+ workspaceDir;
2962
+ logsRootDir;
2963
+ mirrorToConsole;
2964
+ sink;
2965
+ agentLogPath;
2966
+ ensureReady;
2967
+ pending = /* @__PURE__ */ new Set();
2968
+ lineChain = Promise.resolve();
2969
+ callCounter = 0;
2970
+ constructor(config) {
2971
+ this.workspaceDir = import_node_path3.default.resolve(config.workspaceDir ?? process.cwd());
2972
+ this.logsRootDir = import_node_path3.default.join(import_node_path3.default.dirname(this.workspaceDir), "logs");
2973
+ this.mirrorToConsole = config.mirrorToConsole !== false;
2974
+ this.sink = config.sink;
2975
+ this.agentLogPath = import_node_path3.default.join(this.workspaceDir, "agent.log");
2976
+ this.ensureReady = this.prepare();
2977
+ this.track(this.ensureReady);
2978
+ }
2979
+ async prepare() {
2980
+ await (0, import_promises.mkdir)(this.workspaceDir, { recursive: true });
2981
+ await (0, import_promises.mkdir)(this.logsRootDir, { recursive: true });
2982
+ }
2983
+ track(task) {
2984
+ this.pending.add(task);
2985
+ task.finally(() => {
2986
+ this.pending.delete(task);
2987
+ });
2988
+ }
2989
+ enqueueLineWrite(line) {
2990
+ const next = this.lineChain.then(async () => {
2991
+ await this.ensureReady;
2992
+ await (0, import_promises.appendFile)(this.agentLogPath, `${line}
2993
+ `, "utf8");
2994
+ const sinkResult = this.sink?.append(line);
2995
+ if (isPromiseLike(sinkResult)) {
2996
+ await sinkResult;
2997
+ }
2998
+ });
2999
+ const tracked = next.catch(() => void 0);
3000
+ this.lineChain = tracked;
3001
+ this.track(tracked);
3002
+ }
3003
+ logLine(line) {
3004
+ const timestamped = `${toIsoNow()} ${line}`;
3005
+ if (this.mirrorToConsole) {
3006
+ console.log(timestamped);
3007
+ }
3008
+ this.enqueueLineWrite(timestamped);
3009
+ }
3010
+ startLlmCall(input) {
3011
+ const callNumber = this.callCounter + 1;
3012
+ this.callCounter = callNumber;
3013
+ const timestampSegment = toIsoNow().replace(/[:]/g, "-");
3014
+ const modelSegment = normalisePathSegment(input.modelId);
3015
+ const baseDir = import_node_path3.default.join(
3016
+ this.logsRootDir,
3017
+ `${timestampSegment}-${callNumber.toString().padStart(4, "0")}`,
3018
+ modelSegment
3019
+ );
3020
+ const responsePath = import_node_path3.default.join(baseDir, "response.txt");
3021
+ const thoughtsPath = import_node_path3.default.join(baseDir, "thoughts.txt");
3022
+ const responseMetadataPath = import_node_path3.default.join(baseDir, "response.metadata.json");
3023
+ let chain = this.ensureReady.then(async () => {
3024
+ await (0, import_promises.mkdir)(baseDir, { recursive: true });
3025
+ const requestText = input.requestText.trim().length > 0 ? input.requestText : "<empty request>";
3026
+ await (0, import_promises.writeFile)(
3027
+ import_node_path3.default.join(baseDir, "request.txt"),
3028
+ ensureTrailingNewline(requestText),
3029
+ "utf8"
3030
+ );
3031
+ const requestMetadata = {
3032
+ capturedAt: toIsoNow(),
3033
+ provider: input.provider,
3034
+ modelId: input.modelId,
3035
+ ...input.requestMetadata ? { request: sanitiseLogValue(input.requestMetadata) } : {}
3036
+ };
3037
+ await (0, import_promises.writeFile)(
3038
+ import_node_path3.default.join(baseDir, "request.metadata.json"),
3039
+ `${JSON.stringify(requestMetadata, null, 2)}
3040
+ `,
3041
+ "utf8"
3042
+ );
3043
+ const usedNames = /* @__PURE__ */ new Set();
3044
+ for (const attachment of input.attachments ?? []) {
3045
+ let filename = normalisePathSegment(attachment.filename);
3046
+ if (!filename.includes(".")) {
3047
+ filename = `${filename}.bin`;
3048
+ }
3049
+ const ext = import_node_path3.default.extname(filename);
3050
+ const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
3051
+ let candidate = filename;
3052
+ let duplicateIndex = 2;
3053
+ while (usedNames.has(candidate)) {
3054
+ candidate = `${base}-${duplicateIndex.toString()}${ext}`;
3055
+ duplicateIndex += 1;
3056
+ }
3057
+ usedNames.add(candidate);
3058
+ await (0, import_promises.writeFile)(import_node_path3.default.join(baseDir, candidate), attachment.bytes);
3059
+ }
3060
+ }).catch(() => void 0);
3061
+ this.track(chain);
3062
+ let closed = false;
3063
+ const enqueue = (operation) => {
3064
+ const next = chain.then(operation);
3065
+ const tracked = next.catch(() => void 0);
3066
+ chain = tracked;
3067
+ this.track(tracked);
3068
+ };
3069
+ return {
3070
+ appendThoughtDelta: (text) => {
3071
+ if (closed || text.length === 0) {
3072
+ return;
3073
+ }
3074
+ enqueue(async () => {
3075
+ await (0, import_promises.appendFile)(thoughtsPath, text, "utf8");
3076
+ });
3077
+ },
3078
+ appendResponseDelta: (text) => {
3079
+ if (closed || text.length === 0) {
3080
+ return;
3081
+ }
3082
+ enqueue(async () => {
3083
+ await (0, import_promises.appendFile)(responsePath, text, "utf8");
3084
+ });
3085
+ },
3086
+ complete: (metadata) => {
3087
+ if (closed) {
3088
+ return;
3089
+ }
3090
+ closed = true;
3091
+ enqueue(async () => {
3092
+ const payload = {
3093
+ capturedAt: toIsoNow(),
3094
+ status: "completed"
3095
+ };
3096
+ if (metadata) {
3097
+ const sanitised = sanitiseLogValue(metadata);
3098
+ if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3099
+ Object.assign(payload, sanitised);
3100
+ } else if (sanitised !== void 0) {
3101
+ payload.metadata = sanitised;
3102
+ }
3103
+ }
3104
+ await (0, import_promises.writeFile)(responseMetadataPath, `${JSON.stringify(payload, null, 2)}
3105
+ `, "utf8");
3106
+ });
3107
+ },
3108
+ fail: (error, metadata) => {
3109
+ if (closed) {
3110
+ return;
3111
+ }
3112
+ closed = true;
3113
+ enqueue(async () => {
3114
+ const payload = {
3115
+ capturedAt: toIsoNow(),
3116
+ status: "failed",
3117
+ error: toErrorMessage(error)
3118
+ };
3119
+ if (metadata) {
3120
+ const sanitised = sanitiseLogValue(metadata);
3121
+ if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3122
+ Object.assign(payload, sanitised);
3123
+ } else if (sanitised !== void 0) {
3124
+ payload.metadata = sanitised;
3125
+ }
3126
+ }
3127
+ await (0, import_promises.writeFile)(responseMetadataPath, `${JSON.stringify(payload, null, 2)}
3128
+ `, "utf8");
3129
+ });
3130
+ }
3131
+ };
3132
+ }
3133
+ async flush() {
3134
+ while (this.pending.size > 0) {
3135
+ await Promise.allSettled([...this.pending]);
3136
+ }
3137
+ if (typeof this.sink?.flush === "function") {
3138
+ try {
3139
+ await this.sink.flush();
3140
+ } catch {
3141
+ }
3142
+ }
3143
+ }
3144
+ };
3145
+ var loggingSessionStorage = new import_node_async_hooks.AsyncLocalStorage();
3146
+ function createAgentLoggingSession(config) {
3147
+ return new AgentLoggingSessionImpl(config);
3148
+ }
3149
+ function runWithAgentLoggingSession(session, fn) {
3150
+ if (!session) {
3151
+ return fn();
3152
+ }
3153
+ return loggingSessionStorage.run(session, fn);
3154
+ }
3155
+ function getCurrentAgentLoggingSession() {
3156
+ return loggingSessionStorage.getStore();
3157
+ }
2757
3158
 
2758
3159
  // src/llm.ts
2759
- var toolCallContextStorage = new import_node_async_hooks.AsyncLocalStorage();
3160
+ var toolCallContextStorage = new import_node_async_hooks2.AsyncLocalStorage();
2760
3161
  function getCurrentToolCallContext() {
2761
3162
  return toolCallContextStorage.getStore() ?? null;
2762
3163
  }
@@ -3048,9 +3449,9 @@ function sanitisePartForLogging(part) {
3048
3449
  case "inlineData": {
3049
3450
  let omittedBytes;
3050
3451
  try {
3051
- omittedBytes = import_node_buffer2.Buffer.from(part.data, "base64").byteLength;
3452
+ omittedBytes = import_node_buffer3.Buffer.from(part.data, "base64").byteLength;
3052
3453
  } catch {
3053
- omittedBytes = import_node_buffer2.Buffer.byteLength(part.data, "utf8");
3454
+ omittedBytes = import_node_buffer3.Buffer.byteLength(part.data, "utf8");
3054
3455
  }
3055
3456
  return {
3056
3457
  type: "inlineData",
@@ -3135,7 +3536,11 @@ function convertLlmContentToGeminiContent(content) {
3135
3536
  }
3136
3537
  function resolveProvider(model) {
3137
3538
  if (isChatGptModelId(model)) {
3138
- return { provider: "chatgpt", model: stripChatGptPrefix(model) };
3539
+ return {
3540
+ provider: "chatgpt",
3541
+ model: resolveChatGptProviderModel(model),
3542
+ serviceTier: resolveChatGptServiceTier(model)
3543
+ };
3139
3544
  }
3140
3545
  if (isGeminiTextModelId(model) || isGeminiImageModelId(model)) {
3141
3546
  return { provider: "gemini", model };
@@ -4113,8 +4518,8 @@ function parseOpenAiToolArguments(raw) {
4113
4518
  function formatZodIssues(issues) {
4114
4519
  const messages = [];
4115
4520
  for (const issue of issues) {
4116
- const path6 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
4117
- messages.push(`${path6}: ${issue.message}`);
4521
+ const path8 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
4522
+ messages.push(`${path8}: ${issue.message}`);
4118
4523
  }
4119
4524
  return messages.join("; ");
4120
4525
  }
@@ -4500,9 +4905,9 @@ function resolveGeminiThinkingConfig(modelId, thinkingLevel) {
4500
4905
  }
4501
4906
  function decodeInlineDataBuffer(base64) {
4502
4907
  try {
4503
- return import_node_buffer2.Buffer.from(base64, "base64");
4908
+ return import_node_buffer3.Buffer.from(base64, "base64");
4504
4909
  } catch {
4505
- return import_node_buffer2.Buffer.from(base64, "base64url");
4910
+ return import_node_buffer3.Buffer.from(base64, "base64url");
4506
4911
  }
4507
4912
  }
4508
4913
  function extractImages(content) {
@@ -4519,6 +4924,195 @@ function extractImages(content) {
4519
4924
  }
4520
4925
  return images;
4521
4926
  }
4927
+ function resolveAttachmentExtension(mimeType) {
4928
+ const normalized = (mimeType ?? "").trim().toLowerCase();
4929
+ switch (normalized) {
4930
+ case "image/jpeg":
4931
+ return "jpg";
4932
+ case "image/png":
4933
+ return "png";
4934
+ case "image/webp":
4935
+ return "webp";
4936
+ case "image/gif":
4937
+ return "gif";
4938
+ case "image/heic":
4939
+ return "heic";
4940
+ case "image/heif":
4941
+ return "heif";
4942
+ case "application/pdf":
4943
+ return "pdf";
4944
+ case "application/json":
4945
+ return "json";
4946
+ case "text/plain":
4947
+ return "txt";
4948
+ case "text/markdown":
4949
+ return "md";
4950
+ default: {
4951
+ const slashIndex = normalized.indexOf("/");
4952
+ if (slashIndex >= 0) {
4953
+ const subtype = normalized.slice(slashIndex + 1).split("+")[0] ?? "";
4954
+ const cleaned = subtype.replace(/[^a-z0-9]+/giu, "");
4955
+ if (cleaned.length > 0) {
4956
+ return cleaned;
4957
+ }
4958
+ }
4959
+ return "bin";
4960
+ }
4961
+ }
4962
+ }
4963
+ function decodeDataUrlAttachment(value, basename) {
4964
+ const trimmed = value.trim();
4965
+ if (!trimmed.toLowerCase().startsWith("data:")) {
4966
+ return null;
4967
+ }
4968
+ const commaIndex = trimmed.indexOf(",");
4969
+ if (commaIndex < 0) {
4970
+ return null;
4971
+ }
4972
+ const header = trimmed.slice(5, commaIndex);
4973
+ const payload = trimmed.slice(commaIndex + 1);
4974
+ const isBase64 = /;base64(?:;|$)/iu.test(header);
4975
+ const mimeType = (header.split(";")[0] ?? "application/octet-stream").trim().toLowerCase();
4976
+ try {
4977
+ const bytes = isBase64 ? import_node_buffer3.Buffer.from(payload, "base64") : import_node_buffer3.Buffer.from(decodeURIComponent(payload), "utf8");
4978
+ return {
4979
+ filename: `${basename}.${resolveAttachmentExtension(mimeType)}`,
4980
+ bytes
4981
+ };
4982
+ } catch {
4983
+ return null;
4984
+ }
4985
+ }
4986
+ function collectPayloadAttachments(value, options) {
4987
+ if (typeof value === "string") {
4988
+ const attachment = decodeDataUrlAttachment(
4989
+ value,
4990
+ `${options.prefix}-${options.counter.toString()}`
4991
+ );
4992
+ if (attachment) {
4993
+ options.attachments.push(attachment);
4994
+ options.counter += 1;
4995
+ }
4996
+ return;
4997
+ }
4998
+ if (!value || typeof value !== "object") {
4999
+ return;
5000
+ }
5001
+ if (options.seen.has(value)) {
5002
+ return;
5003
+ }
5004
+ options.seen.add(value);
5005
+ if (Array.isArray(value)) {
5006
+ for (const entry of value) {
5007
+ collectPayloadAttachments(entry, options);
5008
+ }
5009
+ return;
5010
+ }
5011
+ const record = value;
5012
+ const mimeType = typeof record.mimeType === "string" ? record.mimeType : void 0;
5013
+ if (typeof record.data === "string" && mimeType) {
5014
+ try {
5015
+ options.attachments.push({
5016
+ filename: `${options.prefix}-${options.counter.toString()}.${resolveAttachmentExtension(mimeType)}`,
5017
+ bytes: decodeInlineDataBuffer(record.data)
5018
+ });
5019
+ options.counter += 1;
5020
+ } catch {
5021
+ }
5022
+ }
5023
+ for (const entry of Object.values(record)) {
5024
+ collectPayloadAttachments(entry, options);
5025
+ }
5026
+ }
5027
+ function serialiseRequestPayloadForLogging(value) {
5028
+ try {
5029
+ return `${JSON.stringify(sanitiseLogValue(value), null, 2)}
5030
+ `;
5031
+ } catch {
5032
+ return `${String(value)}
5033
+ `;
5034
+ }
5035
+ }
5036
+ function startLlmCallLoggerFromContents(options) {
5037
+ const session = getCurrentAgentLoggingSession();
5038
+ if (!session) {
5039
+ return void 0;
5040
+ }
5041
+ const attachments = [];
5042
+ const sections = [];
5043
+ for (const [messageIndex, message] of options.contents.entries()) {
5044
+ sections.push(`### message_${(messageIndex + 1).toString()} role=${message.role}`);
5045
+ for (const [partIndex, part] of message.parts.entries()) {
5046
+ if (part.type === "text") {
5047
+ const channel = part.thought === true ? "thought" : "response";
5048
+ sections.push(`[text:${channel}]`);
5049
+ sections.push(part.text);
5050
+ continue;
5051
+ }
5052
+ const filename = `message-${(messageIndex + 1).toString()}-part-${(partIndex + 1).toString()}.${resolveAttachmentExtension(part.mimeType)}`;
5053
+ attachments.push({
5054
+ filename,
5055
+ bytes: decodeInlineDataBuffer(part.data)
5056
+ });
5057
+ sections.push(
5058
+ `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
5059
+ );
5060
+ }
5061
+ sections.push("");
5062
+ }
5063
+ return session.startLlmCall({
5064
+ provider: options.provider,
5065
+ modelId: options.request.model,
5066
+ requestText: sections.join("\n").trim(),
5067
+ requestMetadata: {
5068
+ model: options.request.model,
5069
+ input: options.contents.map((content) => ({
5070
+ role: content.role,
5071
+ parts: content.parts.map((part) => sanitisePartForLogging(part))
5072
+ })),
5073
+ ...options.request.instructions ? {
5074
+ instructions: options.request.instructions
5075
+ } : {},
5076
+ ...options.request.tools ? { tools: options.request.tools } : {},
5077
+ ...options.request.responseMimeType ? {
5078
+ responseMimeType: options.request.responseMimeType
5079
+ } : {},
5080
+ ...options.request.responseJsonSchema ? {
5081
+ responseJsonSchema: sanitiseLogValue(options.request.responseJsonSchema)
5082
+ } : {},
5083
+ ...options.request.responseModalities ? { responseModalities: options.request.responseModalities } : {},
5084
+ ...options.request.imageAspectRatio ? { imageAspectRatio: options.request.imageAspectRatio } : {},
5085
+ ...options.request.imageSize ? { imageSize: options.request.imageSize } : {},
5086
+ ...options.request.thinkingLevel ? { thinkingLevel: options.request.thinkingLevel } : {},
5087
+ ...options.request.openAiTextFormat ? { openAiTextFormat: sanitiseLogValue(options.request.openAiTextFormat) } : {},
5088
+ ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
5089
+ },
5090
+ attachments
5091
+ });
5092
+ }
5093
+ function startLlmCallLoggerFromPayload(options) {
5094
+ const session = getCurrentAgentLoggingSession();
5095
+ if (!session) {
5096
+ return void 0;
5097
+ }
5098
+ const attachments = [];
5099
+ collectPayloadAttachments(options.requestPayload, {
5100
+ prefix: `step-${options.step.toString()}`,
5101
+ attachments,
5102
+ seen: /* @__PURE__ */ new WeakSet(),
5103
+ counter: 1
5104
+ });
5105
+ return session.startLlmCall({
5106
+ provider: options.provider,
5107
+ modelId: options.modelId,
5108
+ requestText: serialiseRequestPayloadForLogging(options.requestPayload),
5109
+ requestMetadata: {
5110
+ step: options.step,
5111
+ ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
5112
+ },
5113
+ attachments
5114
+ });
5115
+ }
4522
5116
  async function runTextCall(params) {
4523
5117
  const { request, queue, abortController } = params;
4524
5118
  const providerInfo = resolveProvider(request.model);
@@ -4528,6 +5122,11 @@ async function runTextCall(params) {
4528
5122
  if (contents.length === 0) {
4529
5123
  throw new Error("LLM call received an empty prompt.");
4530
5124
  }
5125
+ const callLogger = startLlmCallLoggerFromContents({
5126
+ provider,
5127
+ request,
5128
+ contents
5129
+ });
4531
5130
  let modelVersion = request.model;
4532
5131
  let blocked = false;
4533
5132
  let grounding;
@@ -4535,12 +5134,17 @@ async function runTextCall(params) {
4535
5134
  let responseRole;
4536
5135
  let latestUsage;
4537
5136
  let responseImages = 0;
4538
- const pushDelta = (channel, text2) => {
4539
- if (!text2) {
5137
+ const pushDelta = (channel, text) => {
5138
+ if (!text) {
4540
5139
  return;
4541
5140
  }
4542
- responseParts.push({ type: "text", text: text2, ...channel === "thought" ? { thought: true } : {} });
4543
- queue.push({ type: "delta", channel, text: text2 });
5141
+ responseParts.push({ type: "text", text, ...channel === "thought" ? { thought: true } : {} });
5142
+ if (channel === "thought") {
5143
+ callLogger?.appendThoughtDelta(text);
5144
+ } else {
5145
+ callLogger?.appendResponseDelta(text);
5146
+ }
5147
+ queue.push({ type: "delta", channel, text });
4544
5148
  };
4545
5149
  const pushInline = (data, mimeType) => {
4546
5150
  if (!data) {
@@ -4567,263 +5171,295 @@ async function runTextCall(params) {
4567
5171
  return abortController.signal;
4568
5172
  };
4569
5173
  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;
4600
- }
4601
- case "response.reasoning_summary_text.delta": {
4602
- const delta = event.delta ?? "";
4603
- pushDelta("thought", typeof delta === "string" ? delta : "");
4604
- break;
4605
- }
4606
- case "response.refusal.delta": {
4607
- blocked = true;
4608
- queue.push({ type: "blocked" });
4609
- break;
4610
- }
4611
- default:
4612
- break;
4613
- }
4614
- }
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);
4621
- }
4622
- if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
4623
- const detail = finalResponse.incomplete_details?.reason;
4624
- throw new Error(
4625
- `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
5174
+ try {
5175
+ if (provider === "openai") {
5176
+ const openAiInput = toOpenAiInput(contents);
5177
+ const openAiTools = toOpenAiTools(request.tools);
5178
+ const reasoningEffort = resolveOpenAiReasoningEffort(modelForProvider, request.thinkingLevel);
5179
+ const openAiTextConfig = {
5180
+ format: request.openAiTextFormat ?? { type: "text" },
5181
+ verbosity: resolveOpenAiVerbosity(modelForProvider)
5182
+ };
5183
+ const reasoning = {
5184
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5185
+ summary: "detailed"
5186
+ };
5187
+ await runOpenAiCall(async (client) => {
5188
+ const stream = client.responses.stream(
5189
+ {
5190
+ model: modelForProvider,
5191
+ input: openAiInput,
5192
+ reasoning,
5193
+ text: openAiTextConfig,
5194
+ ...openAiTools ? { tools: openAiTools } : {},
5195
+ include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
5196
+ },
5197
+ { signal }
4626
5198
  );
4627
- }
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);
5199
+ for await (const event of stream) {
5200
+ switch (event.type) {
5201
+ case "response.output_text.delta": {
5202
+ const delta = event.delta ?? "";
5203
+ pushDelta("response", typeof delta === "string" ? delta : "");
5204
+ break;
5205
+ }
5206
+ case "response.reasoning_summary_text.delta": {
5207
+ const delta = event.delta ?? "";
5208
+ pushDelta("thought", typeof delta === "string" ? delta : "");
5209
+ break;
5210
+ }
5211
+ case "response.refusal.delta": {
5212
+ blocked = true;
5213
+ queue.push({ type: "blocked" });
5214
+ break;
5215
+ }
5216
+ default:
5217
+ break;
4637
5218
  }
4638
5219
  }
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);
5220
+ const finalResponse = await stream.finalResponse();
5221
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5222
+ queue.push({ type: "model", modelVersion });
5223
+ if (finalResponse.error) {
5224
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5225
+ throw new Error(message);
4668
5226
  }
4669
- if (delta.textDelta) {
4670
- sawResponseDelta = true;
4671
- pushDelta("response", delta.textDelta);
5227
+ if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
5228
+ const detail = finalResponse.incomplete_details?.reason;
5229
+ throw new Error(
5230
+ `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
5231
+ );
4672
5232
  }
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
5233
+ latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
5234
+ if (responseParts.length === 0) {
5235
+ const fallback = extractOpenAiResponseParts(finalResponse);
5236
+ blocked = blocked || fallback.blocked;
5237
+ for (const part of fallback.parts) {
5238
+ if (part.type === "text") {
5239
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
5240
+ } else {
5241
+ pushInline(part.data, part.mimeType);
5242
+ }
5243
+ }
4708
5244
  }
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 } : {}
5245
+ }, modelForProvider);
5246
+ } else if (provider === "chatgpt") {
5247
+ const chatGptInput = toChatGptInput(contents);
5248
+ const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
5249
+ const openAiTools = toOpenAiTools(request.tools);
5250
+ const requestPayload = {
5251
+ model: modelForProvider,
5252
+ store: false,
5253
+ stream: true,
5254
+ ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
5255
+ instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
5256
+ input: chatGptInput.input,
5257
+ include: ["reasoning.encrypted_content"],
5258
+ reasoning: {
5259
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5260
+ summary: "detailed"
4715
5261
  },
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;
5262
+ text: {
5263
+ format: request.openAiTextFormat ?? { type: "text" },
5264
+ verbosity: resolveOpenAiVerbosity(request.model)
5265
+ },
5266
+ ...openAiTools ? { tools: openAiTools } : {}
5267
+ };
5268
+ let sawResponseDelta = false;
5269
+ let sawThoughtDelta = false;
5270
+ const result = await collectChatGptCodexResponseWithRetry({
5271
+ request: requestPayload,
5272
+ signal,
5273
+ onDelta: (delta) => {
5274
+ if (delta.thoughtDelta) {
5275
+ sawThoughtDelta = true;
5276
+ pushDelta("thought", delta.thoughtDelta);
5277
+ }
5278
+ if (delta.textDelta) {
5279
+ sawResponseDelta = true;
5280
+ pushDelta("response", delta.textDelta);
5281
+ }
5282
+ }
5283
+ });
5284
+ blocked = blocked || result.blocked;
5285
+ if (blocked) {
4723
5286
  queue.push({ type: "blocked" });
4724
5287
  }
4725
- const textOutput = extractFireworksMessageText(
4726
- choice?.message
4727
- );
4728
- if (textOutput.length > 0) {
4729
- pushDelta("response", textOutput);
5288
+ if (result.model) {
5289
+ modelVersion = providerInfo.serviceTier ? request.model : `chatgpt-${result.model}`;
5290
+ queue.push({ type: "model", modelVersion });
4730
5291
  }
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
5292
+ latestUsage = extractChatGptUsageTokens(result.usage);
5293
+ const fallbackText = typeof result.text === "string" ? result.text : "";
5294
+ const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
5295
+ if (!sawThoughtDelta && fallbackThoughts.length > 0) {
5296
+ pushDelta("thought", fallbackThoughts);
5297
+ }
5298
+ if (!sawResponseDelta && fallbackText.length > 0) {
5299
+ pushDelta("response", fallbackText);
5300
+ }
5301
+ } else if (provider === "fireworks") {
5302
+ if (request.tools && request.tools.length > 0) {
5303
+ throw new Error(
5304
+ "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
5305
+ );
5306
+ }
5307
+ const fireworksMessages = toFireworksMessages(contents, {
5308
+ responseMimeType: request.responseMimeType,
5309
+ responseJsonSchema: request.responseJsonSchema
4758
5310
  });
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) {
5311
+ await runFireworksCall(async (client) => {
5312
+ const responseFormat = request.responseJsonSchema ? {
5313
+ type: "json_schema",
5314
+ json_schema: {
5315
+ name: "llm-response",
5316
+ schema: request.responseJsonSchema
5317
+ }
5318
+ } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
5319
+ const response = await client.chat.completions.create(
5320
+ {
5321
+ model: modelForProvider,
5322
+ messages: fireworksMessages,
5323
+ ...responseFormat ? { response_format: responseFormat } : {}
5324
+ },
5325
+ { signal }
5326
+ );
5327
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
5328
+ queue.push({ type: "model", modelVersion });
5329
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5330
+ if (choice?.finish_reason === "content_filter") {
4766
5331
  blocked = true;
4767
5332
  queue.push({ type: "blocked" });
4768
5333
  }
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" });
5334
+ const textOutput = extractFireworksMessageText(
5335
+ choice?.message
5336
+ );
5337
+ if (textOutput.length > 0) {
5338
+ pushDelta("response", textOutput);
4778
5339
  }
4779
- for (const candidate of candidates) {
4780
- const candidateContent = candidate.content;
4781
- if (!candidateContent) {
4782
- continue;
5340
+ latestUsage = extractFireworksUsageTokens(response.usage);
5341
+ }, modelForProvider);
5342
+ } else {
5343
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
5344
+ const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
5345
+ const config = {
5346
+ maxOutputTokens: 32e3,
5347
+ ...thinkingConfig ? { thinkingConfig } : {},
5348
+ ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
5349
+ ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
5350
+ ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
5351
+ ...request.imageAspectRatio || request.imageSize ? {
5352
+ imageConfig: {
5353
+ ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
5354
+ ...request.imageSize ? { imageSize: request.imageSize } : {}
4783
5355
  }
4784
- if (candidate.groundingMetadata) {
4785
- latestGrounding = candidate.groundingMetadata;
5356
+ } : {}
5357
+ };
5358
+ const geminiTools = toGeminiTools(request.tools);
5359
+ if (geminiTools) {
5360
+ config.tools = geminiTools;
5361
+ }
5362
+ await runGeminiCall(async (client) => {
5363
+ const stream = await client.models.generateContentStream({
5364
+ model: modelForProvider,
5365
+ contents: geminiContents,
5366
+ config
5367
+ });
5368
+ let latestGrounding;
5369
+ for await (const chunk of stream) {
5370
+ if (chunk.modelVersion) {
5371
+ modelVersion = chunk.modelVersion;
5372
+ queue.push({ type: "model", modelVersion });
4786
5373
  }
4787
- const content2 = convertGeminiContentToLlmContent(candidateContent);
4788
- if (!responseRole) {
4789
- responseRole = content2.role;
5374
+ if (chunk.promptFeedback?.blockReason) {
5375
+ blocked = true;
5376
+ queue.push({ type: "blocked" });
4790
5377
  }
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);
5378
+ latestUsage = mergeTokenUpdates(
5379
+ latestUsage,
5380
+ extractGeminiUsageTokens(chunk.usageMetadata)
5381
+ );
5382
+ const candidates = chunk.candidates;
5383
+ if (!candidates || candidates.length === 0) {
5384
+ continue;
5385
+ }
5386
+ const primary = candidates[0];
5387
+ if (primary && isModerationFinish(primary.finishReason)) {
5388
+ blocked = true;
5389
+ queue.push({ type: "blocked" });
5390
+ }
5391
+ for (const candidate of candidates) {
5392
+ const candidateContent = candidate.content;
5393
+ if (!candidateContent) {
5394
+ continue;
5395
+ }
5396
+ if (candidate.groundingMetadata) {
5397
+ latestGrounding = candidate.groundingMetadata;
5398
+ }
5399
+ const content2 = convertGeminiContentToLlmContent(candidateContent);
5400
+ if (!responseRole) {
5401
+ responseRole = content2.role;
5402
+ }
5403
+ for (const part of content2.parts) {
5404
+ if (part.type === "text") {
5405
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
5406
+ } else {
5407
+ pushInline(part.data, part.mimeType);
5408
+ }
4796
5409
  }
4797
5410
  }
4798
5411
  }
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 });
5412
+ grounding = latestGrounding;
5413
+ }, modelForProvider);
5414
+ }
5415
+ const mergedParts = mergeConsecutiveTextParts(responseParts);
5416
+ const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
5417
+ const { text, thoughts } = extractTextByChannel(content);
5418
+ const costUsd = estimateCallCostUsd({
5419
+ modelId: modelVersion,
5420
+ tokens: latestUsage,
5421
+ responseImages,
5422
+ imageSize: request.imageSize
5423
+ });
5424
+ if (latestUsage) {
5425
+ queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5426
+ }
5427
+ callLogger?.complete({
5428
+ provider,
5429
+ model: request.model,
5430
+ modelVersion,
5431
+ blocked,
5432
+ costUsd,
5433
+ usage: latestUsage,
5434
+ grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5435
+ responseChars: text.length,
5436
+ thoughtChars: thoughts.length,
5437
+ responseImages
5438
+ });
5439
+ return {
5440
+ provider,
5441
+ model: request.model,
5442
+ modelVersion,
5443
+ content,
5444
+ text,
5445
+ thoughts,
5446
+ blocked,
5447
+ usage: latestUsage,
5448
+ costUsd,
5449
+ grounding
5450
+ };
5451
+ } catch (error) {
5452
+ callLogger?.fail(error, {
5453
+ provider,
5454
+ model: request.model,
5455
+ modelVersion,
5456
+ blocked,
5457
+ usage: latestUsage,
5458
+ partialResponseParts: responseParts.length,
5459
+ responseImages
5460
+ });
5461
+ throw error;
4814
5462
  }
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
5463
  }
4828
5464
  function streamText(request) {
4829
5465
  const queue = createAsyncQueue();
@@ -5286,6 +5922,23 @@ async function runToolLoop(request) {
5286
5922
  let modelVersion = request.model;
5287
5923
  let usageTokens;
5288
5924
  let thoughtDeltaEmitted = false;
5925
+ let blocked = false;
5926
+ const stepRequestPayload = {
5927
+ model: providerInfo.model,
5928
+ input,
5929
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5930
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5931
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5932
+ reasoning,
5933
+ text: textConfig,
5934
+ include: ["reasoning.encrypted_content"]
5935
+ };
5936
+ const stepCallLogger = startLlmCallLoggerFromPayload({
5937
+ provider: "openai",
5938
+ modelId: request.model,
5939
+ requestPayload: stepRequestPayload,
5940
+ step: turn
5941
+ });
5289
5942
  const emitEvent = (ev) => {
5290
5943
  onEvent?.(ev);
5291
5944
  };
@@ -5294,226 +5947,276 @@ async function runToolLoop(request) {
5294
5947
  firstModelEventAtMs = Date.now();
5295
5948
  }
5296
5949
  };
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;
5950
+ try {
5951
+ const finalResponse = await runOpenAiCall(
5952
+ async (client) => {
5953
+ const stream = client.responses.stream(
5954
+ {
5955
+ model: providerInfo.model,
5956
+ input,
5957
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5958
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5959
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5960
+ reasoning,
5961
+ text: textConfig,
5962
+ include: ["reasoning.encrypted_content"]
5963
+ },
5964
+ { signal: abortController.signal }
5965
+ );
5966
+ for await (const event of stream) {
5967
+ markFirstModelEvent();
5968
+ switch (event.type) {
5969
+ case "response.output_text.delta": {
5970
+ const text = typeof event.delta === "string" ? event.delta : "";
5971
+ if (text.length > 0) {
5972
+ stepCallLogger?.appendResponseDelta(text);
5973
+ }
5974
+ emitEvent({
5975
+ type: "delta",
5976
+ channel: "response",
5977
+ text
5978
+ });
5979
+ break;
5980
+ }
5981
+ case "response.reasoning_summary_text.delta": {
5982
+ thoughtDeltaEmitted = true;
5983
+ const text = typeof event.delta === "string" ? event.delta : "";
5984
+ if (text.length > 0) {
5985
+ stepCallLogger?.appendThoughtDelta(text);
5986
+ }
5987
+ emitEvent({
5988
+ type: "delta",
5989
+ channel: "thought",
5990
+ text
5991
+ });
5992
+ break;
5993
+ }
5994
+ case "response.refusal.delta":
5995
+ blocked = true;
5996
+ emitEvent({ type: "blocked" });
5997
+ break;
5998
+ default:
5999
+ break;
6000
+ }
6001
+ }
6002
+ return await stream.finalResponse();
6003
+ },
6004
+ providerInfo.model,
6005
+ {
6006
+ onSettled: (metrics) => {
6007
+ schedulerMetrics = metrics;
5335
6008
  }
5336
6009
  }
5337
- return await stream.finalResponse();
5338
- },
5339
- providerInfo.model,
5340
- {
5341
- onSettled: (metrics) => {
5342
- schedulerMetrics = metrics;
6010
+ );
6011
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
6012
+ emitEvent({ type: "model", modelVersion });
6013
+ if (finalResponse.error) {
6014
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
6015
+ throw new Error(message);
6016
+ }
6017
+ usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
6018
+ const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
6019
+ const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
6020
+ if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
6021
+ stepCallLogger?.appendThoughtDelta(reasoningSummary);
6022
+ emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
6023
+ }
6024
+ const modelCompletedAtMs = Date.now();
6025
+ const stepCostUsd = estimateCallCostUsd({
6026
+ modelId: modelVersion,
6027
+ tokens: usageTokens,
6028
+ responseImages: 0
6029
+ });
6030
+ totalCostUsd += stepCostUsd;
6031
+ if (usageTokens) {
6032
+ emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
6033
+ }
6034
+ const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
6035
+ const stepToolCalls = [];
6036
+ if (responseToolCalls.length === 0) {
6037
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6038
+ const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2) : [];
6039
+ finalText = responseText;
6040
+ finalThoughts = reasoningSummary;
6041
+ const stepCompletedAtMs2 = Date.now();
6042
+ const timing2 = buildStepTiming({
6043
+ stepStartedAtMs,
6044
+ stepCompletedAtMs: stepCompletedAtMs2,
6045
+ modelCompletedAtMs,
6046
+ firstModelEventAtMs,
6047
+ schedulerMetrics,
6048
+ toolExecutionMs: 0,
6049
+ waitToolMs: 0
6050
+ });
6051
+ steps.push({
6052
+ step: steps.length + 1,
6053
+ modelVersion,
6054
+ text: responseText || void 0,
6055
+ thoughts: reasoningSummary || void 0,
6056
+ toolCalls: [],
6057
+ usage: usageTokens,
6058
+ costUsd: stepCostUsd,
6059
+ timing: timing2
6060
+ });
6061
+ stepCallLogger?.complete({
6062
+ provider: "openai",
6063
+ model: request.model,
6064
+ modelVersion,
6065
+ step: turn,
6066
+ usage: usageTokens,
6067
+ costUsd: stepCostUsd,
6068
+ blocked,
6069
+ responseChars: responseText.length,
6070
+ thoughtChars: reasoningSummary.length,
6071
+ toolCalls: 0,
6072
+ finalStep: steeringItems2.length === 0
6073
+ });
6074
+ if (steeringItems2.length === 0) {
6075
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5343
6076
  }
6077
+ previousResponseId = finalResponse.id;
6078
+ input = steeringItems2;
6079
+ continue;
5344
6080
  }
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({
6081
+ const callInputs = responseToolCalls.map((call, index) => {
6082
+ const toolIndex = index + 1;
6083
+ const toolId = buildToolLogId(turn, toolIndex);
6084
+ const toolName = call.name;
6085
+ if (call.kind === "custom") {
6086
+ return {
6087
+ call,
6088
+ toolName,
6089
+ value: call.input,
6090
+ parseError: void 0,
6091
+ toolId,
6092
+ turn,
6093
+ toolIndex
6094
+ };
6095
+ }
6096
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
6097
+ return { call, toolName, value, parseError, toolId, turn, toolIndex };
6098
+ });
6099
+ for (const entry of callInputs) {
6100
+ emitEvent({
6101
+ type: "tool_call",
6102
+ phase: "started",
6103
+ turn: entry.turn,
6104
+ toolIndex: entry.toolIndex,
6105
+ toolName: entry.toolName,
6106
+ toolId: entry.toolId,
6107
+ callKind: entry.call.kind,
6108
+ callId: entry.call.call_id,
6109
+ input: entry.value
6110
+ });
6111
+ }
6112
+ const callResults = await Promise.all(
6113
+ callInputs.map(async (entry) => {
6114
+ return await toolCallContextStorage.run(
6115
+ {
6116
+ toolName: entry.toolName,
6117
+ toolId: entry.toolId,
6118
+ turn: entry.turn,
6119
+ toolIndex: entry.toolIndex
6120
+ },
6121
+ async () => {
6122
+ const { result, outputPayload } = await executeToolCall({
6123
+ callKind: entry.call.kind,
6124
+ toolName: entry.toolName,
6125
+ tool: request.tools[entry.toolName],
6126
+ rawInput: entry.value,
6127
+ parseError: entry.parseError
6128
+ });
6129
+ return { entry, result, outputPayload };
6130
+ }
6131
+ );
6132
+ })
6133
+ );
6134
+ const toolOutputs = [];
6135
+ let toolExecutionMs = 0;
6136
+ let waitToolMs = 0;
6137
+ for (const { entry, result, outputPayload } of callResults) {
6138
+ stepToolCalls.push({ ...result, callId: entry.call.call_id });
6139
+ const callDurationMs = toToolResultDuration(result);
6140
+ toolExecutionMs += callDurationMs;
6141
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6142
+ waitToolMs += callDurationMs;
6143
+ }
6144
+ emitEvent({
6145
+ type: "tool_call",
6146
+ phase: "completed",
6147
+ turn: entry.turn,
6148
+ toolIndex: entry.toolIndex,
6149
+ toolName: entry.toolName,
6150
+ toolId: entry.toolId,
6151
+ callKind: entry.call.kind,
6152
+ callId: entry.call.call_id,
6153
+ input: entry.value,
6154
+ output: result.output,
6155
+ error: result.error,
6156
+ durationMs: result.durationMs
6157
+ });
6158
+ if (entry.call.kind === "custom") {
6159
+ toolOutputs.push({
6160
+ type: "custom_tool_call_output",
6161
+ call_id: entry.call.call_id,
6162
+ output: toOpenAiToolOutput(outputPayload)
6163
+ });
6164
+ } else {
6165
+ toolOutputs.push({
6166
+ type: "function_call_output",
6167
+ call_id: entry.call.call_id,
6168
+ output: toOpenAiToolOutput(outputPayload)
6169
+ });
6170
+ }
6171
+ }
6172
+ const stepCompletedAtMs = Date.now();
6173
+ const timing = buildStepTiming({
5377
6174
  stepStartedAtMs,
5378
- stepCompletedAtMs: stepCompletedAtMs2,
6175
+ stepCompletedAtMs,
5379
6176
  modelCompletedAtMs,
5380
6177
  firstModelEventAtMs,
5381
6178
  schedulerMetrics,
5382
- toolExecutionMs: 0,
5383
- waitToolMs: 0
6179
+ toolExecutionMs,
6180
+ waitToolMs
5384
6181
  });
5385
6182
  steps.push({
5386
6183
  step: steps.length + 1,
5387
6184
  modelVersion,
5388
6185
  text: responseText || void 0,
5389
6186
  thoughts: reasoningSummary || void 0,
5390
- toolCalls: [],
6187
+ toolCalls: stepToolCalls,
5391
6188
  usage: usageTokens,
5392
6189
  costUsd: stepCostUsd,
5393
- timing: timing2
6190
+ timing
5394
6191
  });
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
6192
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6193
+ const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
6194
+ stepCallLogger?.complete({
6195
+ provider: "openai",
6196
+ model: request.model,
6197
+ modelVersion,
6198
+ step: turn,
6199
+ usage: usageTokens,
6200
+ costUsd: stepCostUsd,
6201
+ blocked,
6202
+ responseChars: responseText.length,
6203
+ thoughtChars: reasoningSummary.length,
6204
+ toolCalls: stepToolCalls.length,
6205
+ finalStep: false
5431
6206
  });
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
6207
+ previousResponseId = finalResponse.id;
6208
+ input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
6209
+ } catch (error) {
6210
+ stepCallLogger?.fail(error, {
6211
+ provider: "openai",
6212
+ model: request.model,
6213
+ modelVersion,
6214
+ step: turn,
6215
+ usage: usageTokens,
6216
+ blocked
5478
6217
  });
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
- }
6218
+ throw error;
5492
6219
  }
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
6220
  }
5518
6221
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5519
6222
  }
@@ -5531,242 +6234,656 @@ async function runToolLoop(request) {
5531
6234
  const stepStartedAtMs = Date.now();
5532
6235
  let firstModelEventAtMs;
5533
6236
  let thoughtDeltaEmitted = false;
6237
+ let sawResponseDelta = false;
6238
+ let modelVersion = request.model;
6239
+ let usageTokens;
6240
+ let responseText = "";
6241
+ let reasoningSummaryText = "";
5534
6242
  const markFirstModelEvent = () => {
5535
6243
  if (firstModelEventAtMs === void 0) {
5536
6244
  firstModelEventAtMs = Date.now();
5537
6245
  }
5538
6246
  };
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) }
6247
+ const stepRequestPayload = {
6248
+ model: providerInfo.model,
6249
+ store: false,
6250
+ stream: true,
6251
+ ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
6252
+ instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
6253
+ input,
6254
+ prompt_cache_key: promptCacheKey,
6255
+ include: ["reasoning.encrypted_content"],
6256
+ tools: openAiTools,
6257
+ tool_choice: "auto",
6258
+ parallel_tool_calls: true,
6259
+ reasoning: {
6260
+ effort: toOpenAiReasoningEffort(reasoningEffort),
6261
+ summary: "detailed"
5557
6262
  },
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 });
6263
+ text: { verbosity: resolveOpenAiVerbosity(request.model) }
6264
+ };
6265
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6266
+ provider: "chatgpt",
6267
+ modelId: request.model,
6268
+ requestPayload: stepRequestPayload,
6269
+ step: turn
6270
+ });
6271
+ try {
6272
+ const response = await collectChatGptCodexResponseWithRetry({
6273
+ sessionId: conversationId,
6274
+ request: stepRequestPayload,
6275
+ signal: request.signal,
6276
+ onDelta: (delta) => {
6277
+ if (delta.thoughtDelta) {
6278
+ markFirstModelEvent();
6279
+ thoughtDeltaEmitted = true;
6280
+ stepCallLogger?.appendThoughtDelta(delta.thoughtDelta);
6281
+ request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
6282
+ }
6283
+ if (delta.textDelta) {
6284
+ markFirstModelEvent();
6285
+ sawResponseDelta = true;
6286
+ stepCallLogger?.appendResponseDelta(delta.textDelta);
6287
+ request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
6288
+ }
5564
6289
  }
5565
- if (delta.textDelta) {
5566
- markFirstModelEvent();
5567
- request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
6290
+ });
6291
+ const modelCompletedAtMs = Date.now();
6292
+ modelVersion = response.model && !providerInfo.serviceTier ? `chatgpt-${response.model}` : request.model;
6293
+ usageTokens = extractChatGptUsageTokens(response.usage);
6294
+ const stepCostUsd = estimateCallCostUsd({
6295
+ modelId: modelVersion,
6296
+ tokens: usageTokens,
6297
+ responseImages: 0
6298
+ });
6299
+ totalCostUsd += stepCostUsd;
6300
+ responseText = (response.text ?? "").trim();
6301
+ reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
6302
+ if (!thoughtDeltaEmitted && reasoningSummaryText.length > 0) {
6303
+ stepCallLogger?.appendThoughtDelta(reasoningSummaryText);
6304
+ request.onEvent?.({ type: "delta", channel: "thought", text: reasoningSummaryText });
6305
+ }
6306
+ if (!sawResponseDelta && responseText.length > 0) {
6307
+ stepCallLogger?.appendResponseDelta(responseText);
6308
+ }
6309
+ const responseToolCalls = response.toolCalls ?? [];
6310
+ if (responseToolCalls.length === 0) {
6311
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6312
+ const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
6313
+ finalText = responseText;
6314
+ finalThoughts = reasoningSummaryText;
6315
+ const stepCompletedAtMs2 = Date.now();
6316
+ const timing2 = buildStepTiming({
6317
+ stepStartedAtMs,
6318
+ stepCompletedAtMs: stepCompletedAtMs2,
6319
+ modelCompletedAtMs,
6320
+ firstModelEventAtMs,
6321
+ toolExecutionMs: 0,
6322
+ waitToolMs: 0
6323
+ });
6324
+ steps.push({
6325
+ step: steps.length + 1,
6326
+ modelVersion,
6327
+ text: responseText || void 0,
6328
+ thoughts: reasoningSummaryText || void 0,
6329
+ toolCalls: [],
6330
+ usage: usageTokens,
6331
+ costUsd: stepCostUsd,
6332
+ timing: timing2
6333
+ });
6334
+ stepCallLogger?.complete({
6335
+ provider: "chatgpt",
6336
+ model: request.model,
6337
+ modelVersion,
6338
+ step: turn,
6339
+ usage: usageTokens,
6340
+ costUsd: stepCostUsd,
6341
+ responseChars: responseText.length,
6342
+ thoughtChars: reasoningSummaryText.length,
6343
+ toolCalls: 0,
6344
+ finalStep: steeringItems2.length === 0
6345
+ });
6346
+ if (steeringItems2.length === 0) {
6347
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5568
6348
  }
6349
+ const assistantItem = toChatGptAssistantMessage(responseText);
6350
+ input = assistantItem ? input.concat(assistantItem, steeringItems2) : input.concat(steeringItems2);
6351
+ continue;
5569
6352
  }
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({
6353
+ const toolCalls = [];
6354
+ const toolOutputs = [];
6355
+ const callInputs = responseToolCalls.map((call, index) => {
6356
+ const toolIndex = index + 1;
6357
+ const toolId = buildToolLogId(turn, toolIndex);
6358
+ const toolName = call.name;
6359
+ const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
6360
+ const ids = normalizeChatGptToolIds({
6361
+ callKind: call.kind,
6362
+ callId: call.callId,
6363
+ itemId: call.id
6364
+ });
6365
+ return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
6366
+ });
6367
+ for (const entry of callInputs) {
6368
+ request.onEvent?.({
6369
+ type: "tool_call",
6370
+ phase: "started",
6371
+ turn: entry.turn,
6372
+ toolIndex: entry.toolIndex,
6373
+ toolName: entry.toolName,
6374
+ toolId: entry.toolId,
6375
+ callKind: entry.call.kind,
6376
+ callId: entry.ids.callId,
6377
+ input: entry.value
6378
+ });
6379
+ }
6380
+ const callResults = await Promise.all(
6381
+ callInputs.map(async (entry) => {
6382
+ return await toolCallContextStorage.run(
6383
+ {
6384
+ toolName: entry.toolName,
6385
+ toolId: entry.toolId,
6386
+ turn: entry.turn,
6387
+ toolIndex: entry.toolIndex
6388
+ },
6389
+ async () => {
6390
+ const { result, outputPayload } = await executeToolCall({
6391
+ callKind: entry.call.kind,
6392
+ toolName: entry.toolName,
6393
+ tool: request.tools[entry.toolName],
6394
+ rawInput: entry.value,
6395
+ parseError: entry.parseError
6396
+ });
6397
+ return { entry, result, outputPayload };
6398
+ }
6399
+ );
6400
+ })
6401
+ );
6402
+ let toolExecutionMs = 0;
6403
+ let waitToolMs = 0;
6404
+ for (const { entry, result, outputPayload } of callResults) {
6405
+ toolCalls.push({ ...result, callId: entry.ids.callId });
6406
+ const callDurationMs = toToolResultDuration(result);
6407
+ toolExecutionMs += callDurationMs;
6408
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6409
+ waitToolMs += callDurationMs;
6410
+ }
6411
+ request.onEvent?.({
6412
+ type: "tool_call",
6413
+ phase: "completed",
6414
+ turn: entry.turn,
6415
+ toolIndex: entry.toolIndex,
6416
+ toolName: entry.toolName,
6417
+ toolId: entry.toolId,
6418
+ callKind: entry.call.kind,
6419
+ callId: entry.ids.callId,
6420
+ input: entry.value,
6421
+ output: result.output,
6422
+ error: result.error,
6423
+ durationMs: result.durationMs
6424
+ });
6425
+ if (entry.call.kind === "custom") {
6426
+ toolOutputs.push({
6427
+ type: "custom_tool_call",
6428
+ id: entry.ids.itemId,
6429
+ call_id: entry.ids.callId,
6430
+ name: entry.toolName,
6431
+ input: entry.call.input,
6432
+ status: "completed"
6433
+ });
6434
+ toolOutputs.push({
6435
+ type: "custom_tool_call_output",
6436
+ call_id: entry.ids.callId,
6437
+ output: toOpenAiToolOutput(outputPayload)
6438
+ });
6439
+ } else {
6440
+ toolOutputs.push({
6441
+ type: "function_call",
6442
+ id: entry.ids.itemId,
6443
+ call_id: entry.ids.callId,
6444
+ name: entry.toolName,
6445
+ arguments: entry.call.arguments,
6446
+ status: "completed"
6447
+ });
6448
+ toolOutputs.push({
6449
+ type: "function_call_output",
6450
+ call_id: entry.ids.callId,
6451
+ output: toOpenAiToolOutput(outputPayload)
6452
+ });
6453
+ }
6454
+ }
6455
+ const stepCompletedAtMs = Date.now();
6456
+ const timing = buildStepTiming({
5593
6457
  stepStartedAtMs,
5594
- stepCompletedAtMs: stepCompletedAtMs2,
6458
+ stepCompletedAtMs,
5595
6459
  modelCompletedAtMs,
5596
6460
  firstModelEventAtMs,
5597
- toolExecutionMs: 0,
5598
- waitToolMs: 0
6461
+ toolExecutionMs,
6462
+ waitToolMs
5599
6463
  });
5600
6464
  steps.push({
5601
6465
  step: steps.length + 1,
5602
6466
  modelVersion,
5603
6467
  text: responseText || void 0,
5604
6468
  thoughts: reasoningSummaryText || void 0,
5605
- toolCalls: [],
6469
+ toolCalls,
5606
6470
  usage: usageTokens,
5607
6471
  costUsd: stepCostUsd,
5608
- timing: timing2
6472
+ timing
5609
6473
  });
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
6474
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6475
+ const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
6476
+ stepCallLogger?.complete({
6477
+ provider: "chatgpt",
6478
+ model: request.model,
6479
+ modelVersion,
6480
+ step: turn,
6481
+ usage: usageTokens,
6482
+ costUsd: stepCostUsd,
6483
+ responseChars: responseText.length,
6484
+ thoughtChars: reasoningSummaryText.length,
6485
+ toolCalls: toolCalls.length,
6486
+ finalStep: false
5628
6487
  });
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
6488
+ input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
6489
+ } catch (error) {
6490
+ stepCallLogger?.fail(error, {
6491
+ provider: "chatgpt",
6492
+ model: request.model,
6493
+ modelVersion,
6494
+ step: turn,
6495
+ usage: usageTokens
5642
6496
  });
6497
+ throw error;
5643
6498
  }
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
- })
6499
+ }
6500
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
6501
+ }
6502
+ if (providerInfo.provider === "fireworks") {
6503
+ if (request.modelTools && request.modelTools.length > 0) {
6504
+ throw new Error(
6505
+ "Fireworks provider does not support provider-native modelTools in runToolLoop."
5665
6506
  );
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;
6507
+ }
6508
+ const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
6509
+ const messages = toFireworksMessages(contents);
6510
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
6511
+ const turn = stepIndex + 1;
6512
+ const stepStartedAtMs = Date.now();
6513
+ let schedulerMetrics;
6514
+ let modelVersion = request.model;
6515
+ let usageTokens;
6516
+ let responseText = "";
6517
+ let blocked = false;
6518
+ const stepRequestPayload = {
6519
+ model: providerInfo.model,
6520
+ messages,
6521
+ tools: fireworksTools,
6522
+ tool_choice: "auto",
6523
+ parallel_tool_calls: true
6524
+ };
6525
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6526
+ provider: "fireworks",
6527
+ modelId: request.model,
6528
+ requestPayload: stepRequestPayload,
6529
+ step: turn
6530
+ });
6531
+ try {
6532
+ const response = await runFireworksCall(
6533
+ async (client) => {
6534
+ return await client.chat.completions.create(
6535
+ {
6536
+ model: providerInfo.model,
6537
+ messages,
6538
+ tools: fireworksTools,
6539
+ tool_choice: "auto",
6540
+ parallel_tool_calls: true
6541
+ },
6542
+ { signal: request.signal }
6543
+ );
6544
+ },
6545
+ providerInfo.model,
6546
+ {
6547
+ onSettled: (metrics) => {
6548
+ schedulerMetrics = metrics;
6549
+ }
6550
+ }
6551
+ );
6552
+ const modelCompletedAtMs = Date.now();
6553
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
6554
+ request.onEvent?.({ type: "model", modelVersion });
6555
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
6556
+ if (choice?.finish_reason === "content_filter") {
6557
+ blocked = true;
6558
+ request.onEvent?.({ type: "blocked" });
5674
6559
  }
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
6560
+ const message = choice?.message;
6561
+ responseText = extractFireworksMessageText(message).trim();
6562
+ if (responseText.length > 0) {
6563
+ stepCallLogger?.appendResponseDelta(responseText);
6564
+ request.onEvent?.({ type: "delta", channel: "response", text: responseText });
6565
+ }
6566
+ usageTokens = extractFireworksUsageTokens(response.usage);
6567
+ const stepCostUsd = estimateCallCostUsd({
6568
+ modelId: modelVersion,
6569
+ tokens: usageTokens,
6570
+ responseImages: 0
5688
6571
  });
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"
6572
+ totalCostUsd += stepCostUsd;
6573
+ if (usageTokens) {
6574
+ request.onEvent?.({
6575
+ type: "usage",
6576
+ usage: usageTokens,
6577
+ costUsd: stepCostUsd,
6578
+ modelVersion
5697
6579
  });
5698
- toolOutputs.push({
5699
- type: "custom_tool_call_output",
5700
- call_id: entry.ids.callId,
5701
- output: toOpenAiToolOutput(outputPayload)
6580
+ }
6581
+ const responseToolCalls = extractFireworksToolCalls(message);
6582
+ if (responseToolCalls.length === 0) {
6583
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6584
+ const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
6585
+ finalText = responseText;
6586
+ finalThoughts = "";
6587
+ const stepCompletedAtMs2 = Date.now();
6588
+ const timing2 = buildStepTiming({
6589
+ stepStartedAtMs,
6590
+ stepCompletedAtMs: stepCompletedAtMs2,
6591
+ modelCompletedAtMs,
6592
+ schedulerMetrics,
6593
+ toolExecutionMs: 0,
6594
+ waitToolMs: 0
5702
6595
  });
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"
6596
+ steps.push({
6597
+ step: steps.length + 1,
6598
+ modelVersion,
6599
+ text: responseText || void 0,
6600
+ thoughts: void 0,
6601
+ toolCalls: [],
6602
+ usage: usageTokens,
6603
+ costUsd: stepCostUsd,
6604
+ timing: timing2
5711
6605
  });
5712
- toolOutputs.push({
5713
- type: "function_call_output",
5714
- call_id: entry.ids.callId,
5715
- output: toOpenAiToolOutput(outputPayload)
6606
+ stepCallLogger?.complete({
6607
+ provider: "fireworks",
6608
+ model: request.model,
6609
+ modelVersion,
6610
+ step: turn,
6611
+ usage: usageTokens,
6612
+ costUsd: stepCostUsd,
6613
+ blocked,
6614
+ responseChars: responseText.length,
6615
+ thoughtChars: 0,
6616
+ toolCalls: 0,
6617
+ finalStep: steeringMessages.length === 0
5716
6618
  });
6619
+ if (steeringMessages.length === 0) {
6620
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
6621
+ }
6622
+ if (responseText.length > 0) {
6623
+ messages.push({ role: "assistant", content: responseText });
6624
+ }
6625
+ messages.push(...steeringMessages);
6626
+ continue;
5717
6627
  }
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);
6628
+ const stepToolCalls = [];
6629
+ const callInputs = responseToolCalls.map((call, index) => {
6630
+ const toolIndex = index + 1;
6631
+ const toolId = buildToolLogId(turn, toolIndex);
6632
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
6633
+ return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
6634
+ });
6635
+ for (const entry of callInputs) {
6636
+ request.onEvent?.({
6637
+ type: "tool_call",
6638
+ phase: "started",
6639
+ turn: entry.turn,
6640
+ toolIndex: entry.toolIndex,
6641
+ toolName: entry.toolName,
6642
+ toolId: entry.toolId,
6643
+ callKind: "function",
6644
+ callId: entry.call.id,
6645
+ input: entry.value
6646
+ });
6647
+ }
6648
+ const callResults = await Promise.all(
6649
+ callInputs.map(async (entry) => {
6650
+ return await toolCallContextStorage.run(
6651
+ {
6652
+ toolName: entry.toolName,
6653
+ toolId: entry.toolId,
6654
+ turn: entry.turn,
6655
+ toolIndex: entry.toolIndex
6656
+ },
6657
+ async () => {
6658
+ const { result, outputPayload } = await executeToolCall({
6659
+ callKind: "function",
6660
+ toolName: entry.toolName,
6661
+ tool: request.tools[entry.toolName],
6662
+ rawInput: entry.value,
6663
+ parseError: entry.parseError
6664
+ });
6665
+ return { entry, result, outputPayload };
6666
+ }
6667
+ );
6668
+ })
6669
+ );
6670
+ const assistantToolCalls = [];
6671
+ const toolMessages = [];
6672
+ let toolExecutionMs = 0;
6673
+ let waitToolMs = 0;
6674
+ for (const { entry, result, outputPayload } of callResults) {
6675
+ stepToolCalls.push({ ...result, callId: entry.call.id });
6676
+ const callDurationMs = toToolResultDuration(result);
6677
+ toolExecutionMs += callDurationMs;
6678
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6679
+ waitToolMs += callDurationMs;
6680
+ }
6681
+ request.onEvent?.({
6682
+ type: "tool_call",
6683
+ phase: "completed",
6684
+ turn: entry.turn,
6685
+ toolIndex: entry.toolIndex,
6686
+ toolName: entry.toolName,
6687
+ toolId: entry.toolId,
6688
+ callKind: "function",
6689
+ callId: entry.call.id,
6690
+ input: entry.value,
6691
+ output: result.output,
6692
+ error: result.error,
6693
+ durationMs: result.durationMs
6694
+ });
6695
+ assistantToolCalls.push({
6696
+ id: entry.call.id,
6697
+ type: "function",
6698
+ function: {
6699
+ name: entry.toolName,
6700
+ arguments: entry.call.arguments
6701
+ }
6702
+ });
6703
+ toolMessages.push({
6704
+ role: "tool",
6705
+ tool_call_id: entry.call.id,
6706
+ content: mergeToolOutput(outputPayload)
6707
+ });
6708
+ }
6709
+ const stepCompletedAtMs = Date.now();
6710
+ const timing = buildStepTiming({
6711
+ stepStartedAtMs,
6712
+ stepCompletedAtMs,
6713
+ modelCompletedAtMs,
6714
+ schedulerMetrics,
6715
+ toolExecutionMs,
6716
+ waitToolMs
6717
+ });
6718
+ steps.push({
6719
+ step: steps.length + 1,
6720
+ modelVersion,
6721
+ text: responseText || void 0,
6722
+ thoughts: void 0,
6723
+ toolCalls: stepToolCalls,
6724
+ usage: usageTokens,
6725
+ costUsd: stepCostUsd,
6726
+ timing
6727
+ });
6728
+ stepCallLogger?.complete({
6729
+ provider: "fireworks",
6730
+ model: request.model,
6731
+ modelVersion,
6732
+ step: turn,
6733
+ usage: usageTokens,
6734
+ costUsd: stepCostUsd,
6735
+ blocked,
6736
+ responseChars: responseText.length,
6737
+ thoughtChars: 0,
6738
+ toolCalls: stepToolCalls.length,
6739
+ finalStep: false
6740
+ });
6741
+ messages.push({
6742
+ role: "assistant",
6743
+ ...responseText.length > 0 ? { content: responseText } : {},
6744
+ tool_calls: assistantToolCalls
6745
+ });
6746
+ messages.push(...toolMessages);
6747
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6748
+ if (steeringInput.length > 0) {
6749
+ messages.push(...toFireworksMessages(steeringInput));
6750
+ }
6751
+ } catch (error) {
6752
+ stepCallLogger?.fail(error, {
6753
+ provider: "fireworks",
6754
+ model: request.model,
6755
+ modelVersion,
6756
+ step: turn,
6757
+ usage: usageTokens,
6758
+ blocked
6759
+ });
6760
+ throw error;
6761
+ }
5741
6762
  }
5742
6763
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5743
6764
  }
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(
6765
+ const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
6766
+ const geminiNativeTools = toGeminiTools(request.modelTools);
6767
+ const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
6768
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
6769
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
6770
+ const turn = stepIndex + 1;
6771
+ const stepStartedAtMs = Date.now();
6772
+ let firstModelEventAtMs;
6773
+ let schedulerMetrics;
6774
+ let modelVersion = request.model;
6775
+ let usageTokens;
6776
+ let responseText = "";
6777
+ let thoughtsText = "";
6778
+ const markFirstModelEvent = () => {
6779
+ if (firstModelEventAtMs === void 0) {
6780
+ firstModelEventAtMs = Date.now();
6781
+ }
6782
+ };
6783
+ const thinkingConfig = resolveGeminiThinkingConfig(request.model, request.thinkingLevel);
6784
+ const config = {
6785
+ maxOutputTokens: 32e3,
6786
+ tools: geminiTools,
6787
+ toolConfig: {
6788
+ functionCallingConfig: {
6789
+ mode: import_genai2.FunctionCallingConfigMode.VALIDATED
6790
+ }
6791
+ },
6792
+ ...thinkingConfig ? { thinkingConfig } : {}
6793
+ };
6794
+ const onEvent = request.onEvent;
6795
+ const stepRequestPayload = {
6796
+ model: request.model,
6797
+ contents: geminiContents,
6798
+ config
6799
+ };
6800
+ const stepCallLogger = startLlmCallLoggerFromPayload({
6801
+ provider: "gemini",
6802
+ modelId: request.model,
6803
+ requestPayload: stepRequestPayload,
6804
+ step: turn
6805
+ });
6806
+ try {
6807
+ const response = await runGeminiCall(
5757
6808
  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
- );
6809
+ const stream = await client.models.generateContentStream({
6810
+ model: request.model,
6811
+ contents: geminiContents,
6812
+ config
6813
+ });
6814
+ let responseText2 = "";
6815
+ let thoughtsText2 = "";
6816
+ const modelParts = [];
6817
+ const functionCalls = [];
6818
+ const seenFunctionCallIds = /* @__PURE__ */ new Set();
6819
+ const seenFunctionCallKeys = /* @__PURE__ */ new Set();
6820
+ let latestUsageMetadata;
6821
+ let resolvedModelVersion;
6822
+ for await (const chunk of stream) {
6823
+ markFirstModelEvent();
6824
+ if (chunk.modelVersion) {
6825
+ resolvedModelVersion = chunk.modelVersion;
6826
+ onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
6827
+ }
6828
+ if (chunk.usageMetadata) {
6829
+ latestUsageMetadata = chunk.usageMetadata;
6830
+ }
6831
+ const candidates = chunk.candidates;
6832
+ if (!candidates || candidates.length === 0) {
6833
+ continue;
6834
+ }
6835
+ const primary = candidates[0];
6836
+ const parts = primary?.content?.parts;
6837
+ if (!parts || parts.length === 0) {
6838
+ continue;
6839
+ }
6840
+ for (const part of parts) {
6841
+ modelParts.push(part);
6842
+ const call = part.functionCall;
6843
+ if (call) {
6844
+ const id = typeof call.id === "string" ? call.id : "";
6845
+ const shouldAdd = (() => {
6846
+ if (id.length > 0) {
6847
+ if (seenFunctionCallIds.has(id)) {
6848
+ return false;
6849
+ }
6850
+ seenFunctionCallIds.add(id);
6851
+ return true;
6852
+ }
6853
+ const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
6854
+ if (seenFunctionCallKeys.has(key)) {
6855
+ return false;
6856
+ }
6857
+ seenFunctionCallKeys.add(key);
6858
+ return true;
6859
+ })();
6860
+ if (shouldAdd) {
6861
+ functionCalls.push(call);
6862
+ }
6863
+ }
6864
+ if (typeof part.text === "string" && part.text.length > 0) {
6865
+ if (part.thought) {
6866
+ thoughtsText2 += part.text;
6867
+ stepCallLogger?.appendThoughtDelta(part.text);
6868
+ onEvent?.({ type: "delta", channel: "thought", text: part.text });
6869
+ } else {
6870
+ responseText2 += part.text;
6871
+ stepCallLogger?.appendResponseDelta(part.text);
6872
+ onEvent?.({ type: "delta", channel: "response", text: part.text });
6873
+ }
6874
+ }
6875
+ }
6876
+ }
6877
+ return {
6878
+ responseText: responseText2,
6879
+ thoughtsText: thoughtsText2,
6880
+ functionCalls,
6881
+ modelParts,
6882
+ usageMetadata: latestUsageMetadata,
6883
+ modelVersion: resolvedModelVersion ?? request.model
6884
+ };
5768
6885
  },
5769
- providerInfo.model,
6886
+ request.model,
5770
6887
  {
5771
6888
  onSettled: (metrics) => {
5772
6889
  schedulerMetrics = metrics;
@@ -5774,43 +6891,26 @@ async function runToolLoop(request) {
5774
6891
  }
5775
6892
  );
5776
6893
  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);
6894
+ usageTokens = extractGeminiUsageTokens(response.usageMetadata);
6895
+ modelVersion = response.modelVersion ?? request.model;
6896
+ responseText = response.responseText.trim();
6897
+ thoughtsText = response.thoughtsText.trim();
5789
6898
  const stepCostUsd = estimateCallCostUsd({
5790
6899
  modelId: modelVersion,
5791
6900
  tokens: usageTokens,
5792
6901
  responseImages: 0
5793
6902
  });
5794
6903
  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) {
6904
+ if (response.functionCalls.length === 0) {
5805
6905
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5806
- const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
5807
6906
  finalText = responseText;
5808
- finalThoughts = "";
6907
+ finalThoughts = thoughtsText;
5809
6908
  const stepCompletedAtMs2 = Date.now();
5810
6909
  const timing2 = buildStepTiming({
5811
6910
  stepStartedAtMs,
5812
6911
  stepCompletedAtMs: stepCompletedAtMs2,
5813
6912
  modelCompletedAtMs,
6913
+ firstModelEventAtMs,
5814
6914
  schedulerMetrics,
5815
6915
  toolExecutionMs: 0,
5816
6916
  waitToolMs: 0
@@ -5818,31 +6918,65 @@ async function runToolLoop(request) {
5818
6918
  steps.push({
5819
6919
  step: steps.length + 1,
5820
6920
  modelVersion,
5821
- text: responseText || void 0,
5822
- thoughts: void 0,
6921
+ text: finalText || void 0,
6922
+ thoughts: finalThoughts || void 0,
5823
6923
  toolCalls: [],
5824
6924
  usage: usageTokens,
5825
6925
  costUsd: stepCostUsd,
5826
6926
  timing: timing2
5827
6927
  });
5828
- if (steeringMessages.length === 0) {
6928
+ stepCallLogger?.complete({
6929
+ provider: "gemini",
6930
+ model: request.model,
6931
+ modelVersion,
6932
+ step: turn,
6933
+ usage: usageTokens,
6934
+ costUsd: stepCostUsd,
6935
+ responseChars: responseText.length,
6936
+ thoughtChars: thoughtsText.length,
6937
+ toolCalls: 0,
6938
+ finalStep: steeringInput2.length === 0
6939
+ });
6940
+ if (steeringInput2.length === 0) {
5829
6941
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5830
6942
  }
5831
- if (responseText.length > 0) {
5832
- messages.push({ role: "assistant", content: responseText });
6943
+ const modelPartsForHistory2 = response.modelParts.filter(
6944
+ (part) => !(typeof part.text === "string" && part.thought === true)
6945
+ );
6946
+ if (modelPartsForHistory2.length > 0) {
6947
+ geminiContents.push({ role: "model", parts: modelPartsForHistory2 });
6948
+ } else if (response.responseText.length > 0) {
6949
+ geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
5833
6950
  }
5834
- messages.push(...steeringMessages);
6951
+ geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
5835
6952
  continue;
5836
6953
  }
5837
- const stepToolCalls = [];
5838
- const callInputs = responseToolCalls.map((call, index) => {
6954
+ const toolCalls = [];
6955
+ const modelPartsForHistory = response.modelParts.filter(
6956
+ (part) => !(typeof part.text === "string" && part.thought === true)
6957
+ );
6958
+ if (modelPartsForHistory.length > 0) {
6959
+ geminiContents.push({ role: "model", parts: modelPartsForHistory });
6960
+ } else {
6961
+ const parts = [];
6962
+ if (response.responseText) {
6963
+ parts.push({ text: response.responseText });
6964
+ }
6965
+ for (const call of response.functionCalls) {
6966
+ parts.push({ functionCall: call });
6967
+ }
6968
+ geminiContents.push({ role: "model", parts });
6969
+ }
6970
+ const responseParts = [];
6971
+ const callInputs = response.functionCalls.map((call, index) => {
5839
6972
  const toolIndex = index + 1;
5840
6973
  const toolId = buildToolLogId(turn, toolIndex);
5841
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5842
- return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
6974
+ const toolName = call.name ?? "unknown";
6975
+ const rawInput = call.args ?? {};
6976
+ return { call, toolName, rawInput, toolId, turn, toolIndex };
5843
6977
  });
5844
6978
  for (const entry of callInputs) {
5845
- request.onEvent?.({
6979
+ onEvent?.({
5846
6980
  type: "tool_call",
5847
6981
  phase: "started",
5848
6982
  turn: entry.turn,
@@ -5851,7 +6985,7 @@ async function runToolLoop(request) {
5851
6985
  toolId: entry.toolId,
5852
6986
  callKind: "function",
5853
6987
  callId: entry.call.id,
5854
- input: entry.value
6988
+ input: entry.rawInput
5855
6989
  });
5856
6990
  }
5857
6991
  const callResults = await Promise.all(
@@ -5868,26 +7002,23 @@ async function runToolLoop(request) {
5868
7002
  callKind: "function",
5869
7003
  toolName: entry.toolName,
5870
7004
  tool: request.tools[entry.toolName],
5871
- rawInput: entry.value,
5872
- parseError: entry.parseError
7005
+ rawInput: entry.rawInput
5873
7006
  });
5874
7007
  return { entry, result, outputPayload };
5875
7008
  }
5876
7009
  );
5877
7010
  })
5878
7011
  );
5879
- const assistantToolCalls = [];
5880
- const toolMessages = [];
5881
7012
  let toolExecutionMs = 0;
5882
7013
  let waitToolMs = 0;
5883
7014
  for (const { entry, result, outputPayload } of callResults) {
5884
- stepToolCalls.push({ ...result, callId: entry.call.id });
7015
+ toolCalls.push({ ...result, callId: entry.call.id });
5885
7016
  const callDurationMs = toToolResultDuration(result);
5886
7017
  toolExecutionMs += callDurationMs;
5887
7018
  if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5888
7019
  waitToolMs += callDurationMs;
5889
7020
  }
5890
- request.onEvent?.({
7021
+ onEvent?.({
5891
7022
  type: "tool_call",
5892
7023
  phase: "completed",
5893
7024
  turn: entry.turn,
@@ -5896,30 +7027,26 @@ async function runToolLoop(request) {
5896
7027
  toolId: entry.toolId,
5897
7028
  callKind: "function",
5898
7029
  callId: entry.call.id,
5899
- input: entry.value,
7030
+ input: entry.rawInput,
5900
7031
  output: result.output,
5901
7032
  error: result.error,
5902
7033
  durationMs: result.durationMs
5903
7034
  });
5904
- assistantToolCalls.push({
5905
- id: entry.call.id,
5906
- type: "function",
5907
- function: {
7035
+ const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
7036
+ responseParts.push({
7037
+ functionResponse: {
5908
7038
  name: entry.toolName,
5909
- arguments: entry.call.arguments
7039
+ response: responsePayload,
7040
+ ...entry.call.id ? { id: entry.call.id } : {}
5910
7041
  }
5911
7042
  });
5912
- toolMessages.push({
5913
- role: "tool",
5914
- tool_call_id: entry.call.id,
5915
- content: mergeToolOutput(outputPayload)
5916
- });
5917
7043
  }
5918
7044
  const stepCompletedAtMs = Date.now();
5919
7045
  const timing = buildStepTiming({
5920
7046
  stepStartedAtMs,
5921
7047
  stepCompletedAtMs,
5922
7048
  modelCompletedAtMs,
7049
+ firstModelEventAtMs,
5923
7050
  schedulerMetrics,
5924
7051
  toolExecutionMs,
5925
7052
  waitToolMs
@@ -5928,296 +7055,40 @@ async function runToolLoop(request) {
5928
7055
  step: steps.length + 1,
5929
7056
  modelVersion,
5930
7057
  text: responseText || void 0,
5931
- thoughts: void 0,
5932
- toolCalls: stepToolCalls,
7058
+ thoughts: thoughtsText || void 0,
7059
+ toolCalls,
5933
7060
  usage: usageTokens,
5934
7061
  costUsd: stepCostUsd,
5935
7062
  timing
5936
7063
  });
5937
- messages.push({
5938
- role: "assistant",
5939
- ...responseText.length > 0 ? { content: responseText } : {},
5940
- tool_calls: assistantToolCalls
7064
+ stepCallLogger?.complete({
7065
+ provider: "gemini",
7066
+ model: request.model,
7067
+ modelVersion,
7068
+ step: turn,
7069
+ usage: usageTokens,
7070
+ costUsd: stepCostUsd,
7071
+ responseChars: responseText.length,
7072
+ thoughtChars: thoughtsText.length,
7073
+ toolCalls: toolCalls.length,
7074
+ finalStep: false
5941
7075
  });
5942
- messages.push(...toolMessages);
7076
+ geminiContents.push({ role: "user", parts: responseParts });
5943
7077
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5944
7078
  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
- }
7079
+ geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
6057
7080
  }
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,
7081
+ } catch (error) {
7082
+ stepCallLogger?.fail(error, {
7083
+ provider: "gemini",
7084
+ model: request.model,
6084
7085
  modelVersion,
6085
- text: finalText || void 0,
6086
- thoughts: finalThoughts || void 0,
6087
- toolCalls: [],
7086
+ step: turn,
6088
7087
  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
- }
7088
+ responseChars: responseText.length,
7089
+ thoughtChars: thoughtsText.length
6195
7090
  });
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));
7091
+ throw error;
6221
7092
  }
6222
7093
  }
6223
7094
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
@@ -6531,6 +7402,7 @@ ${lines}`;
6531
7402
 
6532
7403
  // src/agent.ts
6533
7404
  var import_node_crypto3 = require("crypto");
7405
+ var import_node_path7 = __toESM(require("path"), 1);
6534
7406
 
6535
7407
  // src/agent/subagents.ts
6536
7408
  var import_node_crypto2 = require("crypto");
@@ -7005,26 +7877,26 @@ function resolveInputItemsText(items) {
7005
7877
  }
7006
7878
  const itemType = typeof item.type === "string" ? item.type.trim() : "";
7007
7879
  const name = typeof item.name === "string" ? item.name.trim() : "";
7008
- const path6 = typeof item.path === "string" ? item.path.trim() : "";
7880
+ const path8 = typeof item.path === "string" ? item.path.trim() : "";
7009
7881
  const imageUrl = typeof item.image_url === "string" ? item.image_url.trim() : "";
7010
7882
  if (itemType === "image") {
7011
7883
  lines.push("[image]");
7012
7884
  continue;
7013
7885
  }
7014
- if (itemType === "local_image" && path6) {
7015
- lines.push(`[local_image:${path6}]`);
7886
+ if (itemType === "local_image" && path8) {
7887
+ lines.push(`[local_image:${path8}]`);
7016
7888
  continue;
7017
7889
  }
7018
- if (itemType === "skill" && name && path6) {
7019
- lines.push(`[skill:$${name}](${path6})`);
7890
+ if (itemType === "skill" && name && path8) {
7891
+ lines.push(`[skill:$${name}](${path8})`);
7020
7892
  continue;
7021
7893
  }
7022
- if (itemType === "mention" && name && path6) {
7023
- lines.push(`[mention:$${name}](${path6})`);
7894
+ if (itemType === "mention" && name && path8) {
7895
+ lines.push(`[mention:$${name}](${path8})`);
7024
7896
  continue;
7025
7897
  }
7026
- if (path6 || imageUrl) {
7027
- lines.push(`[${itemType || "input"}:${path6 || imageUrl}]`);
7898
+ if (path8 || imageUrl) {
7899
+ lines.push(`[${itemType || "input"}:${path8 || imageUrl}]`);
7028
7900
  continue;
7029
7901
  }
7030
7902
  if (name) {
@@ -7149,7 +8021,7 @@ function startRun(agent, options) {
7149
8021
  setLifecycle(agent, "idle", "input_queued", `Subagent ${agent.id} run interrupted.`);
7150
8022
  return;
7151
8023
  }
7152
- const message = toErrorMessage(error);
8024
+ const message = toErrorMessage2(error);
7153
8025
  agent.lastError = message;
7154
8026
  setLifecycle(agent, "failed", "run_failed", `Subagent ${agent.id} failed: ${message}`);
7155
8027
  emitBackgroundNotification(agent, options);
@@ -7329,7 +8201,7 @@ function trimToUndefined(value) {
7329
8201
  const trimmed = value?.trim();
7330
8202
  return trimmed && trimmed.length > 0 ? trimmed : void 0;
7331
8203
  }
7332
- function toErrorMessage(error) {
8204
+ function toErrorMessage2(error) {
7333
8205
  if (error instanceof Error) {
7334
8206
  return error.message;
7335
8207
  }
@@ -7342,27 +8214,27 @@ function sleep2(ms) {
7342
8214
  }
7343
8215
 
7344
8216
  // src/tools/filesystemTools.ts
7345
- var import_node_path5 = __toESM(require("path"), 1);
7346
- var import_node_buffer3 = require("buffer");
8217
+ var import_node_path6 = __toESM(require("path"), 1);
8218
+ var import_node_buffer4 = require("buffer");
7347
8219
  var import_zod6 = require("zod");
7348
8220
 
7349
8221
  // src/tools/applyPatch.ts
7350
- var import_node_path4 = __toESM(require("path"), 1);
8222
+ var import_node_path5 = __toESM(require("path"), 1);
7351
8223
  var import_zod5 = require("zod");
7352
8224
 
7353
8225
  // src/tools/filesystem.ts
7354
8226
  var import_node_fs3 = require("fs");
7355
- var import_node_path3 = __toESM(require("path"), 1);
8227
+ var import_node_path4 = __toESM(require("path"), 1);
7356
8228
  var InMemoryAgentFilesystem = class {
7357
8229
  #files = /* @__PURE__ */ new Map();
7358
8230
  #dirs = /* @__PURE__ */ new Map();
7359
8231
  #clock = 0;
7360
8232
  constructor(initialFiles = {}) {
7361
- const root = import_node_path3.default.resolve("/");
8233
+ const root = import_node_path4.default.resolve("/");
7362
8234
  this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
7363
8235
  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));
8236
+ const absolutePath = import_node_path4.default.resolve(filePath);
8237
+ this.#ensureDirSync(import_node_path4.default.dirname(absolutePath));
7366
8238
  this.#files.set(absolutePath, {
7367
8239
  content,
7368
8240
  mtimeMs: this.#nextMtime()
@@ -7370,7 +8242,7 @@ var InMemoryAgentFilesystem = class {
7370
8242
  }
7371
8243
  }
7372
8244
  async readTextFile(filePath) {
7373
- const absolutePath = import_node_path3.default.resolve(filePath);
8245
+ const absolutePath = import_node_path4.default.resolve(filePath);
7374
8246
  const file = this.#files.get(absolutePath);
7375
8247
  if (!file) {
7376
8248
  throw createNoSuchFileError("open", absolutePath);
@@ -7382,24 +8254,24 @@ var InMemoryAgentFilesystem = class {
7382
8254
  return Buffer.from(content, "utf8");
7383
8255
  }
7384
8256
  async writeTextFile(filePath, content) {
7385
- const absolutePath = import_node_path3.default.resolve(filePath);
7386
- const parentPath = import_node_path3.default.dirname(absolutePath);
8257
+ const absolutePath = import_node_path4.default.resolve(filePath);
8258
+ const parentPath = import_node_path4.default.dirname(absolutePath);
7387
8259
  if (!this.#dirs.has(parentPath)) {
7388
8260
  throw createNoSuchFileError("open", parentPath);
7389
8261
  }
7390
8262
  this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
7391
8263
  }
7392
8264
  async deleteFile(filePath) {
7393
- const absolutePath = import_node_path3.default.resolve(filePath);
8265
+ const absolutePath = import_node_path4.default.resolve(filePath);
7394
8266
  if (!this.#files.delete(absolutePath)) {
7395
8267
  throw createNoSuchFileError("unlink", absolutePath);
7396
8268
  }
7397
8269
  }
7398
8270
  async ensureDir(directoryPath) {
7399
- this.#ensureDirSync(import_node_path3.default.resolve(directoryPath));
8271
+ this.#ensureDirSync(import_node_path4.default.resolve(directoryPath));
7400
8272
  }
7401
8273
  async readDir(directoryPath) {
7402
- const absolutePath = import_node_path3.default.resolve(directoryPath);
8274
+ const absolutePath = import_node_path4.default.resolve(directoryPath);
7403
8275
  const directory = this.#dirs.get(absolutePath);
7404
8276
  if (!directory) {
7405
8277
  throw createNoSuchFileError("scandir", absolutePath);
@@ -7410,10 +8282,10 @@ var InMemoryAgentFilesystem = class {
7410
8282
  if (dirPath === absolutePath) {
7411
8283
  continue;
7412
8284
  }
7413
- if (import_node_path3.default.dirname(dirPath) !== absolutePath) {
8285
+ if (import_node_path4.default.dirname(dirPath) !== absolutePath) {
7414
8286
  continue;
7415
8287
  }
7416
- const name = import_node_path3.default.basename(dirPath);
8288
+ const name = import_node_path4.default.basename(dirPath);
7417
8289
  if (seenNames.has(name)) {
7418
8290
  continue;
7419
8291
  }
@@ -7426,10 +8298,10 @@ var InMemoryAgentFilesystem = class {
7426
8298
  });
7427
8299
  }
7428
8300
  for (const [filePath, fileRecord] of this.#files.entries()) {
7429
- if (import_node_path3.default.dirname(filePath) !== absolutePath) {
8301
+ if (import_node_path4.default.dirname(filePath) !== absolutePath) {
7430
8302
  continue;
7431
8303
  }
7432
- const name = import_node_path3.default.basename(filePath);
8304
+ const name = import_node_path4.default.basename(filePath);
7433
8305
  if (seenNames.has(name)) {
7434
8306
  continue;
7435
8307
  }
@@ -7445,7 +8317,7 @@ var InMemoryAgentFilesystem = class {
7445
8317
  return entries;
7446
8318
  }
7447
8319
  async stat(entryPath) {
7448
- const absolutePath = import_node_path3.default.resolve(entryPath);
8320
+ const absolutePath = import_node_path4.default.resolve(entryPath);
7449
8321
  const file = this.#files.get(absolutePath);
7450
8322
  if (file) {
7451
8323
  return { kind: "file", mtimeMs: file.mtimeMs };
@@ -7461,7 +8333,7 @@ var InMemoryAgentFilesystem = class {
7461
8333
  return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
7462
8334
  }
7463
8335
  #ensureDirSync(directoryPath) {
7464
- const absolutePath = import_node_path3.default.resolve(directoryPath);
8336
+ const absolutePath = import_node_path4.default.resolve(directoryPath);
7465
8337
  const parts = [];
7466
8338
  let cursor = absolutePath;
7467
8339
  for (; ; ) {
@@ -7469,7 +8341,7 @@ var InMemoryAgentFilesystem = class {
7469
8341
  break;
7470
8342
  }
7471
8343
  parts.push(cursor);
7472
- const parent = import_node_path3.default.dirname(cursor);
8344
+ const parent = import_node_path4.default.dirname(cursor);
7473
8345
  if (parent === cursor) {
7474
8346
  break;
7475
8347
  }
@@ -7503,7 +8375,7 @@ function createNodeAgentFilesystem() {
7503
8375
  const entries = await import_node_fs3.promises.readdir(directoryPath, { withFileTypes: true });
7504
8376
  const result = [];
7505
8377
  for (const entry of entries) {
7506
- const entryPath = import_node_path3.default.resolve(directoryPath, entry.name);
8378
+ const entryPath = import_node_path4.default.resolve(directoryPath, entry.name);
7507
8379
  const stats = await import_node_fs3.promises.lstat(entryPath);
7508
8380
  result.push({
7509
8381
  name: entry.name,
@@ -7667,7 +8539,7 @@ function createApplyPatchTool(options = {}) {
7667
8539
  });
7668
8540
  }
7669
8541
  async function applyPatch(request) {
7670
- const cwd = import_node_path4.default.resolve(request.cwd ?? process.cwd());
8542
+ const cwd = import_node_path5.default.resolve(request.cwd ?? process.cwd());
7671
8543
  const adapter = request.fs ?? createNodeAgentFilesystem();
7672
8544
  const allowOutsideCwd = request.allowOutsideCwd === true;
7673
8545
  const patchBytes = Buffer.byteLength(request.patch, "utf8");
@@ -7689,7 +8561,7 @@ async function applyPatch(request) {
7689
8561
  kind: "add",
7690
8562
  path: absolutePath2
7691
8563
  });
7692
- await adapter.ensureDir(import_node_path4.default.dirname(absolutePath2));
8564
+ await adapter.ensureDir(import_node_path5.default.dirname(absolutePath2));
7693
8565
  await adapter.writeTextFile(absolutePath2, operation.content);
7694
8566
  added.push(toDisplayPath(absolutePath2, cwd));
7695
8567
  continue;
@@ -7723,7 +8595,7 @@ async function applyPatch(request) {
7723
8595
  fromPath: absolutePath,
7724
8596
  toPath: destinationPath
7725
8597
  });
7726
- await adapter.ensureDir(import_node_path4.default.dirname(destinationPath));
8598
+ await adapter.ensureDir(import_node_path5.default.dirname(destinationPath));
7727
8599
  await adapter.writeTextFile(destinationPath, next);
7728
8600
  await adapter.deleteFile(absolutePath);
7729
8601
  modified.push(toDisplayPath(destinationPath, cwd));
@@ -7754,22 +8626,22 @@ function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
7754
8626
  if (trimmed.length === 0) {
7755
8627
  throw new Error("apply_patch failed: empty file path");
7756
8628
  }
7757
- const absolutePath = import_node_path4.default.isAbsolute(trimmed) ? import_node_path4.default.resolve(trimmed) : import_node_path4.default.resolve(cwd, trimmed);
8629
+ const absolutePath = import_node_path5.default.isAbsolute(trimmed) ? import_node_path5.default.resolve(trimmed) : import_node_path5.default.resolve(cwd, trimmed);
7758
8630
  if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
7759
8631
  throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
7760
8632
  }
7761
8633
  return absolutePath;
7762
8634
  }
7763
8635
  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);
8636
+ const relative = import_node_path5.default.relative(cwd, candidatePath);
8637
+ return relative === "" || !relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative);
7766
8638
  }
7767
8639
  function toDisplayPath(absolutePath, cwd) {
7768
- const relative = import_node_path4.default.relative(cwd, absolutePath);
8640
+ const relative = import_node_path5.default.relative(cwd, absolutePath);
7769
8641
  if (relative === "") {
7770
8642
  return ".";
7771
8643
  }
7772
- if (!relative.startsWith("..") && !import_node_path4.default.isAbsolute(relative)) {
8644
+ if (!relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative)) {
7773
8645
  return relative;
7774
8646
  }
7775
8647
  return absolutePath;
@@ -8536,7 +9408,7 @@ async function readBinaryFile(filesystem, filePath) {
8536
9408
  return await filesystem.readBinaryFile(filePath);
8537
9409
  }
8538
9410
  const text = await filesystem.readTextFile(filePath);
8539
- return import_node_buffer3.Buffer.from(text, "utf8");
9411
+ return import_node_buffer4.Buffer.from(text, "utf8");
8540
9412
  }
8541
9413
  function detectImageMimeType(buffer, filePath) {
8542
9414
  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 +9426,7 @@ function detectImageMimeType(buffer, filePath) {
8554
9426
  if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
8555
9427
  return "image/webp";
8556
9428
  }
8557
- const fromExtension = IMAGE_MIME_BY_EXTENSION[import_node_path5.default.extname(filePath).toLowerCase()];
9429
+ const fromExtension = IMAGE_MIME_BY_EXTENSION[import_node_path6.default.extname(filePath).toLowerCase()];
8558
9430
  if (fromExtension && SUPPORTED_IMAGE_MIME_TYPES.has(fromExtension)) {
8559
9431
  return fromExtension;
8560
9432
  }
@@ -8564,13 +9436,13 @@ function isPdfFile(buffer, filePath) {
8564
9436
  if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
8565
9437
  return true;
8566
9438
  }
8567
- return import_node_path5.default.extname(filePath).toLowerCase() === ".pdf";
9439
+ return import_node_path6.default.extname(filePath).toLowerCase() === ".pdf";
8568
9440
  }
8569
9441
  function isValidUtf8(buffer) {
8570
9442
  if (buffer.length === 0) {
8571
9443
  return true;
8572
9444
  }
8573
- return import_node_buffer3.Buffer.from(buffer.toString("utf8"), "utf8").equals(buffer);
9445
+ return import_node_buffer4.Buffer.from(buffer.toString("utf8"), "utf8").equals(buffer);
8574
9446
  }
8575
9447
  async function readFileGemini(input, options) {
8576
9448
  const runtime = resolveRuntime(options);
@@ -8602,7 +9474,7 @@ async function writeFileGemini(input, options) {
8602
9474
  action: "write",
8603
9475
  path: filePath
8604
9476
  });
8605
- await runtime.filesystem.ensureDir(import_node_path5.default.dirname(filePath));
9477
+ await runtime.filesystem.ensureDir(import_node_path6.default.dirname(filePath));
8606
9478
  await runtime.filesystem.writeTextFile(filePath, input.content);
8607
9479
  return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
8608
9480
  }
@@ -8623,7 +9495,7 @@ async function replaceFileContentGemini(input, options) {
8623
9495
  originalContent = await runtime.filesystem.readTextFile(filePath);
8624
9496
  } catch (error) {
8625
9497
  if (isNoEntError(error) && oldValue.length === 0) {
8626
- await runtime.filesystem.ensureDir(import_node_path5.default.dirname(filePath));
9498
+ await runtime.filesystem.ensureDir(import_node_path6.default.dirname(filePath));
8627
9499
  await runtime.filesystem.writeTextFile(filePath, newValue);
8628
9500
  return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
8629
9501
  }
@@ -8801,7 +9673,7 @@ async function globFilesGemini(input, options) {
8801
9673
  });
8802
9674
  const matched = [];
8803
9675
  for (const filePath of files) {
8804
- const relativePath = normalizeSlashes(import_node_path5.default.relative(dirPath, filePath));
9676
+ const relativePath = normalizeSlashes(import_node_path6.default.relative(dirPath, filePath));
8805
9677
  if (!matcher(relativePath)) {
8806
9678
  continue;
8807
9679
  }
@@ -8819,7 +9691,7 @@ async function globFilesGemini(input, options) {
8819
9691
  }
8820
9692
  function resolveRuntime(options) {
8821
9693
  return {
8822
- cwd: import_node_path5.default.resolve(options.cwd ?? process.cwd()),
9694
+ cwd: import_node_path6.default.resolve(options.cwd ?? process.cwd()),
8823
9695
  filesystem: options.fs ?? createNodeAgentFilesystem(),
8824
9696
  allowOutsideCwd: options.allowOutsideCwd === true,
8825
9697
  checkAccess: options.checkAccess,
@@ -8850,13 +9722,13 @@ function mapApplyPatchAction(action) {
8850
9722
  return "move";
8851
9723
  }
8852
9724
  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);
9725
+ const absolutePath = import_node_path6.default.isAbsolute(inputPath) ? import_node_path6.default.resolve(inputPath) : import_node_path6.default.resolve(cwd, inputPath);
8854
9726
  if (allowOutsideCwd || isPathInsideCwd2(absolutePath, cwd)) {
8855
9727
  return absolutePath;
8856
9728
  }
8857
- if (import_node_path5.default.isAbsolute(inputPath)) {
9729
+ if (import_node_path6.default.isAbsolute(inputPath)) {
8858
9730
  const sandboxRelativePath = inputPath.replace(/^[/\\]+/, "");
8859
- const sandboxRootedPath = import_node_path5.default.resolve(cwd, sandboxRelativePath);
9731
+ const sandboxRootedPath = import_node_path6.default.resolve(cwd, sandboxRelativePath);
8860
9732
  if (isPathInsideCwd2(sandboxRootedPath, cwd)) {
8861
9733
  return sandboxRootedPath;
8862
9734
  }
@@ -8864,25 +9736,25 @@ function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
8864
9736
  throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
8865
9737
  }
8866
9738
  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);
9739
+ const relative = import_node_path6.default.relative(cwd, candidatePath);
9740
+ return relative === "" || !relative.startsWith("..") && !import_node_path6.default.isAbsolute(relative);
8869
9741
  }
8870
9742
  function toDisplayPath2(absolutePath, cwd) {
8871
- const relative = import_node_path5.default.relative(cwd, absolutePath);
9743
+ const relative = import_node_path6.default.relative(cwd, absolutePath);
8872
9744
  if (relative === "") {
8873
9745
  return ".";
8874
9746
  }
8875
- if (!relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative)) {
9747
+ if (!relative.startsWith("..") && !import_node_path6.default.isAbsolute(relative)) {
8876
9748
  return relative;
8877
9749
  }
8878
9750
  return absolutePath;
8879
9751
  }
8880
9752
  function toSandboxDisplayPath(absolutePath, cwd) {
8881
- const relative = import_node_path5.default.relative(cwd, absolutePath);
9753
+ const relative = import_node_path6.default.relative(cwd, absolutePath);
8882
9754
  if (relative === "") {
8883
9755
  return "/";
8884
9756
  }
8885
- if (!relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative)) {
9757
+ if (!relative.startsWith("..") && !import_node_path6.default.isAbsolute(relative)) {
8886
9758
  return `/${normalizeSlashes(relative)}`;
8887
9759
  }
8888
9760
  return normalizeSlashes(absolutePath);
@@ -8998,7 +9870,7 @@ function createGlobMatcher(pattern, caseSensitive = false) {
8998
9870
  }));
8999
9871
  return (candidatePath) => {
9000
9872
  const normalizedPath = normalizeSlashes(candidatePath);
9001
- const basename = import_node_path5.default.posix.basename(normalizedPath);
9873
+ const basename = import_node_path6.default.posix.basename(normalizedPath);
9002
9874
  return compiled.some(
9003
9875
  (entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
9004
9876
  );
@@ -9114,10 +9986,18 @@ function isNoEntError(error) {
9114
9986
  // src/agent.ts
9115
9987
  async function runAgentLoop(request) {
9116
9988
  const telemetry = createAgentTelemetrySession(request.telemetry);
9989
+ const logging = createRootAgentLoggingSession(request);
9117
9990
  try {
9118
- return await runAgentLoopInternal(request, { depth: 0, telemetry });
9991
+ return await runWithAgentLoggingSession(logging, async () => {
9992
+ return await runAgentLoopInternal(request, {
9993
+ depth: 0,
9994
+ telemetry,
9995
+ logging
9996
+ });
9997
+ });
9119
9998
  } finally {
9120
9999
  await telemetry?.flush();
10000
+ await logging?.flush();
9121
10001
  }
9122
10002
  }
9123
10003
  function mergeAbortSignals2(first, second) {
@@ -9188,9 +10068,11 @@ async function runAgentLoopInternal(request, context) {
9188
10068
  subagent_tool,
9189
10069
  subagents,
9190
10070
  telemetry,
10071
+ logging: _logging,
9191
10072
  ...toolLoopRequest
9192
10073
  } = request;
9193
10074
  const telemetrySession = context.telemetry ?? createAgentTelemetrySession(telemetry);
10075
+ const loggingSession = context.logging;
9194
10076
  const runId = randomRunId();
9195
10077
  const startedAtMs = Date.now();
9196
10078
  const steeringChannel = toolLoopRequest.steering ?? createToolLoopSteeringChannel();
@@ -9204,6 +10086,7 @@ async function runAgentLoopInternal(request, context) {
9204
10086
  model: request.model,
9205
10087
  depth: context.depth,
9206
10088
  telemetry: telemetrySession,
10089
+ logging: loggingSession,
9207
10090
  customTools: customTools ?? {},
9208
10091
  filesystemSelection,
9209
10092
  subagentSelection,
@@ -9240,6 +10123,16 @@ async function runAgentLoopInternal(request, context) {
9240
10123
  filesystemToolsEnabled: Object.keys(filesystemTools).length > 0,
9241
10124
  subagentToolsEnabled: resolvedSubagentConfig.enabled
9242
10125
  });
10126
+ loggingSession?.logLine(
10127
+ [
10128
+ `[agent:${runId}] run_started`,
10129
+ `depth=${context.depth.toString()}`,
10130
+ `model=${request.model}`,
10131
+ `tools=${Object.keys(mergedTools).length.toString()}`,
10132
+ `filesystemTools=${Object.keys(filesystemTools).length > 0 ? "true" : "false"}`,
10133
+ `subagentTools=${resolvedSubagentConfig.enabled ? "true" : "false"}`
10134
+ ].join(" ")
10135
+ );
9243
10136
  const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
9244
10137
  const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
9245
10138
  const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
@@ -9247,6 +10140,14 @@ async function runAgentLoopInternal(request, context) {
9247
10140
  if (includeLlmStreamEvents) {
9248
10141
  emitTelemetry({ type: "agent.run.stream", event });
9249
10142
  }
10143
+ if (loggingSession) {
10144
+ appendAgentStreamEventLog({
10145
+ event,
10146
+ append: (line) => {
10147
+ loggingSession.logLine(`[agent:${runId}] ${line}`);
10148
+ }
10149
+ });
10150
+ }
9250
10151
  } : void 0;
9251
10152
  try {
9252
10153
  const result = await runToolLoop({
@@ -9264,14 +10165,43 @@ async function runAgentLoopInternal(request, context) {
9264
10165
  totalCostUsd: result.totalCostUsd,
9265
10166
  usage: summarizeResultUsage(result)
9266
10167
  });
10168
+ loggingSession?.logLine(
10169
+ [
10170
+ `[agent:${runId}] run_completed`,
10171
+ `status=ok`,
10172
+ `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
10173
+ `steps=${result.steps.length.toString()}`,
10174
+ `toolCalls=${countToolCalls(result).toString()}`,
10175
+ `totalCostUsd=${(result.totalCostUsd ?? 0).toFixed(6)}`
10176
+ ].join(" ")
10177
+ );
10178
+ for (const step of result.steps) {
10179
+ loggingSession?.logLine(
10180
+ [
10181
+ `[agent:${runId}] step_completed`,
10182
+ `step=${step.step.toString()}`,
10183
+ `modelVersion=${step.modelVersion}`,
10184
+ `toolCalls=${step.toolCalls.length.toString()}`,
10185
+ `costUsd=${(step.costUsd ?? 0).toFixed(6)}`
10186
+ ].join(" ")
10187
+ );
10188
+ }
9267
10189
  return result;
9268
10190
  } catch (error) {
9269
10191
  emitTelemetry({
9270
10192
  type: "agent.run.completed",
9271
10193
  success: false,
9272
10194
  durationMs: Math.max(0, Date.now() - startedAtMs),
9273
- error: toErrorMessage2(error)
10195
+ error: toErrorMessage3(error)
9274
10196
  });
10197
+ loggingSession?.logLine(
10198
+ [
10199
+ `[agent:${runId}] run_completed`,
10200
+ `status=error`,
10201
+ `durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
10202
+ `error=${toErrorMessage3(error)}`
10203
+ ].join(" ")
10204
+ );
9275
10205
  throw error;
9276
10206
  } finally {
9277
10207
  await subagentController?.closeAll();
@@ -9342,7 +10272,8 @@ function createSubagentController(params) {
9342
10272
  {
9343
10273
  depth: params.depth + 1,
9344
10274
  parentRunId: params.runId,
9345
- telemetry: params.telemetry
10275
+ telemetry: params.telemetry,
10276
+ logging: params.logging
9346
10277
  }
9347
10278
  );
9348
10279
  }
@@ -9406,10 +10337,10 @@ function trimToUndefined2(value) {
9406
10337
  function randomRunId() {
9407
10338
  return (0, import_node_crypto3.randomBytes)(8).toString("hex");
9408
10339
  }
9409
- function toIsoNow() {
10340
+ function toIsoNow2() {
9410
10341
  return (/* @__PURE__ */ new Date()).toISOString();
9411
10342
  }
9412
- function toErrorMessage2(error) {
10343
+ function toErrorMessage3(error) {
9413
10344
  if (error instanceof Error && error.message) {
9414
10345
  return error.message;
9415
10346
  }
@@ -9454,9 +10385,41 @@ function summarizeResultUsage(result) {
9454
10385
  }
9455
10386
  return summary;
9456
10387
  }
9457
- function isPromiseLike(value) {
10388
+ function isPromiseLike2(value) {
9458
10389
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
9459
10390
  }
10391
+ function resolveAgentLoggingSelection(value) {
10392
+ if (value === false) {
10393
+ return void 0;
10394
+ }
10395
+ if (value === void 0 || value === true) {
10396
+ return {
10397
+ mirrorToConsole: true
10398
+ };
10399
+ }
10400
+ return value;
10401
+ }
10402
+ function resolveWorkspaceDirForLogging(request) {
10403
+ const explicitSelection = request.filesystemTool ?? request.filesystem_tool;
10404
+ if (explicitSelection && typeof explicitSelection === "object" && !Array.isArray(explicitSelection)) {
10405
+ const cwd = explicitSelection.options?.cwd;
10406
+ if (typeof cwd === "string" && cwd.trim().length > 0) {
10407
+ return import_node_path7.default.resolve(cwd);
10408
+ }
10409
+ }
10410
+ return process.cwd();
10411
+ }
10412
+ function createRootAgentLoggingSession(request) {
10413
+ const selected = resolveAgentLoggingSelection(request.logging);
10414
+ if (!selected) {
10415
+ return void 0;
10416
+ }
10417
+ return createAgentLoggingSession({
10418
+ ...selected,
10419
+ workspaceDir: typeof selected.workspaceDir === "string" && selected.workspaceDir.trim().length > 0 ? import_node_path7.default.resolve(selected.workspaceDir) : resolveWorkspaceDirForLogging(request),
10420
+ mirrorToConsole: selected.mirrorToConsole !== false
10421
+ });
10422
+ }
9460
10423
  function isAgentTelemetrySink(value) {
9461
10424
  return typeof value === "object" && value !== null && typeof value.emit === "function";
9462
10425
  }
@@ -9487,7 +10450,7 @@ function createAgentTelemetrySession(telemetry) {
9487
10450
  const emit = (event) => {
9488
10451
  try {
9489
10452
  const output = config.sink.emit(event);
9490
- if (isPromiseLike(output)) {
10453
+ if (isPromiseLike2(output)) {
9491
10454
  const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
9492
10455
  trackPromise(task);
9493
10456
  }
@@ -9518,7 +10481,7 @@ function createAgentTelemetryEmitter(params) {
9518
10481
  }
9519
10482
  params.session.emit({
9520
10483
  ...event,
9521
- timestamp: toIsoNow(),
10484
+ timestamp: toIsoNow2(),
9522
10485
  runId: params.runId,
9523
10486
  ...params.parentRunId ? { parentRunId: params.parentRunId } : {},
9524
10487
  depth: params.depth,