@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.cjs CHANGED
@@ -2794,6 +2794,7 @@ var import_node_async_hooks = require("async_hooks");
2794
2794
  var import_node_buffer2 = require("buffer");
2795
2795
  var import_promises = require("fs/promises");
2796
2796
  var import_node_path3 = __toESM(require("path"), 1);
2797
+ var DEFAULT_THOUGHT_DELTA_LOG_THROTTLE_MS = 4e3;
2797
2798
  function toIsoNow() {
2798
2799
  return (/* @__PURE__ */ new Date()).toISOString();
2799
2800
  }
@@ -2817,6 +2818,9 @@ function ensureTrailingNewline(value) {
2817
2818
  return value.endsWith("\n") ? value : `${value}
2818
2819
  `;
2819
2820
  }
2821
+ function hasNonEmptyText(value) {
2822
+ return typeof value === "string" && value.length > 0;
2823
+ }
2820
2824
  function redactDataUrlPayload(value) {
2821
2825
  if (!value.toLowerCase().startsWith("data:")) {
2822
2826
  return value;
@@ -2957,6 +2961,63 @@ function appendAgentStreamEventLog(options) {
2957
2961
  }
2958
2962
  }
2959
2963
  }
2964
+ function createAgentStreamEventLogger(options) {
2965
+ const thoughtDeltaThrottleMs = Math.max(
2966
+ 0,
2967
+ options.thoughtDeltaThrottleMs ?? DEFAULT_THOUGHT_DELTA_LOG_THROTTLE_MS
2968
+ );
2969
+ let pendingThoughtDelta = "";
2970
+ let thoughtFlushTimer = null;
2971
+ const cancelThoughtFlushTimer = () => {
2972
+ if (thoughtFlushTimer === null) {
2973
+ return;
2974
+ }
2975
+ clearTimeout(thoughtFlushTimer);
2976
+ thoughtFlushTimer = null;
2977
+ };
2978
+ const flushThoughtDelta = () => {
2979
+ cancelThoughtFlushTimer();
2980
+ if (pendingThoughtDelta.length === 0) {
2981
+ return;
2982
+ }
2983
+ options.append(`thought_delta: ${pendingThoughtDelta}`);
2984
+ pendingThoughtDelta = "";
2985
+ };
2986
+ const scheduleThoughtFlush = () => {
2987
+ if (thoughtFlushTimer !== null || thoughtDeltaThrottleMs === 0) {
2988
+ return;
2989
+ }
2990
+ thoughtFlushTimer = setTimeout(() => {
2991
+ thoughtFlushTimer = null;
2992
+ flushThoughtDelta();
2993
+ }, thoughtDeltaThrottleMs);
2994
+ thoughtFlushTimer.unref?.();
2995
+ };
2996
+ return {
2997
+ appendEvent: (event) => {
2998
+ if (event.type === "delta" && event.channel === "thought") {
2999
+ if (event.text.length === 0) {
3000
+ return;
3001
+ }
3002
+ pendingThoughtDelta += event.text;
3003
+ if (thoughtDeltaThrottleMs === 0) {
3004
+ flushThoughtDelta();
3005
+ return;
3006
+ }
3007
+ scheduleThoughtFlush();
3008
+ return;
3009
+ }
3010
+ flushThoughtDelta();
3011
+ appendAgentStreamEventLog({
3012
+ event,
3013
+ append: options.append
3014
+ });
3015
+ },
3016
+ flush: () => {
3017
+ flushThoughtDelta();
3018
+ }
3019
+ };
3020
+ }
2960
3021
  var AgentLoggingSessionImpl = class {
2961
3022
  workspaceDir;
2962
3023
  logsRootDir;
@@ -3008,6 +3069,25 @@ var AgentLoggingSessionImpl = class {
3008
3069
  }
3009
3070
  this.enqueueLineWrite(timestamped);
3010
3071
  }
