@ljoukov/llm 4.0.5 → 4.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2682,6 +2682,7 @@ import { AsyncLocalStorage } from "async_hooks";
2682
2682
  import { Buffer as Buffer3 } from "buffer";
2683
2683
  import { appendFile, mkdir, writeFile } from "fs/promises";
2684
2684
  import path3 from "path";
2685
+ var DEFAULT_THOUGHT_DELTA_LOG_THROTTLE_MS = 4e3;
2685
2686
  function toIsoNow() {
2686
2687
  return (/* @__PURE__ */ new Date()).toISOString();
2687
2688
  }
@@ -2705,6 +2706,9 @@ function ensureTrailingNewline(value) {
2705
2706
  return value.endsWith("\n") ? value : `${value}
2706
2707
  `;
2707
2708
  }
2709
+ function hasNonEmptyText(value) {
2710
+ return typeof value === "string" && value.length > 0;
2711
+ }
2708
2712
  function redactDataUrlPayload(value) {
2709
2713
  if (!value.toLowerCase().startsWith("data:")) {
2710
2714
  return value;
@@ -2845,6 +2849,63 @@ function appendAgentStreamEventLog(options) {
2845
2849
  }
2846
2850
  }
2847
2851
  }
2852
+ function createAgentStreamEventLogger(options) {
2853
+ const thoughtDeltaThrottleMs = Math.max(
2854
+ 0,
2855
+ options.thoughtDeltaThrottleMs ?? DEFAULT_THOUGHT_DELTA_LOG_THROTTLE_MS
2856
+ );
2857
+ let pendingThoughtDelta = "";
2858
+ let thoughtFlushTimer = null;
2859
+ const cancelThoughtFlushTimer = () => {
2860
+ if (thoughtFlushTimer === null) {
2861
+ return;
2862
+ }
2863
+ clearTimeout(thoughtFlushTimer);
2864
+ thoughtFlushTimer = null;
2865
+ };
2866
+ const flushThoughtDelta = () => {
2867
+ cancelThoughtFlushTimer();
2868
+ if (pendingThoughtDelta.length === 0) {
2869
+ return;
2870
+ }
2871
+ options.append(`thought_delta: ${pendingThoughtDelta}`);
2872
+ pendingThoughtDelta = "";
2873
+ };
2874
+ const scheduleThoughtFlush = () => {
2875
+ if (thoughtFlushTimer !== null || thoughtDeltaThrottleMs === 0) {
2876
+ return;
2877
+ }
2878
+ thoughtFlushTimer = setTimeout(() => {
2879
+ thoughtFlushTimer = null;
2880
+ flushThoughtDelta();
2881
+ }, thoughtDeltaThrottleMs);
2882
+ thoughtFlushTimer.unref?.();
2883
+ };
2884
+ return {
2885
+ appendEvent: (event) => {
2886
+ if (event.type === "delta" && event.channel === "thought") {
2887
+ if (event.text.length === 0) {
2888
+ return;
2889
+ }
2890
+ pendingThoughtDelta += event.text;
2891
+ if (thoughtDeltaThrottleMs === 0) {
2892
+ flushThoughtDelta();
2893
+ return;
2894
+ }
2895
+ scheduleThoughtFlush();
2896
+ return;
2897
+ }
2898
+ flushThoughtDelta();
2899
+ appendAgentStreamEventLog({
2900
+ event,
2901
+ append: options.append
2902
+ });
2903
+ },
2904
+ flush: () => {
2905
+ flushThoughtDelta();
2906
+ }
2907
+ };
2908
+ }
2848
2909
  var AgentLoggingSessionImpl = class {
2849
2910
  workspaceDir;
2850
2911
  logsRootDir;
@@ -2896,6 +2957,25 @@ var AgentLoggingSessionImpl = class {
2896
2957
  }
2897
2958
  this.enqueueLineWrite(timestamped);
2898
2959
  }