3072
+ async writeAttachments(baseDir, attachments) {
3073
+ const usedNames = /* @__PURE__ */ new Set();
3074
+ for (const attachment of attachments ?? []) {
3075
+ let filename = normalisePathSegment(attachment.filename);
3076
+ if (!filename.includes(".")) {
3077
+ filename = `${filename}.bin`;
3078
+ }
3079
+ const ext = import_node_path3.default.extname(filename);
3080
+ const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
3081
+ let candidate = filename;
3082
+ let duplicateIndex = 2;
3083
+ while (usedNames.has(candidate)) {
3084
+ candidate = `${base}-${duplicateIndex.toString()}${ext}`;
3085
+ duplicateIndex += 1;
3086
+ }
3087
+ usedNames.add(candidate);
3088
+ await (0, import_promises.writeFile)(import_node_path3.default.join(baseDir, candidate), attachment.bytes);
3089
+ }
3090
+ }
3011
3091
  startLlmCall(input) {
3012
3092
  const callNumber = this.callCounter + 1;
3013
3093
  this.callCounter = callNumber;
@@ -3020,6 +3100,9 @@ var AgentLoggingSessionImpl = class {
3020
3100
  );
3021
3101
  const responsePath = import_node_path3.default.join(baseDir, "response.txt");
3022
3102
  const thoughtsPath = import_node_path3.default.join(baseDir, "thoughts.txt");
3103
+ const toolCallPath = import_node_path3.default.join(baseDir, "tool_call.txt");
3104
+ const toolCallResponsePath = import_node_path3.default.join(baseDir, "tool_call_response.txt");
3105
+ const errorPath = import_node_path3.default.join(baseDir, "error.txt");
3023
3106
  const responseMetadataPath = import_node_path3.default.join(baseDir, "response.metadata.json");
3024
3107
  let chain = this.ensureReady.then(async () => {
3025
3108
  await (0, import_promises.mkdir)(baseDir, { recursive: true });
@@ -3041,22 +3124,13 @@ var AgentLoggingSessionImpl = class {
3041
3124
  `,
3042
3125
  "utf8"
3043
3126
  );
3044
- const usedNames = /* @__PURE__ */ new Set();
3045
- for (const attachment of input.attachments ?? []) {
3046
- let filename = normalisePathSegment(attachment.filename);
3047
- if (!filename.includes(".")) {
3048
- filename = `${filename}.bin`;
3049
- }
3050
- const ext = import_node_path3.default.extname(filename);
3051
- const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
3052
- let candidate = filename;
3053
- let duplicateIndex = 2;
3054
- while (usedNames.has(candidate)) {
3055
- candidate = `${base}-${duplicateIndex.toString()}${ext}`;
3056
- duplicateIndex += 1;
3057
- }
3058
- usedNames.add(candidate);
3059
- await (0, import_promises.writeFile)(import_node_path3.default.join(baseDir, candidate), attachment.bytes);
3127
+ await this.writeAttachments(baseDir, input.attachments);
3128
+ if (hasNonEmptyText(input.toolCallResponseText)) {
3129
+ await (0, import_promises.writeFile)(
3130
+ toolCallResponsePath,
3131
+ ensureTrailingNewline(input.toolCallResponseText),
3132
+ "utf8"
3133
+ );
3060
3134
  }
3061
3135
  }).catch(() => void 0);
3062
3136
  this.track(chain);
@@ -3084,18 +3158,25 @@ var AgentLoggingSessionImpl = class {
3084
3158
  await (0, import_promises.appendFile)(responsePath, text, "utf8");
3085
3159
  });
3086
3160
  },
3087
- complete: (metadata) => {
3161
+ complete: (options) => {
3088
3162
  if (closed) {
3089
3163
  return;
3090
3164
  }
3091
3165
  closed = true;
3092
3166
  enqueue(async () => {
3167
+ if (hasNonEmptyText(options?.responseText)) {
3168
+ await (0, import_promises.writeFile)(responsePath, options.responseText, "utf8");
3169
+ }
3170
+ if (hasNonEmptyText(options?.toolCallText)) {
3171
+ await (0, import_promises.writeFile)(toolCallPath, ensureTrailingNewline(options.toolCallText), "utf8");
3172
+ }
3173
+ await this.writeAttachments(baseDir, options?.attachments);
3093
3174
  const payload = {
3094
3175
  capturedAt: toIsoNow(),
3095
3176
  status: "completed"
3096
3177
  };
3097
- if (metadata) {
3098
- const sanitised = sanitiseLogValue(metadata);
3178
+ if (options?.metadata) {
3179
+ const sanitised = sanitiseLogValue(options.metadata);
3099
3180
  if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3100
3181
  Object.assign(payload, sanitised);
3101
3182
  } else if (sanitised !== void 0) {
@@ -3106,19 +3187,27 @@ var AgentLoggingSessionImpl = class {
3106
3187
  `, "utf8");
3107
3188
  });
3108
3189
  },
3109
- fail: (error, metadata) => {
3190
+ fail: (error, options) => {
3110
3191
  if (closed) {
3111
3192
  return;
3112
3193
  }
3113
3194
  closed = true;
3114
3195
  enqueue(async () => {
3196
+ if (hasNonEmptyText(options?.responseText)) {
3197
+ await (0, import_promises.writeFile)(responsePath, options.responseText, "utf8");
3198
+ }
3199
+ if (hasNonEmptyText(options?.toolCallText)) {
3200
+ await (0, import_promises.writeFile)(toolCallPath, ensureTrailingNewline(options.toolCallText), "utf8");
3201
+ }
3202
+ await this.writeAttachments(baseDir, options?.attachments);
3203
+ await (0, import_promises.writeFile)(errorPath, ensureTrailingNewline(toErrorMessage(error)), "utf8");
3115
3204
  const payload = {
3116
3205
  capturedAt: toIsoNow(),
3117
3206
  status: "failed",
3118
3207
  error: toErrorMessage(error)
3119
3208
  };
3120
- if (metadata) {
3121
- const sanitised = sanitiseLogValue(metadata);
3209
+ if (options?.metadata) {
3210
+ const sanitised = sanitiseLogValue(options.metadata);
3122
3211
  if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3123
3212
  Object.assign(payload, sanitised);
3124
3213
  } else if (sanitised !== void 0) {
@@ -4961,7 +5050,10 @@ function resolveAttachmentExtension(mimeType) {
4961
5050
  }
4962
5051
  }
4963
5052
  }
4964
- function decodeDataUrlAttachment(value, basename) {
5053
+ function buildLoggedAttachmentFilename(prefix, index, mimeType) {
5054
+ return `${prefix}-${index.toString()}.${resolveAttachmentExtension(mimeType)}`;
5055
+ }
5056
+ function decodeDataUrlAttachment(value, options) {
4965
5057
  const trimmed = value.trim();
4966
5058
  if (!trimmed.toLowerCase().startsWith("data:")) {
4967
5059
  return null;
@@ -4977,7 +5069,7 @@ function decodeDataUrlAttachment(value, basename) {
4977
5069
  try {
4978
5070
  const bytes = isBase64 ? import_node_buffer3.Buffer.from(payload, "base64") : import_node_buffer3.Buffer.from(decodeURIComponent(payload), "utf8");
4979
5071
  return {
4980
- filename: `${basename}.${resolveAttachmentExtension(mimeType)}`,
5072
+ filename: buildLoggedAttachmentFilename(options.prefix, options.index, mimeType),
4981
5073
  bytes
4982
5074
  };
4983
5075
  } catch {
@@ -4986,10 +5078,10 @@ function decodeDataUrlAttachment(value, basename) {
4986
5078
  }
4987
5079
  function collectPayloadAttachments(value, options) {
4988
5080
  if (typeof value === "string") {
4989
- const attachment = decodeDataUrlAttachment(
4990
- value,
4991
- `${options.prefix}-${options.counter.toString()}`
4992
- );
5081
+ const attachment = decodeDataUrlAttachment(value, {
5082
+ prefix: options.prefix,
5083
+ index: options.counter
5084
+ });
4993
5085
  if (attachment) {
4994
5086
  options.attachments.push(attachment);
4995
5087
  options.counter += 1;
@@ -5014,7 +5106,7 @@ function collectPayloadAttachments(value, options) {
5014
5106
  if (typeof record.data === "string" && mimeType) {
5015
5107
  try {
5016
5108
  options.attachments.push({
5017
- filename: `${options.prefix}-${options.counter.toString()}.${resolveAttachmentExtension(mimeType)}`,
5109
+ filename: buildLoggedAttachmentFilename(options.prefix, options.counter, mimeType),
5018
5110
  bytes: decodeInlineDataBuffer(record.data)
5019
5111
  });
5020
5112
  options.counter += 1;
@@ -5034,27 +5126,166 @@ function serialiseRequestPayloadForLogging(value) {
5034
5126
  `;
5035
5127
  }
5036
5128
  }
5129
+ function serialiseLogArtifactText(value) {
5130
+ if (value === null || value === void 0) {
5131
+ return void 0;
5132
+ }
5133
+ if (typeof value === "string") {
5134
+ if (value.length === 0) {
5135
+ return void 0;
5136
+ }
5137
+ return value.endsWith("\n") ? value : `${value}
5138
+ `;
5139
+ }
5140
+ if (Array.isArray(value) && value.length === 0) {
5141
+ return void 0;
5142
+ }
5143
+ if (isPlainRecord(value) && Object.keys(value).length === 0) {
5144
+ return void 0;
5145
+ }
5146
+ try {
5147
+ return `${JSON.stringify(sanitiseLogValue(value), null, 2)}
5148
+ `;
5149
+ } catch {
5150
+ return `${String(value)}
5151
+ `;
5152
+ }
5153
+ }
5154
+ function collectLoggedAttachmentsFromLlmParts(parts, prefix) {
5155
+ const attachments = [];
5156
+ let index = 1;
5157
+ for (const part of parts) {
5158
+ if (part.type !== "inlineData") {
5159
+ continue;
5160
+ }
5161
+ attachments.push({
5162
+ filename: buildLoggedAttachmentFilename(prefix, index, part.mimeType),
5163
+ bytes: decodeInlineDataBuffer(part.data)
5164
+ });
5165
+ index += 1;
5166
+ }
5167
+ return attachments;
5168
+ }
5169
+ function collectLoggedAttachmentsFromGeminiParts(parts, prefix) {
5170
+ return collectLoggedAttachmentsFromLlmParts(convertGooglePartsToLlmParts(parts), prefix);
5171
+ }
5172
+ function extractToolCallResponseTextFromOpenAiInput(input) {
5173
+ if (!Array.isArray(input)) {
5174
+ return void 0;
5175
+ }
5176
+ const responses = input.filter((item) => isPlainRecord(item)).flatMap((item) => {
5177
+ const type = typeof item.type === "string" ? item.type : "";
5178
+ if (type !== "function_call_output" && type !== "custom_tool_call_output") {
5179
+ return [];
5180
+ }
5181
+ return [
5182
+ {
5183
+ type,
5184
+ callId: typeof item.call_id === "string" ? item.call_id : void 0,
5185
+ output: "output" in item ? sanitiseLogValue(item.output) : void 0
5186
+ }
5187
+ ];
5188
+ });
5189
+ return serialiseLogArtifactText(responses);
5190
+ }
5191
+ function extractToolCallResponseTextFromFireworksMessages(messages) {
5192
+ if (!Array.isArray(messages)) {
5193
+ return void 0;
5194
+ }
5195
+ const responses = messages.filter((message) => isPlainRecord(message)).flatMap((message) => {
5196
+ if (message.role !== "tool") {
5197
+ return [];
5198
+ }
5199
+ return [
5200
+ {
5201
+ toolCallId: typeof message.tool_call_id === "string" ? message.tool_call_id : void 0,
5202
+ content: sanitiseLogValue(message.content)
5203
+ }
5204
+ ];
5205
+ });
5206
+ return serialiseLogArtifactText(responses);
5207
+ }
5208
+ function extractToolCallResponseTextFromGeminiContents(contents) {
5209
+ if (!Array.isArray(contents)) {
5210
+ return void 0;
5211
+ }
5212
+ const responses = [];
5213
+ for (const content of contents) {
5214
+ if (!content || typeof content !== "object") {
5215
+ continue;
5216
+ }
5217
+ const parts = content.parts;
5218
+ if (!Array.isArray(parts)) {
5219
+ continue;
5220
+ }
5221
+ for (const part of parts) {
5222
+ if (!part || typeof part !== "object") {
5223
+ continue;
5224
+ }
5225
+ const functionResponse = part.functionResponse;
5226
+ if (functionResponse) {
5227
+ responses.push(sanitiseLogValue(functionResponse));
5228
+ }
5229
+ }
5230
+ }
5231
+ return serialiseLogArtifactText(responses);
5232
+ }
5233
+ function serialiseOpenAiStyleToolCallsForLogging(calls) {
5234
+ return serialiseLogArtifactText(
5235
+ calls.map((call) => {
5236
+ if (call.kind === "custom") {
5237
+ return {
5238
+ kind: call.kind,
5239
+ name: call.name,
5240
+ callId: call.callId,
5241
+ itemId: call.itemId,
5242
+ input: call.input
5243
+ };
5244
+ }
5245
+ const { value, error } = parseOpenAiToolArguments(call.arguments);
5246
+ return {
5247
+ kind: call.kind,
5248
+ name: call.name,
5249
+ callId: call.callId,
5250
+ itemId: call.itemId,
5251
+ arguments: value,
5252
+ ...error ? { parseError: error, rawArguments: call.arguments } : {}
5253
+ };
5254
+ })
5255
+ );
5256
+ }
5257
+ function serialiseGeminiToolCallsForLogging(calls) {
5258
+ return serialiseLogArtifactText(
5259
+ calls.map((call) => ({
5260
+ name: call.name ?? "unknown",
5261
+ callId: typeof call.id === "string" ? call.id : void 0,
5262
+ arguments: sanitiseLogValue(call.args ?? {})
5263
+ }))
5264
+ );
5265
+ }
5037
5266
  function startLlmCallLoggerFromContents(options) {
5038
5267
  const session = getCurrentAgentLoggingSession();
5039
5268
  if (!session) {
5040
5269
  return void 0;
5041
5270
  }
5042
5271
  const attachments = [];
5272
+ let attachmentIndex = 1;
5043
5273
  const sections = [];
5044
5274
  for (const [messageIndex, message] of options.contents.entries()) {
5045
5275
  sections.push(`### message_${(messageIndex + 1).toString()} role=${message.role}`);
5046
- for (const [partIndex, part] of message.parts.entries()) {
5276
+ for (const part of message.parts) {
5047
5277
  if (part.type === "text") {
5048
5278
  const channel = part.thought === true ? "thought" : "response";
5049
5279
  sections.push(`[text:${channel}]`);
5050
5280
  sections.push(part.text);
5051
5281
  continue;
5052
5282
  }
5053
- const filename = `message-${(messageIndex + 1).toString()}-part-${(partIndex + 1).toString()}.${resolveAttachmentExtension(part.mimeType)}`;
5283
+ const filename = buildLoggedAttachmentFilename("input", attachmentIndex, part.mimeType);
5054
5284
  attachments.push({
5055
5285
  filename,
5056
5286
  bytes: decodeInlineDataBuffer(part.data)
5057
5287
  });
5288
+ attachmentIndex += 1;
5058
5289
  sections.push(
5059
5290
  `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
5060
5291
  );
@@ -5098,11 +5329,18 @@ function startLlmCallLoggerFromPayload(options) {
5098
5329
  }
5099
5330
  const attachments = [];
5100
5331
  collectPayloadAttachments(options.requestPayload, {
5101
- prefix: `step-${options.step.toString()}`,
5332
+ prefix: "input",
5102
5333
  attachments,
5103
5334
  seen: /* @__PURE__ */ new WeakSet(),
5104
5335
  counter: 1
5105
5336
  });
5337
+ const toolCallResponseText = options.provider === "openai" || options.provider === "chatgpt" ? extractToolCallResponseTextFromOpenAiInput(
5338
+ options.requestPayload.input
5339
+ ) : options.provider === "fireworks" ? extractToolCallResponseTextFromFireworksMessages(
5340
+ options.requestPayload.messages
5341
+ ) : extractToolCallResponseTextFromGeminiContents(
5342
+ options.requestPayload.contents
5343
+ );
5106
5344
  return session.startLlmCall({
5107
5345
  provider: options.provider,
5108
5346
  modelId: options.modelId,
@@ -5111,7 +5349,8 @@ function startLlmCallLoggerFromPayload(options) {
5111
5349
  step: options.step,
5112
5350
  ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
5113
5351
  },
5114
- attachments
5352
+ attachments,
5353
+ toolCallResponseText
5115
5354
  });
5116
5355
  }
5117
5356
  async function runTextCall(params) {
@@ -5416,6 +5655,7 @@ async function runTextCall(params) {
5416
5655
  const mergedParts = mergeConsecutiveTextParts(responseParts);
5417
5656
  const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
5418
5657
  const { text, thoughts } = extractTextByChannel(content);
5658
+ const outputAttachments = collectLoggedAttachmentsFromLlmParts(mergedParts, "output");
5419
5659
  const costUsd = estimateCallCostUsd({
5420
5660
  modelId: modelVersion,
5421
5661
  tokens: latestUsage,
@@ -5426,16 +5666,20 @@ async function runTextCall(params) {
5426
5666
  queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5427
5667
  }
5428
5668
  callLogger?.complete({
5429
- provider,
5430
- model: request.model,
5431
- modelVersion,
5432
- blocked,
5433
- costUsd,
5434
- usage: latestUsage,
5435
- grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5436
- responseChars: text.length,
5437
- thoughtChars: thoughts.length,
5438
- responseImages
5669
+ responseText: text,
5670
+ attachments: outputAttachments,
5671
+ metadata: {
5672
+ provider,
5673
+ model: request.model,
5674
+ modelVersion,
5675
+ blocked,
5676
+ costUsd,
5677
+ usage: latestUsage,
5678
+ grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5679
+ responseChars: text.length,
5680
+ thoughtChars: thoughts.length,
5681
+ responseImages
5682
+ }
5439
5683
  });
5440
5684
  return {
5441
5685
  provider,
@@ -5450,14 +5694,21 @@ async function runTextCall(params) {
5450
5694
  grounding
5451
5695
  };
5452
5696
  } catch (error) {
5697
+ const partialParts = mergeConsecutiveTextParts(responseParts);
5698
+ const partialContent = partialParts.length > 0 ? { role: responseRole ?? "assistant", parts: partialParts } : void 0;
5699
+ const { text: partialText } = extractTextByChannel(partialContent);
5453
5700
  callLogger?.fail(error, {
5454
- provider,
5455
- model: request.model,
5456
- modelVersion,
5457
- blocked,
5458
- usage: latestUsage,
5459
- partialResponseParts: responseParts.length,
5460
- responseImages
5701
+ responseText: partialText,
5702
+ attachments: collectLoggedAttachmentsFromLlmParts(partialParts, "output"),
5703
+ metadata: {
5704
+ provider,
5705
+ model: request.model,
5706
+ modelVersion,
5707
+ blocked,
5708
+ usage: latestUsage,
5709
+ partialResponseParts: responseParts.length,
5710
+ responseImages
5711
+ }
5461
5712
  });
5462
5713
  throw error;
5463
5714
  }
@@ -5924,6 +6175,9 @@ async function runToolLoop(request) {
5924
6175
  let usageTokens;
5925
6176
  let thoughtDeltaEmitted = false;
5926
6177
  let blocked = false;
6178
+ let responseText = "";
6179
+ let reasoningSummary = "";
6180
+ let stepToolCallText;
5927
6181
  const stepRequestPayload = {
5928
6182
  model: providerInfo.model,
5929
6183
  input,
@@ -6016,8 +6270,8 @@ async function runToolLoop(request) {
6016
6270
  throw new Error(message);
6017
6271
  }
6018
6272
  usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
6019
- const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
6020
- const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
6273
+ responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
6274
+ reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
6021
6275
  if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
6022
6276
  stepCallLogger?.appendThoughtDelta(reasoningSummary);
6023
6277
  emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
@@ -6033,6 +6287,23 @@ async function runToolLoop(request) {
6033
6287
  emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
6034
6288
  }
6035
6289
  const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
6290
+ stepToolCallText = serialiseOpenAiStyleToolCallsForLogging(
6291
+ responseToolCalls.map(
6292
+ (call) => call.kind === "custom" ? {
6293
+ kind: call.kind,
6294
+ name: call.name,
6295
+ input: call.input,
6296
+ callId: call.call_id,
6297
+ itemId: call.id
6298
+ } : {
6299
+ kind: call.kind,
6300
+ name: call.name,
6301
+ arguments: call.arguments,
6302
+ callId: call.call_id,
6303
+ itemId: call.id
6304
+ }
6305
+ )
6306
+ );
6036
6307
  const stepToolCalls = [];
6037
6308
  if (responseToolCalls.length === 0) {
6038
6309
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
@@ -6060,17 +6331,20 @@ async function runToolLoop(request) {
6060
6331
  timing: timing2
6061
6332
  });
6062
6333
  stepCallLogger?.complete({
6063
- provider: "openai",
6064
- model: request.model,
6065
- modelVersion,
6066
- step: turn,
6067
- usage: usageTokens,
6068
- costUsd: stepCostUsd,
6069
- blocked,
6070
- responseChars: responseText.length,
6071
- thoughtChars: reasoningSummary.length,
6072
- toolCalls: 0,
6073
- finalStep: steeringItems2.length === 0
6334
+ responseText,
6335
+ metadata: {
6336
+ provider: "openai",
6337
+ model: request.model,
6338
+ modelVersion,
6339
+ step: turn,
6340
+ usage: usageTokens,
6341
+ costUsd: stepCostUsd,
6342
+ blocked,
6343
+ responseChars: responseText.length,
6344
+ thoughtChars: reasoningSummary.length,
6345
+ toolCalls: 0,
6346
+ finalStep: steeringItems2.length === 0
6347
+ }
6074
6348
  });
6075
6349
  if (steeringItems2.length === 0) {
6076
6350
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6193,28 +6467,36 @@ async function runToolLoop(request) {
6193
6467
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6194
6468
  const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
6195
6469
  stepCallLogger?.complete({
6196
- provider: "openai",
6197
- model: request.model,
6198
- modelVersion,
6199
- step: turn,
6200
- usage: usageTokens,
6201
- costUsd: stepCostUsd,
6202
- blocked,
6203
- responseChars: responseText.length,
6204
- thoughtChars: reasoningSummary.length,
6205
- toolCalls: stepToolCalls.length,
6206
- finalStep: false
6470
+ responseText,
6471
+ toolCallText: stepToolCallText,
6472
+ metadata: {
6473
+ provider: "openai",
6474
+ model: request.model,
6475
+ modelVersion,
6476
+ step: turn,
6477
+ usage: usageTokens,
6478
+ costUsd: stepCostUsd,
6479
+ blocked,
6480
+ responseChars: responseText.length,
6481
+ thoughtChars: reasoningSummary.length,
6482
+ toolCalls: stepToolCalls.length,
6483
+ finalStep: false
6484
+ }
6207
6485
  });
6208
6486
  previousResponseId = finalResponse.id;
6209
6487
  input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
6210
6488
  } catch (error) {
6211
6489
  stepCallLogger?.fail(error, {
6212
- provider: "openai",
6213
- model: request.model,
6214
- modelVersion,
6215
- step: turn,
6216
- usage: usageTokens,
6217
- blocked
6490
+ responseText,
6491
+ toolCallText: stepToolCallText,
6492
+ metadata: {
6493
+ provider: "openai",
6494
+ model: request.model,
6495
+ modelVersion,
6496
+ step: turn,
6497
+ usage: usageTokens,
6498
+ blocked
6499
+ }
6218
6500
  });
6219
6501
  throw error;
6220
6502
  }
@@ -6240,6 +6522,7 @@ async function runToolLoop(request) {
6240
6522
  let usageTokens;
6241
6523
  let responseText = "";
6242
6524
  let reasoningSummaryText = "";
6525
+ let stepToolCallText;
6243
6526
  const markFirstModelEvent = () => {
6244
6527
  if (firstModelEventAtMs === void 0) {
6245
6528
  firstModelEventAtMs = Date.now();
@@ -6308,6 +6591,23 @@ async function runToolLoop(request) {
6308
6591
  stepCallLogger?.appendResponseDelta(responseText);
6309
6592
  }
6310
6593
  const responseToolCalls = response.toolCalls ?? [];
6594
+ stepToolCallText = serialiseOpenAiStyleToolCallsForLogging(
6595
+ responseToolCalls.map(
6596
+ (call) => call.kind === "custom" ? {
6597
+ kind: call.kind,
6598
+ name: call.name,
6599
+ input: call.input,
6600
+ callId: call.callId,
6601
+ itemId: call.id
6602
+ } : {
6603
+ kind: call.kind,
6604
+ name: call.name,
6605
+ arguments: call.arguments,
6606
+ callId: call.callId,
6607
+ itemId: call.id
6608
+ }
6609
+ )
6610
+ );
6311
6611
  if (responseToolCalls.length === 0) {
6312
6612
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6313
6613
  const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
@@ -6333,16 +6633,19 @@ async function runToolLoop(request) {
6333
6633
  timing: timing2
6334
6634
  });
6335
6635
  stepCallLogger?.complete({
6336
- provider: "chatgpt",
6337
- model: request.model,
6338
- modelVersion,
6339
- step: turn,
6340
- usage: usageTokens,
6341
- costUsd: stepCostUsd,
6342
- responseChars: responseText.length,
6343
- thoughtChars: reasoningSummaryText.length,
6344
- toolCalls: 0,
6345
- finalStep: steeringItems2.length === 0
6636
+ responseText,
6637
+ metadata: {
6638
+ provider: "chatgpt",
6639
+ model: request.model,
6640
+ modelVersion,
6641
+ step: turn,
6642
+ usage: usageTokens,
6643
+ costUsd: stepCostUsd,
6644
+ responseChars: responseText.length,
6645
+ thoughtChars: reasoningSummaryText.length,
6646
+ toolCalls: 0,
6647
+ finalStep: steeringItems2.length === 0
6648
+ }
6346
6649
  });
6347
6650
  if (steeringItems2.length === 0) {
6348
6651
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6475,25 +6778,33 @@ async function runToolLoop(request) {
6475
6778
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6476
6779
  const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
6477
6780
  stepCallLogger?.complete({
6478
- provider: "chatgpt",
6479
- model: request.model,
6480
- modelVersion,
6481
- step: turn,
6482
- usage: usageTokens,
6483
- costUsd: stepCostUsd,
6484
- responseChars: responseText.length,
6485
- thoughtChars: reasoningSummaryText.length,
6486
- toolCalls: toolCalls.length,
6487
- finalStep: false
6781
+ responseText,
6782
+ toolCallText: stepToolCallText,
6783
+ metadata: {
6784
+ provider: "chatgpt",
6785
+ model: request.model,
6786
+ modelVersion,
6787
+ step: turn,
6788
+ usage: usageTokens,
6789
+ costUsd: stepCostUsd,
6790
+ responseChars: responseText.length,
6791
+ thoughtChars: reasoningSummaryText.length,
6792
+ toolCalls: toolCalls.length,
6793
+ finalStep: false
6794
+ }
6488
6795
  });
6489
6796
  input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
6490
6797
  } catch (error) {
6491
6798
  stepCallLogger?.fail(error, {
6492
- provider: "chatgpt",
6493
- model: request.model,
6494
- modelVersion,
6495
- step: turn,
6496
- usage: usageTokens
6799
+ responseText,
6800
+ toolCallText: stepToolCallText,
6801
+ metadata: {
6802
+ provider: "chatgpt",
6803
+ model: request.model,
6804
+ modelVersion,
6805
+ step: turn,
6806
+ usage: usageTokens
6807
+ }
6497
6808
  });
6498
6809
  throw error;
6499
6810
  }
@@ -6516,6 +6827,7 @@ async function runToolLoop(request) {
6516
6827
  let usageTokens;
6517
6828
  let responseText = "";
6518
6829
  let blocked = false;
6830
+ let stepToolCallText;
6519
6831
  const stepRequestPayload = {
6520
6832
  model: providerInfo.model,
6521
6833
  messages,
@@ -6580,6 +6892,14 @@ async function runToolLoop(request) {
6580
6892
  });
6581
6893
  }
6582
6894
  const responseToolCalls = extractFireworksToolCalls(message);
6895
+ stepToolCallText = serialiseOpenAiStyleToolCallsForLogging(
6896
+ responseToolCalls.map((call) => ({
6897
+ kind: "function",
6898
+ name: call.name,
6899
+ arguments: call.arguments,
6900
+ callId: call.id
6901
+ }))
6902
+ );
6583
6903
  if (responseToolCalls.length === 0) {
6584
6904
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6585
6905
  const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
@@ -6605,17 +6925,20 @@ async function runToolLoop(request) {
6605
6925
  timing: timing2
6606
6926
  });
6607
6927
  stepCallLogger?.complete({
6608
- provider: "fireworks",
6609
- model: request.model,
6610
- modelVersion,
6611
- step: turn,
6612
- usage: usageTokens,
6613
- costUsd: stepCostUsd,
6614
- blocked,
6615
- responseChars: responseText.length,
6616
- thoughtChars: 0,
6617
- toolCalls: 0,
6618
- finalStep: steeringMessages.length === 0
6928
+ responseText,
6929
+ metadata: {
6930
+ provider: "fireworks",
6931
+ model: request.model,
6932
+ modelVersion,
6933
+ step: turn,
6934
+ usage: usageTokens,
6935
+ costUsd: stepCostUsd,
6936
+ blocked,
6937
+ responseChars: responseText.length,
6938
+ thoughtChars: 0,
6939
+ toolCalls: 0,
6940
+ finalStep: steeringMessages.length === 0
6941
+ }
6619
6942
  });
6620
6943
  if (steeringMessages.length === 0) {
6621
6944
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6727,17 +7050,21 @@ async function runToolLoop(request) {
6727
7050
  timing
6728
7051
  });
6729
7052
  stepCallLogger?.complete({
6730
- provider: "fireworks",
6731
- model: request.model,
6732
- modelVersion,
6733
- step: turn,
6734
- usage: usageTokens,
6735
- costUsd: stepCostUsd,
6736
- blocked,
6737
- responseChars: responseText.length,
6738
- thoughtChars: 0,
6739
- toolCalls: stepToolCalls.length,
6740
- finalStep: false
7053
+ responseText,
7054
+ toolCallText: stepToolCallText,
7055
+ metadata: {
7056
+ provider: "fireworks",
7057
+ model: request.model,
7058
+ modelVersion,
7059
+ step: turn,
7060
+ usage: usageTokens,
7061
+ costUsd: stepCostUsd,
7062
+ blocked,
7063
+ responseChars: responseText.length,
7064
+ thoughtChars: 0,
7065
+ toolCalls: stepToolCalls.length,
7066
+ finalStep: false
7067
+ }
6741
7068
  });
6742
7069
  messages.push({
6743
7070
  role: "assistant",
@@ -6751,12 +7078,16 @@ async function runToolLoop(request) {
6751
7078
  }
6752
7079
  } catch (error) {
6753
7080
  stepCallLogger?.fail(error, {
6754
- provider: "fireworks",
6755
- model: request.model,
6756
- modelVersion,
6757
- step: turn,
6758
- usage: usageTokens,
6759
- blocked
7081
+ responseText,
7082
+ toolCallText: stepToolCallText,
7083
+ metadata: {
7084
+ provider: "fireworks",
7085
+ model: request.model,
7086
+ modelVersion,
7087
+ step: turn,
7088
+ usage: usageTokens,
7089
+ blocked
7090
+ }
6760
7091
  });
6761
7092
  throw error;
6762
7093
  }
@@ -6776,6 +7107,7 @@ async function runToolLoop(request) {
6776
7107
  let usageTokens;
6777
7108
  let responseText = "";
6778
7109
  let thoughtsText = "";
7110
+ let stepToolCallText;
6779
7111
  const markFirstModelEvent = () => {
6780
7112
  if (firstModelEventAtMs === void 0) {
6781
7113
  firstModelEventAtMs = Date.now();
@@ -6896,12 +7228,17 @@ async function runToolLoop(request) {
6896
7228
  modelVersion = response.modelVersion ?? request.model;
6897
7229
  responseText = response.responseText.trim();
6898
7230
  thoughtsText = response.thoughtsText.trim();
7231
+ const responseOutputAttachments = collectLoggedAttachmentsFromGeminiParts(
7232
+ response.modelParts,
7233
+ "output"
7234
+ );
6899
7235
  const stepCostUsd = estimateCallCostUsd({
6900
7236
  modelId: modelVersion,
6901
7237
  tokens: usageTokens,
6902
7238
  responseImages: 0
6903
7239
  });
6904
7240
  totalCostUsd += stepCostUsd;
7241
+ stepToolCallText = serialiseGeminiToolCallsForLogging(response.functionCalls);
6905
7242
  if (response.functionCalls.length === 0) {
6906
7243
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6907
7244
  finalText = responseText;
@@ -6927,16 +7264,20 @@ async function runToolLoop(request) {
6927
7264
  timing: timing2
6928
7265
  });
6929
7266
  stepCallLogger?.complete({
6930
- provider: "gemini",
6931
- model: request.model,
6932
- modelVersion,
6933
- step: turn,
6934
- usage: usageTokens,
6935
- costUsd: stepCostUsd,
6936
- responseChars: responseText.length,
6937
- thoughtChars: thoughtsText.length,
6938
- toolCalls: 0,
6939
- finalStep: steeringInput2.length === 0
7267
+ responseText,
7268
+ attachments: responseOutputAttachments,
7269
+ metadata: {
7270
+ provider: "gemini",
7271
+ model: request.model,
7272
+ modelVersion,
7273
+ step: turn,
7274
+ usage: usageTokens,
7275
+ costUsd: stepCostUsd,
7276
+ responseChars: responseText.length,
7277
+ thoughtChars: thoughtsText.length,
7278
+ toolCalls: 0,
7279
+ finalStep: steeringInput2.length === 0
7280
+ }
6940
7281
  });
6941
7282
  if (steeringInput2.length === 0) {
6942
7283
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -7063,16 +7404,21 @@ async function runToolLoop(request) {
7063
7404
  timing
7064
7405
  });
7065
7406
  stepCallLogger?.complete({
7066
- provider: "gemini",
7067
- model: request.model,
7068
- modelVersion,
7069
- step: turn,
7070
- usage: usageTokens,
7071
- costUsd: stepCostUsd,
7072
- responseChars: responseText.length,
7073
- thoughtChars: thoughtsText.length,
7074
- toolCalls: toolCalls.length,
7075
- finalStep: false
7407
+ responseText,
7408
+ attachments: responseOutputAttachments,
7409
+ toolCallText: stepToolCallText,
7410
+ metadata: {
7411
+ provider: "gemini",
7412
+ model: request.model,
7413
+ modelVersion,
7414
+ step: turn,
7415
+ usage: usageTokens,
7416
+ costUsd: stepCostUsd,
7417
+ responseChars: responseText.length,
7418
+ thoughtChars: thoughtsText.length,
7419
+ toolCalls: toolCalls.length,
7420
+ finalStep: false
7421
+ }
7076
7422
  });
7077
7423
  geminiContents.push({ role: "user", parts: responseParts });
7078
7424
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
@@ -7081,13 +7427,17 @@ async function runToolLoop(request) {
7081
7427
  }
7082
7428
  } catch (error) {
7083
7429
  stepCallLogger?.fail(error, {
7084
- provider: "gemini",
7085
- model: request.model,
7086
- modelVersion,
7087
- step: turn,
7088
- usage: usageTokens,
7089
- responseChars: responseText.length,
7090
- thoughtChars: thoughtsText.length
7430
+ responseText,
7431
+ toolCallText: stepToolCallText,
7432
+ metadata: {
7433
+ provider: "gemini",
7434
+ model: request.model,
7435
+ modelVersion,
7436
+ step: turn,
7437
+ usage: usageTokens,
7438
+ responseChars: responseText.length,
7439
+ thoughtChars: thoughtsText.length
7440
+ }
7091
7441
  });
7092
7442
  throw error;
7093
7443
  }
@@ -10136,19 +10486,17 @@ async function runAgentLoopInternal(request, context) {
10136
10486
  );
10137
10487
  const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
10138
10488
  const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
10489
+ const streamEventLogger = loggingSession ? createAgentStreamEventLogger({
10490
+ append: (line) => {
10491
+ loggingSession.logLine(`[agent:${runId}] ${line}`);
10492
+ }
10493
+ }) : void 0;
10139
10494
  const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
10140
10495
  sourceOnEvent?.(event);
10141
10496
  if (includeLlmStreamEvents) {
10142
10497
  emitTelemetry({ type: "agent.run.stream", event });
10143
10498
  }
10144
- if (loggingSession) {
10145
- appendAgentStreamEventLog({
10146
- event,
10147
- append: (line) => {
10148
- loggingSession.logLine(`[agent:${runId}] ${line}`);
10149
- }
10150
- });
10151
- }
10499
+ streamEventLogger?.appendEvent(event);
10152
10500
  } : void 0;
10153
10501
  try {
10154
10502
  const result = await runToolLoop({
@@ -10157,6 +10505,7 @@ async function runAgentLoopInternal(request, context) {
10157
10505
  ...wrappedOnEvent ? { onEvent: wrappedOnEvent } : {},
10158
10506
  tools: mergedTools
10159
10507
  });
10508
+ streamEventLogger?.flush();
10160
10509
  emitTelemetry({
10161
10510
  type: "agent.run.completed",
10162
10511
  success: true,
@@ -10189,6 +10538,7 @@ async function runAgentLoopInternal(request, context) {
10189
10538
  }
10190
10539
  return result;
10191
10540
  } catch (error) {
10541
+ streamEventLogger?.flush();
10192
10542
  emitTelemetry({
10193
10543
  type: "agent.run.completed",
10194
10544
  success: false,
@@ -10205,6 +10555,7 @@ async function runAgentLoopInternal(request, context) {
10205
10555
  );
10206
10556
  throw error;
10207
10557
  } finally {
10558
+ streamEventLogger?.flush();
10208
10559
  await subagentController?.closeAll();
10209
10560
  }
10210
10561
  }