2960
+ async writeAttachments(baseDir, attachments) {
2961
+ const usedNames = /* @__PURE__ */ new Set();
2962
+ for (const attachment of attachments ?? []) {
2963
+ let filename = normalisePathSegment(attachment.filename);
2964
+ if (!filename.includes(".")) {
2965
+ filename = `${filename}.bin`;
2966
+ }
2967
+ const ext = path3.extname(filename);
2968
+ const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
2969
+ let candidate = filename;
2970
+ let duplicateIndex = 2;
2971
+ while (usedNames.has(candidate)) {
2972
+ candidate = `${base}-${duplicateIndex.toString()}${ext}`;
2973
+ duplicateIndex += 1;
2974
+ }
2975
+ usedNames.add(candidate);
2976
+ await writeFile(path3.join(baseDir, candidate), attachment.bytes);
2977
+ }
2978
+ }
2899
2979
  startLlmCall(input) {
2900
2980
  const callNumber = this.callCounter + 1;
2901
2981
  this.callCounter = callNumber;
@@ -2908,6 +2988,9 @@ var AgentLoggingSessionImpl = class {
2908
2988
  );
2909
2989
  const responsePath = path3.join(baseDir, "response.txt");
2910
2990
  const thoughtsPath = path3.join(baseDir, "thoughts.txt");
2991
+ const toolCallPath = path3.join(baseDir, "tool_call.txt");
2992
+ const toolCallResponsePath = path3.join(baseDir, "tool_call_response.txt");
2993
+ const errorPath = path3.join(baseDir, "error.txt");
2911
2994
  const responseMetadataPath = path3.join(baseDir, "response.metadata.json");
2912
2995
  let chain = this.ensureReady.then(async () => {
2913
2996
  await mkdir(baseDir, { recursive: true });
@@ -2929,22 +3012,13 @@ var AgentLoggingSessionImpl = class {
2929
3012
  `,
2930
3013
  "utf8"
2931
3014
  );
2932
- const usedNames = /* @__PURE__ */ new Set();
2933
- for (const attachment of input.attachments ?? []) {
2934
- let filename = normalisePathSegment(attachment.filename);
2935
- if (!filename.includes(".")) {
2936
- filename = `${filename}.bin`;
2937
- }
2938
- const ext = path3.extname(filename);
2939
- const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
2940
- let candidate = filename;
2941
- let duplicateIndex = 2;
2942
- while (usedNames.has(candidate)) {
2943
- candidate = `${base}-${duplicateIndex.toString()}${ext}`;
2944
- duplicateIndex += 1;
2945
- }
2946
- usedNames.add(candidate);
2947
- await writeFile(path3.join(baseDir, candidate), attachment.bytes);
3015
+ await this.writeAttachments(baseDir, input.attachments);
3016
+ if (hasNonEmptyText(input.toolCallResponseText)) {
3017
+ await writeFile(
3018
+ toolCallResponsePath,
3019
+ ensureTrailingNewline(input.toolCallResponseText),
3020
+ "utf8"
3021
+ );
2948
3022
  }
2949
3023
  }).catch(() => void 0);
2950
3024
  this.track(chain);
@@ -2972,18 +3046,25 @@ var AgentLoggingSessionImpl = class {
2972
3046
  await appendFile(responsePath, text, "utf8");
2973
3047
  });
2974
3048
  },
2975
- complete: (metadata) => {
3049
+ complete: (options) => {
2976
3050
  if (closed) {
2977
3051
  return;
2978
3052
  }
2979
3053
  closed = true;
2980
3054
  enqueue(async () => {
3055
+ if (hasNonEmptyText(options?.responseText)) {
3056
+ await writeFile(responsePath, options.responseText, "utf8");
3057
+ }
3058
+ if (hasNonEmptyText(options?.toolCallText)) {
3059
+ await writeFile(toolCallPath, ensureTrailingNewline(options.toolCallText), "utf8");
3060
+ }
3061
+ await this.writeAttachments(baseDir, options?.attachments);
2981
3062
  const payload = {
2982
3063
  capturedAt: toIsoNow(),
2983
3064
  status: "completed"
2984
3065
  };
2985
- if (metadata) {
2986
- const sanitised = sanitiseLogValue(metadata);
3066
+ if (options?.metadata) {
3067
+ const sanitised = sanitiseLogValue(options.metadata);
2987
3068
  if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
2988
3069
  Object.assign(payload, sanitised);
2989
3070
  } else if (sanitised !== void 0) {
@@ -2994,19 +3075,27 @@ var AgentLoggingSessionImpl = class {
2994
3075
  `, "utf8");
2995
3076
  });
2996
3077
  },
2997
- fail: (error, metadata) => {
3078
+ fail: (error, options) => {
2998
3079
  if (closed) {
2999
3080
  return;
3000
3081
  }
3001
3082
  closed = true;
3002
3083
  enqueue(async () => {
3084
+ if (hasNonEmptyText(options?.responseText)) {
3085
+ await writeFile(responsePath, options.responseText, "utf8");
3086
+ }
3087
+ if (hasNonEmptyText(options?.toolCallText)) {
3088
+ await writeFile(toolCallPath, ensureTrailingNewline(options.toolCallText), "utf8");
3089
+ }
3090
+ await this.writeAttachments(baseDir, options?.attachments);
3091
+ await writeFile(errorPath, ensureTrailingNewline(toErrorMessage(error)), "utf8");
3003
3092
  const payload = {
3004
3093
  capturedAt: toIsoNow(),
3005
3094
  status: "failed",
3006
3095
  error: toErrorMessage(error)
3007
3096
  };
3008
- if (metadata) {
3009
- const sanitised = sanitiseLogValue(metadata);
3097
+ if (options?.metadata) {
3098
+ const sanitised = sanitiseLogValue(options.metadata);
3010
3099
  if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3011
3100
  Object.assign(payload, sanitised);
3012
3101
  } else if (sanitised !== void 0) {
@@ -4849,7 +4938,10 @@ function resolveAttachmentExtension(mimeType) {
4849
4938
  }
4850
4939
  }
4851
4940
  }
4852
- function decodeDataUrlAttachment(value, basename) {
4941
+ function buildLoggedAttachmentFilename(prefix, index, mimeType) {
4942
+ return `${prefix}-${index.toString()}.${resolveAttachmentExtension(mimeType)}`;
4943
+ }
4944
+ function decodeDataUrlAttachment(value, options) {
4853
4945
  const trimmed = value.trim();
4854
4946
  if (!trimmed.toLowerCase().startsWith("data:")) {
4855
4947
  return null;
@@ -4865,7 +4957,7 @@ function decodeDataUrlAttachment(value, basename) {
4865
4957
  try {
4866
4958
  const bytes = isBase64 ? Buffer4.from(payload, "base64") : Buffer4.from(decodeURIComponent(payload), "utf8");
4867
4959
  return {
4868
- filename: `${basename}.${resolveAttachmentExtension(mimeType)}`,
4960
+ filename: buildLoggedAttachmentFilename(options.prefix, options.index, mimeType),
4869
4961
  bytes
4870
4962
  };
4871
4963
  } catch {
@@ -4874,10 +4966,10 @@ function decodeDataUrlAttachment(value, basename) {
4874
4966
  }
4875
4967
  function collectPayloadAttachments(value, options) {
4876
4968
  if (typeof value === "string") {
4877
- const attachment = decodeDataUrlAttachment(
4878
- value,
4879
- `${options.prefix}-${options.counter.toString()}`
4880
- );
4969
+ const attachment = decodeDataUrlAttachment(value, {
4970
+ prefix: options.prefix,
4971
+ index: options.counter
4972
+ });
4881
4973
  if (attachment) {
4882
4974
  options.attachments.push(attachment);
4883
4975
  options.counter += 1;
@@ -4902,7 +4994,7 @@ function collectPayloadAttachments(value, options) {
4902
4994
  if (typeof record.data === "string" && mimeType) {
4903
4995
  try {
4904
4996
  options.attachments.push({
4905
- filename: `${options.prefix}-${options.counter.toString()}.${resolveAttachmentExtension(mimeType)}`,
4997
+ filename: buildLoggedAttachmentFilename(options.prefix, options.counter, mimeType),
4906
4998
  bytes: decodeInlineDataBuffer(record.data)
4907
4999
  });
4908
5000
  options.counter += 1;
@@ -4922,27 +5014,166 @@ function serialiseRequestPayloadForLogging(value) {
4922
5014
  `;
4923
5015
  }
4924
5016
  }
5017
+ function serialiseLogArtifactText(value) {
5018
+ if (value === null || value === void 0) {
5019
+ return void 0;
5020
+ }
5021
+ if (typeof value === "string") {
5022
+ if (value.length === 0) {
5023
+ return void 0;
5024
+ }
5025
+ return value.endsWith("\n") ? value : `${value}
5026
+ `;
5027
+ }
5028
+ if (Array.isArray(value) && value.length === 0) {
5029
+ return void 0;
5030
+ }
5031
+ if (isPlainRecord(value) && Object.keys(value).length === 0) {
5032
+ return void 0;
5033
+ }
5034
+ try {
5035
+ return `${JSON.stringify(sanitiseLogValue(value), null, 2)}
5036
+ `;
5037
+ } catch {
5038
+ return `${String(value)}
5039
+ `;
5040
+ }
5041
+ }
5042
+ function collectLoggedAttachmentsFromLlmParts(parts, prefix) {
5043
+ const attachments = [];
5044
+ let index = 1;
5045
+ for (const part of parts) {
5046
+ if (part.type !== "inlineData") {
5047
+ continue;
5048
+ }
5049
+ attachments.push({
5050
+ filename: buildLoggedAttachmentFilename(prefix, index, part.mimeType),
5051
+ bytes: decodeInlineDataBuffer(part.data)
5052
+ });
5053
+ index += 1;
5054
+ }
5055
+ return attachments;
5056
+ }
5057
+ function collectLoggedAttachmentsFromGeminiParts(parts, prefix) {
5058
+ return collectLoggedAttachmentsFromLlmParts(convertGooglePartsToLlmParts(parts), prefix);
5059
+ }
5060
+ function extractToolCallResponseTextFromOpenAiInput(input) {
5061
+ if (!Array.isArray(input)) {
5062
+ return void 0;
5063
+ }
5064
+ const responses = input.filter((item) => isPlainRecord(item)).flatMap((item) => {
5065
+ const type = typeof item.type === "string" ? item.type : "";
5066
+ if (type !== "function_call_output" && type !== "custom_tool_call_output") {
5067
+ return [];
5068
+ }
5069
+ return [
5070
+ {
5071
+ type,
5072
+ callId: typeof item.call_id === "string" ? item.call_id : void 0,
5073
+ output: "output" in item ? sanitiseLogValue(item.output) : void 0
5074
+ }
5075
+ ];
5076
+ });
5077
+ return serialiseLogArtifactText(responses);
5078
+ }
5079
+ function extractToolCallResponseTextFromFireworksMessages(messages) {
5080
+ if (!Array.isArray(messages)) {
5081
+ return void 0;
5082
+ }
5083
+ const responses = messages.filter((message) => isPlainRecord(message)).flatMap((message) => {
5084
+ if (message.role !== "tool") {
5085
+ return [];
5086
+ }
5087
+ return [
5088
+ {
5089
+ toolCallId: typeof message.tool_call_id === "string" ? message.tool_call_id : void 0,
5090
+ content: sanitiseLogValue(message.content)
5091
+ }
5092
+ ];
5093
+ });
5094
+ return serialiseLogArtifactText(responses);
5095
+ }
5096
+ function extractToolCallResponseTextFromGeminiContents(contents) {
5097
+ if (!Array.isArray(contents)) {
5098
+ return void 0;
5099
+ }
5100
+ const responses = [];
5101
+ for (const content of contents) {
5102
+ if (!content || typeof content !== "object") {
5103
+ continue;
5104
+ }
5105
+ const parts = content.parts;
5106
+ if (!Array.isArray(parts)) {
5107
+ continue;
5108
+ }
5109
+ for (const part of parts) {
5110
+ if (!part || typeof part !== "object") {
5111
+ continue;
5112
+ }
5113
+ const functionResponse = part.functionResponse;
5114
+ if (functionResponse) {
5115
+ responses.push(sanitiseLogValue(functionResponse));
5116
+ }
5117
+ }
5118
+ }
5119
+ return serialiseLogArtifactText(responses);
5120
+ }
5121
+ function serialiseOpenAiStyleToolCallsForLogging(calls) {
5122
+ return serialiseLogArtifactText(
5123
+ calls.map((call) => {
5124
+ if (call.kind === "custom") {
5125
+ return {
5126
+ kind: call.kind,
5127
+ name: call.name,
5128
+ callId: call.callId,
5129
+ itemId: call.itemId,
5130
+ input: call.input
5131
+ };
5132
+ }
5133
+ const { value, error } = parseOpenAiToolArguments(call.arguments);
5134
+ return {
5135
+ kind: call.kind,
5136
+ name: call.name,
5137
+ callId: call.callId,
5138
+ itemId: call.itemId,
5139
+ arguments: value,
5140
+ ...error ? { parseError: error, rawArguments: call.arguments } : {}
5141
+ };
5142
+ })
5143
+ );
5144
+ }
5145
+ function serialiseGeminiToolCallsForLogging(calls) {
5146
+ return serialiseLogArtifactText(
5147
+ calls.map((call) => ({
5148
+ name: call.name ?? "unknown",
5149
+ callId: typeof call.id === "string" ? call.id : void 0,
5150
+ arguments: sanitiseLogValue(call.args ?? {})
5151
+ }))
5152
+ );
5153
+ }
4925
5154
  function startLlmCallLoggerFromContents(options) {
4926
5155
  const session = getCurrentAgentLoggingSession();
4927
5156
  if (!session) {
4928
5157
  return void 0;
4929
5158
  }
4930
5159
  const attachments = [];
5160
+ let attachmentIndex = 1;
4931
5161
  const sections = [];
4932
5162
  for (const [messageIndex, message] of options.contents.entries()) {
4933
5163
  sections.push(`### message_${(messageIndex + 1).toString()} role=${message.role}`);
4934
- for (const [partIndex, part] of message.parts.entries()) {
5164
+ for (const part of message.parts) {
4935
5165
  if (part.type === "text") {
4936
5166
  const channel = part.thought === true ? "thought" : "response";
4937
5167
  sections.push(`[text:${channel}]`);
4938
5168
  sections.push(part.text);
4939
5169
  continue;
4940
5170
  }
4941
- const filename = `message-${(messageIndex + 1).toString()}-part-${(partIndex + 1).toString()}.${resolveAttachmentExtension(part.mimeType)}`;
5171
+ const filename = buildLoggedAttachmentFilename("input", attachmentIndex, part.mimeType);
4942
5172
  attachments.push({
4943
5173
  filename,
4944
5174
  bytes: decodeInlineDataBuffer(part.data)
4945
5175
  });
5176
+ attachmentIndex += 1;
4946
5177
  sections.push(
4947
5178
  `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
4948
5179
  );
@@ -4986,11 +5217,18 @@ function startLlmCallLoggerFromPayload(options) {
4986
5217
  }
4987
5218
  const attachments = [];
4988
5219
  collectPayloadAttachments(options.requestPayload, {
4989
- prefix: `step-${options.step.toString()}`,
5220
+ prefix: "input",
4990
5221
  attachments,
4991
5222
  seen: /* @__PURE__ */ new WeakSet(),
4992
5223
  counter: 1
4993
5224
  });
5225
+ const toolCallResponseText = options.provider === "openai" || options.provider === "chatgpt" ? extractToolCallResponseTextFromOpenAiInput(
5226
+ options.requestPayload.input
5227
+ ) : options.provider === "fireworks" ? extractToolCallResponseTextFromFireworksMessages(
5228
+ options.requestPayload.messages
5229
+ ) : extractToolCallResponseTextFromGeminiContents(
5230
+ options.requestPayload.contents
5231
+ );
4994
5232
  return session.startLlmCall({
4995
5233
  provider: options.provider,
4996
5234
  modelId: options.modelId,
@@ -4999,7 +5237,8 @@ function startLlmCallLoggerFromPayload(options) {
4999
5237
  step: options.step,
5000
5238
  ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
5001
5239
  },
5002
- attachments
5240
+ attachments,
5241
+ toolCallResponseText
5003
5242
  });
5004
5243
  }
5005
5244
  async function runTextCall(params) {
@@ -5304,6 +5543,7 @@ async function runTextCall(params) {
5304
5543
  const mergedParts = mergeConsecutiveTextParts(responseParts);
5305
5544
  const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
5306
5545
  const { text, thoughts } = extractTextByChannel(content);
5546
+ const outputAttachments = collectLoggedAttachmentsFromLlmParts(mergedParts, "output");
5307
5547
  const costUsd = estimateCallCostUsd({
5308
5548
  modelId: modelVersion,
5309
5549
  tokens: latestUsage,
@@ -5314,16 +5554,20 @@ async function runTextCall(params) {
5314
5554
  queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5315
5555
  }
5316
5556
  callLogger?.complete({
5317
- provider,
5318
- model: request.model,
5319
- modelVersion,
5320
- blocked,
5321
- costUsd,
5322
- usage: latestUsage,
5323
- grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5324
- responseChars: text.length,
5325
- thoughtChars: thoughts.length,
5326
- responseImages
5557
+ responseText: text,
5558
+ attachments: outputAttachments,
5559
+ metadata: {
5560
+ provider,
5561
+ model: request.model,
5562
+ modelVersion,
5563
+ blocked,
5564
+ costUsd,
5565
+ usage: latestUsage,
5566
+ grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5567
+ responseChars: text.length,
5568
+ thoughtChars: thoughts.length,
5569
+ responseImages
5570
+ }
5327
5571
  });
5328
5572
  return {
5329
5573
  provider,
@@ -5338,14 +5582,21 @@ async function runTextCall(params) {
5338
5582
  grounding
5339
5583
  };
5340
5584
  } catch (error) {
5585
+ const partialParts = mergeConsecutiveTextParts(responseParts);
5586
+ const partialContent = partialParts.length > 0 ? { role: responseRole ?? "assistant", parts: partialParts } : void 0;
5587
+ const { text: partialText } = extractTextByChannel(partialContent);
5341
5588
  callLogger?.fail(error, {
5342
- provider,
5343
- model: request.model,
5344
- modelVersion,
5345
- blocked,
5346
- usage: latestUsage,
5347
- partialResponseParts: responseParts.length,
5348
- responseImages
5589
+ responseText: partialText,
5590
+ attachments: collectLoggedAttachmentsFromLlmParts(partialParts, "output"),
5591
+ metadata: {
5592
+ provider,
5593
+ model: request.model,
5594
+ modelVersion,
5595
+ blocked,
5596
+ usage: latestUsage,
5597
+ partialResponseParts: responseParts.length,
5598
+ responseImages
5599
+ }
5349
5600
  });
5350
5601
  throw error;
5351
5602
  }
@@ -5812,6 +6063,9 @@ async function runToolLoop(request) {
5812
6063
  let usageTokens;
5813
6064
  let thoughtDeltaEmitted = false;
5814
6065
  let blocked = false;
6066
+ let responseText = "";
6067
+ let reasoningSummary = "";
6068
+ let stepToolCallText;
5815
6069
  const stepRequestPayload = {
5816
6070
  model: providerInfo.model,
5817
6071
  input,
@@ -5904,8 +6158,8 @@ async function runToolLoop(request) {
5904
6158
  throw new Error(message);
5905
6159
  }
5906
6160
  usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
5907
- const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
5908
- const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
6161
+ responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
6162
+ reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
5909
6163
  if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
5910
6164
  stepCallLogger?.appendThoughtDelta(reasoningSummary);
5911
6165
  emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
@@ -5921,6 +6175,23 @@ async function runToolLoop(request) {
5921
6175
  emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
5922
6176
  }
5923
6177
  const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
6178
+ stepToolCallText = serialiseOpenAiStyleToolCallsForLogging(
6179
+ responseToolCalls.map(
6180
+ (call) => call.kind === "custom" ? {
6181
+ kind: call.kind,
6182
+ name: call.name,
6183
+ input: call.input,
6184
+ callId: call.call_id,
6185
+ itemId: call.id
6186
+ } : {
6187
+ kind: call.kind,
6188
+ name: call.name,
6189
+ arguments: call.arguments,
6190
+ callId: call.call_id,
6191
+ itemId: call.id
6192
+ }
6193
+ )
6194
+ );
5924
6195
  const stepToolCalls = [];
5925
6196
  if (responseToolCalls.length === 0) {
5926
6197
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
@@ -5948,17 +6219,20 @@ async function runToolLoop(request) {
5948
6219
  timing: timing2
5949
6220
  });
5950
6221
  stepCallLogger?.complete({
5951
- provider: "openai",
5952
- model: request.model,
5953
- modelVersion,
5954
- step: turn,
5955
- usage: usageTokens,
5956
- costUsd: stepCostUsd,
5957
- blocked,
5958
- responseChars: responseText.length,
5959
- thoughtChars: reasoningSummary.length,
5960
- toolCalls: 0,
5961
- finalStep: steeringItems2.length === 0
6222
+ responseText,
6223
+ metadata: {
6224
+ provider: "openai",
6225
+ model: request.model,
6226
+ modelVersion,
6227
+ step: turn,
6228
+ usage: usageTokens,
6229
+ costUsd: stepCostUsd,
6230
+ blocked,
6231
+ responseChars: responseText.length,
6232
+ thoughtChars: reasoningSummary.length,
6233
+ toolCalls: 0,
6234
+ finalStep: steeringItems2.length === 0
6235
+ }
5962
6236
  });
5963
6237
  if (steeringItems2.length === 0) {
5964
6238
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6081,28 +6355,36 @@ async function runToolLoop(request) {
6081
6355
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6082
6356
  const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
6083
6357
  stepCallLogger?.complete({
6084
- provider: "openai",
6085
- model: request.model,
6086
- modelVersion,
6087
- step: turn,
6088
- usage: usageTokens,
6089
- costUsd: stepCostUsd,
6090
- blocked,
6091
- responseChars: responseText.length,
6092
- thoughtChars: reasoningSummary.length,
6093
- toolCalls: stepToolCalls.length,
6094
- finalStep: false
6358
+ responseText,
6359
+ toolCallText: stepToolCallText,
6360
+ metadata: {
6361
+ provider: "openai",
6362
+ model: request.model,
6363
+ modelVersion,
6364
+ step: turn,
6365
+ usage: usageTokens,
6366
+ costUsd: stepCostUsd,
6367
+ blocked,
6368
+ responseChars: responseText.length,
6369
+ thoughtChars: reasoningSummary.length,
6370
+ toolCalls: stepToolCalls.length,
6371
+ finalStep: false
6372
+ }
6095
6373
  });
6096
6374
  previousResponseId = finalResponse.id;
6097
6375
  input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
6098
6376
  } catch (error) {
6099
6377
  stepCallLogger?.fail(error, {
6100
- provider: "openai",
6101
- model: request.model,
6102
- modelVersion,
6103
- step: turn,
6104
- usage: usageTokens,
6105
- blocked
6378
+ responseText,
6379
+ toolCallText: stepToolCallText,
6380
+ metadata: {
6381
+ provider: "openai",
6382
+ model: request.model,
6383
+ modelVersion,
6384
+ step: turn,
6385
+ usage: usageTokens,
6386
+ blocked
6387
+ }
6106
6388
  });
6107
6389
  throw error;
6108
6390
  }
@@ -6128,6 +6410,7 @@ async function runToolLoop(request) {
6128
6410
  let usageTokens;
6129
6411
  let responseText = "";
6130
6412
  let reasoningSummaryText = "";
6413
+ let stepToolCallText;
6131
6414
  const markFirstModelEvent = () => {
6132
6415
  if (firstModelEventAtMs === void 0) {
6133
6416
  firstModelEventAtMs = Date.now();
@@ -6196,6 +6479,23 @@ async function runToolLoop(request) {
6196
6479
  stepCallLogger?.appendResponseDelta(responseText);
6197
6480
  }
6198
6481
  const responseToolCalls = response.toolCalls ?? [];
6482
+ stepToolCallText = serialiseOpenAiStyleToolCallsForLogging(
6483
+ responseToolCalls.map(
6484
+ (call) => call.kind === "custom" ? {
6485
+ kind: call.kind,
6486
+ name: call.name,
6487
+ input: call.input,
6488
+ callId: call.callId,
6489
+ itemId: call.id
6490
+ } : {
6491
+ kind: call.kind,
6492
+ name: call.name,
6493
+ arguments: call.arguments,
6494
+ callId: call.callId,
6495
+ itemId: call.id
6496
+ }
6497
+ )
6498
+ );
6199
6499
  if (responseToolCalls.length === 0) {
6200
6500
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6201
6501
  const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
@@ -6221,16 +6521,19 @@ async function runToolLoop(request) {
6221
6521
  timing: timing2
6222
6522
  });
6223
6523
  stepCallLogger?.complete({
6224
- provider: "chatgpt",
6225
- model: request.model,
6226
- modelVersion,
6227
- step: turn,
6228
- usage: usageTokens,
6229
- costUsd: stepCostUsd,
6230
- responseChars: responseText.length,
6231
- thoughtChars: reasoningSummaryText.length,
6232
- toolCalls: 0,
6233
- finalStep: steeringItems2.length === 0
6524
+ responseText,
6525
+ metadata: {
6526
+ provider: "chatgpt",
6527
+ model: request.model,
6528
+ modelVersion,
6529
+ step: turn,
6530
+ usage: usageTokens,
6531
+ costUsd: stepCostUsd,
6532
+ responseChars: responseText.length,
6533
+ thoughtChars: reasoningSummaryText.length,
6534
+ toolCalls: 0,
6535
+ finalStep: steeringItems2.length === 0
6536
+ }
6234
6537
  });
6235
6538
  if (steeringItems2.length === 0) {
6236
6539
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6363,25 +6666,33 @@ async function runToolLoop(request) {
6363
6666
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6364
6667
  const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
6365
6668
  stepCallLogger?.complete({
6366
- provider: "chatgpt",
6367
- model: request.model,
6368
- modelVersion,
6369
- step: turn,
6370
- usage: usageTokens,
6371
- costUsd: stepCostUsd,
6372
- responseChars: responseText.length,
6373
- thoughtChars: reasoningSummaryText.length,
6374
- toolCalls: toolCalls.length,
6375
- finalStep: false
6669
+ responseText,
6670
+ toolCallText: stepToolCallText,
6671
+ metadata: {
6672
+ provider: "chatgpt",
6673
+ model: request.model,
6674
+ modelVersion,
6675
+ step: turn,
6676
+ usage: usageTokens,
6677
+ costUsd: stepCostUsd,
6678
+ responseChars: responseText.length,
6679
+ thoughtChars: reasoningSummaryText.length,
6680
+ toolCalls: toolCalls.length,
6681
+ finalStep: false
6682
+ }
6376
6683
  });
6377
6684
  input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
6378
6685
  } catch (error) {
6379
6686
  stepCallLogger?.fail(error, {
6380
- provider: "chatgpt",
6381
- model: request.model,
6382
- modelVersion,
6383
- step: turn,
6384
- usage: usageTokens
6687
+ responseText,
6688
+ toolCallText: stepToolCallText,
6689
+ metadata: {
6690
+ provider: "chatgpt",
6691
+ model: request.model,
6692
+ modelVersion,
6693
+ step: turn,
6694
+ usage: usageTokens
6695
+ }
6385
6696
  });
6386
6697
  throw error;
6387
6698
  }
@@ -6404,6 +6715,7 @@ async function runToolLoop(request) {
6404
6715
  let usageTokens;
6405
6716
  let responseText = "";
6406
6717
  let blocked = false;
6718
+ let stepToolCallText;
6407
6719
  const stepRequestPayload = {
6408
6720
  model: providerInfo.model,
6409
6721
  messages,
@@ -6468,6 +6780,14 @@ async function runToolLoop(request) {
6468
6780
  });
6469
6781
  }
6470
6782
  const responseToolCalls = extractFireworksToolCalls(message);
6783
+ stepToolCallText = serialiseOpenAiStyleToolCallsForLogging(
6784
+ responseToolCalls.map((call) => ({
6785
+ kind: "function",
6786
+ name: call.name,
6787
+ arguments: call.arguments,
6788
+ callId: call.id
6789
+ }))
6790
+ );
6471
6791
  if (responseToolCalls.length === 0) {
6472
6792
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6473
6793
  const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
@@ -6493,17 +6813,20 @@ async function runToolLoop(request) {
6493
6813
  timing: timing2
6494
6814
  });
6495
6815
  stepCallLogger?.complete({
6496
- provider: "fireworks",
6497
- model: request.model,
6498
- modelVersion,
6499
- step: turn,
6500
- usage: usageTokens,
6501
- costUsd: stepCostUsd,
6502
- blocked,
6503
- responseChars: responseText.length,
6504
- thoughtChars: 0,
6505
- toolCalls: 0,
6506
- finalStep: steeringMessages.length === 0
6816
+ responseText,
6817
+ metadata: {
6818
+ provider: "fireworks",
6819
+ model: request.model,
6820
+ modelVersion,
6821
+ step: turn,
6822
+ usage: usageTokens,
6823
+ costUsd: stepCostUsd,
6824
+ blocked,
6825
+ responseChars: responseText.length,
6826
+ thoughtChars: 0,
6827
+ toolCalls: 0,
6828
+ finalStep: steeringMessages.length === 0
6829
+ }
6507
6830
  });
6508
6831
  if (steeringMessages.length === 0) {
6509
6832
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6615,17 +6938,21 @@ async function runToolLoop(request) {
6615
6938
  timing
6616
6939
  });
6617
6940
  stepCallLogger?.complete({
6618
- provider: "fireworks",
6619
- model: request.model,
6620
- modelVersion,
6621
- step: turn,
6622
- usage: usageTokens,
6623
- costUsd: stepCostUsd,
6624
- blocked,
6625
- responseChars: responseText.length,
6626
- thoughtChars: 0,
6627
- toolCalls: stepToolCalls.length,
6628
- finalStep: false
6941
+ responseText,
6942
+ toolCallText: stepToolCallText,
6943
+ metadata: {
6944
+ provider: "fireworks",
6945
+ model: request.model,
6946
+ modelVersion,
6947
+ step: turn,
6948
+ usage: usageTokens,
6949
+ costUsd: stepCostUsd,
6950
+ blocked,
6951
+ responseChars: responseText.length,
6952
+ thoughtChars: 0,
6953
+ toolCalls: stepToolCalls.length,
6954
+ finalStep: false
6955
+ }
6629
6956
  });
6630
6957
  messages.push({
6631
6958
  role: "assistant",
@@ -6639,12 +6966,16 @@ async function runToolLoop(request) {
6639
6966
  }
6640
6967
  } catch (error) {
6641
6968
  stepCallLogger?.fail(error, {
6642
- provider: "fireworks",
6643
- model: request.model,
6644
- modelVersion,
6645
- step: turn,
6646
- usage: usageTokens,
6647
- blocked
6969
+ responseText,
6970
+ toolCallText: stepToolCallText,
6971
+ metadata: {
6972
+ provider: "fireworks",
6973
+ model: request.model,
6974
+ modelVersion,
6975
+ step: turn,
6976
+ usage: usageTokens,
6977
+ blocked
6978
+ }
6648
6979
  });
6649
6980
  throw error;
6650
6981
  }
@@ -6664,6 +6995,7 @@ async function runToolLoop(request) {
6664
6995
  let usageTokens;
6665
6996
  let responseText = "";
6666
6997
  let thoughtsText = "";
6998
+ let stepToolCallText;
6667
6999
  const markFirstModelEvent = () => {
6668
7000
  if (firstModelEventAtMs === void 0) {
6669
7001
  firstModelEventAtMs = Date.now();
@@ -6784,12 +7116,17 @@ async function runToolLoop(request) {
6784
7116
  modelVersion = response.modelVersion ?? request.model;
6785
7117
  responseText = response.responseText.trim();
6786
7118
  thoughtsText = response.thoughtsText.trim();
7119
+ const responseOutputAttachments = collectLoggedAttachmentsFromGeminiParts(
7120
+ response.modelParts,
7121
+ "output"
7122
+ );
6787
7123
  const stepCostUsd = estimateCallCostUsd({
6788
7124
  modelId: modelVersion,
6789
7125
  tokens: usageTokens,
6790
7126
  responseImages: 0
6791
7127
  });
6792
7128
  totalCostUsd += stepCostUsd;
7129
+ stepToolCallText = serialiseGeminiToolCallsForLogging(response.functionCalls);
6793
7130
  if (response.functionCalls.length === 0) {
6794
7131
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6795
7132
  finalText = responseText;
@@ -6815,16 +7152,20 @@ async function runToolLoop(request) {
6815
7152
  timing: timing2
6816
7153
  });
6817
7154
  stepCallLogger?.complete({
6818
- provider: "gemini",
6819
- model: request.model,
6820
- modelVersion,
6821
- step: turn,
6822
- usage: usageTokens,
6823
- costUsd: stepCostUsd,
6824
- responseChars: responseText.length,
6825
- thoughtChars: thoughtsText.length,
6826
- toolCalls: 0,
6827
- finalStep: steeringInput2.length === 0
7155
+ responseText,
7156
+ attachments: responseOutputAttachments,
7157
+ metadata: {
7158
+ provider: "gemini",
7159
+ model: request.model,
7160
+ modelVersion,
7161
+ step: turn,
7162
+ usage: usageTokens,
7163
+ costUsd: stepCostUsd,
7164
+ responseChars: responseText.length,
7165
+ thoughtChars: thoughtsText.length,
7166
+ toolCalls: 0,
7167
+ finalStep: steeringInput2.length === 0
7168
+ }
6828
7169
  });
6829
7170
  if (steeringInput2.length === 0) {
6830
7171
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6951,16 +7292,21 @@ async function runToolLoop(request) {
6951
7292
  timing
6952
7293
  });
6953
7294
  stepCallLogger?.complete({
6954
- provider: "gemini",
6955
- model: request.model,
6956
- modelVersion,
6957
- step: turn,
6958
- usage: usageTokens,
6959
- costUsd: stepCostUsd,
6960
- responseChars: responseText.length,
6961
- thoughtChars: thoughtsText.length,
6962
- toolCalls: toolCalls.length,
6963
- finalStep: false
7295
+ responseText,
7296
+ attachments: responseOutputAttachments,
7297
+ toolCallText: stepToolCallText,
7298
+ metadata: {
7299
+ provider: "gemini",
7300
+ model: request.model,
7301
+ modelVersion,
7302
+ step: turn,
7303
+ usage: usageTokens,
7304
+ costUsd: stepCostUsd,
7305
+ responseChars: responseText.length,
7306
+ thoughtChars: thoughtsText.length,
7307
+ toolCalls: toolCalls.length,
7308
+ finalStep: false
7309
+ }
6964
7310
  });
6965
7311
  geminiContents.push({ role: "user", parts: responseParts });
6966
7312
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
@@ -6969,13 +7315,17 @@ async function runToolLoop(request) {
6969
7315
  }
6970
7316
  } catch (error) {
6971
7317
  stepCallLogger?.fail(error, {
6972
- provider: "gemini",
6973
- model: request.model,
6974
- modelVersion,
6975
- step: turn,
6976
- usage: usageTokens,
6977
- responseChars: responseText.length,
6978
- thoughtChars: thoughtsText.length
7318
+ responseText,
7319
+ toolCallText: stepToolCallText,
7320
+ metadata: {
7321
+ provider: "gemini",
7322
+ model: request.model,
7323
+ modelVersion,
7324
+ step: turn,
7325
+ usage: usageTokens,
7326
+ responseChars: responseText.length,
7327
+ thoughtChars: thoughtsText.length
7328
+ }
6979
7329
  });
6980
7330
  throw error;
6981
7331
  }
@@ -10024,19 +10374,17 @@ async function runAgentLoopInternal(request, context) {
10024
10374
  );
10025
10375
  const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
10026
10376
  const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
10377
+ const streamEventLogger = loggingSession ? createAgentStreamEventLogger({
10378
+ append: (line) => {
10379
+ loggingSession.logLine(`[agent:${runId}] ${line}`);
10380
+ }
10381
+ }) : void 0;
10027
10382
  const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
10028
10383
  sourceOnEvent?.(event);
10029
10384
  if (includeLlmStreamEvents) {
10030
10385
  emitTelemetry({ type: "agent.run.stream", event });
10031
10386
  }
10032
- if (loggingSession) {
10033
- appendAgentStreamEventLog({
10034
- event,
10035
- append: (line) => {
10036
- loggingSession.logLine(`[agent:${runId}] ${line}`);
10037
- }
10038
- });
10039
- }
10387
+ streamEventLogger?.appendEvent(event);
10040
10388
  } : void 0;
10041
10389
  try {
10042
10390
  const result = await runToolLoop({
@@ -10045,6 +10393,7 @@ async function runAgentLoopInternal(request, context) {
10045
10393
  ...wrappedOnEvent ? { onEvent: wrappedOnEvent } : {},
10046
10394
  tools: mergedTools
10047
10395
  });
10396
+ streamEventLogger?.flush();
10048
10397
  emitTelemetry({
10049
10398
  type: "agent.run.completed",
10050
10399
  success: true,
@@ -10077,6 +10426,7 @@ async function runAgentLoopInternal(request, context) {
10077
10426
  }
10078
10427
  return result;
10079
10428
  } catch (error) {
10429
+ streamEventLogger?.flush();
10080
10430
  emitTelemetry({
10081
10431
  type: "agent.run.completed",
10082
10432
  success: false,
@@ -10093,6 +10443,7 @@ async function runAgentLoopInternal(request, context) {
10093
10443
  );
10094
10444
  throw error;
10095
10445
  } finally {
10446
+ streamEventLogger?.flush();
10096
10447
  await subagentController?.closeAll();
10097
10448
  }
10098
10449
  }