@ljoukov/llm 4.0.6 → 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
@@ -2818,6 +2818,9 @@ function ensureTrailingNewline(value) {
2818
2818
  return value.endsWith("\n") ? value : `${value}
2819
2819
  `;
2820
2820
  }
2821
+ function hasNonEmptyText(value) {
2822
+ return typeof value === "string" && value.length > 0;
2823
+ }
2821
2824
  function redactDataUrlPayload(value) {
2822
2825
  if (!value.toLowerCase().startsWith("data:")) {
2823
2826
  return value;
@@ -3066,6 +3069,25 @@ var AgentLoggingSessionImpl = class {
3066
3069
  }
3067
3070
  this.enqueueLineWrite(timestamped);
3068
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
+ }
3069
3091
  startLlmCall(input) {
3070
3092
  const callNumber = this.callCounter + 1;
3071
3093
  this.callCounter = callNumber;
@@ -3078,6 +3100,9 @@ var AgentLoggingSessionImpl = class {
3078
3100
  );
3079
3101
  const responsePath = import_node_path3.default.join(baseDir, "response.txt");
3080
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");
3081
3106
  const responseMetadataPath = import_node_path3.default.join(baseDir, "response.metadata.json");
3082
3107
  let chain = this.ensureReady.then(async () => {
3083
3108
  await (0, import_promises.mkdir)(baseDir, { recursive: true });
@@ -3099,22 +3124,13 @@ var AgentLoggingSessionImpl = class {
3099
3124
  `,
3100
3125
  "utf8"
3101
3126
  );
3102
- const usedNames = /* @__PURE__ */ new Set();
3103
- for (const attachment of input.attachments ?? []) {
3104
- let filename = normalisePathSegment(attachment.filename);
3105
- if (!filename.includes(".")) {
3106
- filename = `${filename}.bin`;
3107
- }
3108
- const ext = import_node_path3.default.extname(filename);
3109
- const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
3110
- let candidate = filename;
3111
- let duplicateIndex = 2;
3112
- while (usedNames.has(candidate)) {
3113
- candidate = `${base}-${duplicateIndex.toString()}${ext}`;
3114
- duplicateIndex += 1;
3115
- }
3116
- usedNames.add(candidate);
3117
- 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
+ );
3118
3134
  }
3119
3135
  }).catch(() => void 0);
3120
3136
  this.track(chain);
@@ -3142,18 +3158,25 @@ var AgentLoggingSessionImpl = class {
3142
3158
  await (0, import_promises.appendFile)(responsePath, text, "utf8");
3143
3159
  });
3144
3160
  },
3145
- complete: (metadata) => {
3161
+ complete: (options) => {
3146
3162
  if (closed) {
3147
3163
  return;
3148
3164
  }
3149
3165
  closed = true;
3150
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);
3151
3174
  const payload = {
3152
3175
  capturedAt: toIsoNow(),
3153
3176
  status: "completed"
3154
3177
  };
3155
- if (metadata) {
3156
- const sanitised = sanitiseLogValue(metadata);
3178
+ if (options?.metadata) {
3179
+ const sanitised = sanitiseLogValue(options.metadata);
3157
3180
  if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3158
3181
  Object.assign(payload, sanitised);
3159
3182
  } else if (sanitised !== void 0) {
@@ -3164,19 +3187,27 @@ var AgentLoggingSessionImpl = class {
3164
3187
  `, "utf8");
3165
3188
  });
3166
3189
  },
3167
- fail: (error, metadata) => {
3190
+ fail: (error, options) => {
3168
3191
  if (closed) {
3169
3192
  return;
3170
3193
  }
3171
3194
  closed = true;
3172
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");
3173
3204
  const payload = {
3174
3205
  capturedAt: toIsoNow(),
3175
3206
  status: "failed",
3176
3207
  error: toErrorMessage(error)
3177
3208
  };
3178
- if (metadata) {
3179
- const sanitised = sanitiseLogValue(metadata);
3209
+ if (options?.metadata) {
3210
+ const sanitised = sanitiseLogValue(options.metadata);
3180
3211
  if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3181
3212
  Object.assign(payload, sanitised);
3182
3213
  } else if (sanitised !== void 0) {
@@ -5019,7 +5050,10 @@ function resolveAttachmentExtension(mimeType) {
5019
5050
  }
5020
5051
  }
5021
5052
  }
5022
- function decodeDataUrlAttachment(value, basename) {
5053
+ function buildLoggedAttachmentFilename(prefix, index, mimeType) {
5054
+ return `${prefix}-${index.toString()}.${resolveAttachmentExtension(mimeType)}`;
5055
+ }
5056
+ function decodeDataUrlAttachment(value, options) {
5023
5057
  const trimmed = value.trim();
5024
5058
  if (!trimmed.toLowerCase().startsWith("data:")) {
5025
5059
  return null;
@@ -5035,7 +5069,7 @@ function decodeDataUrlAttachment(value, basename) {
5035
5069
  try {
5036
5070
  const bytes = isBase64 ? import_node_buffer3.Buffer.from(payload, "base64") : import_node_buffer3.Buffer.from(decodeURIComponent(payload), "utf8");
5037
5071
  return {
5038
- filename: `${basename}.${resolveAttachmentExtension(mimeType)}`,
5072
+ filename: buildLoggedAttachmentFilename(options.prefix, options.index, mimeType),
5039
5073
  bytes
5040
5074
  };
5041
5075
  } catch {
@@ -5044,10 +5078,10 @@ function decodeDataUrlAttachment(value, basename) {
5044
5078
  }
5045
5079
  function collectPayloadAttachments(value, options) {
5046
5080
  if (typeof value === "string") {
5047
- const attachment = decodeDataUrlAttachment(
5048
- value,
5049
- `${options.prefix}-${options.counter.toString()}`
5050
- );
5081
+ const attachment = decodeDataUrlAttachment(value, {
5082
+ prefix: options.prefix,
5083
+ index: options.counter
5084
+ });
5051
5085
  if (attachment) {
5052
5086
  options.attachments.push(attachment);
5053
5087
  options.counter += 1;
@@ -5072,7 +5106,7 @@ function collectPayloadAttachments(value, options) {
5072
5106
  if (typeof record.data === "string" && mimeType) {
5073
5107
  try {
5074
5108
  options.attachments.push({
5075
- filename: `${options.prefix}-${options.counter.toString()}.${resolveAttachmentExtension(mimeType)}`,
5109
+ filename: buildLoggedAttachmentFilename(options.prefix, options.counter, mimeType),
5076
5110
  bytes: decodeInlineDataBuffer(record.data)
5077
5111
  });
5078
5112
  options.counter += 1;
@@ -5092,27 +5126,166 @@ function serialiseRequestPayloadForLogging(value) {
5092
5126
  `;
5093
5127
  }
5094
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
+ }
5095
5266
  function startLlmCallLoggerFromContents(options) {
5096
5267
  const session = getCurrentAgentLoggingSession();
5097
5268
  if (!session) {
5098
5269
  return void 0;
5099
5270
  }
5100
5271
  const attachments = [];
5272
+ let attachmentIndex = 1;
5101
5273
  const sections = [];
5102
5274
  for (const [messageIndex, message] of options.contents.entries()) {
5103
5275
  sections.push(`### message_${(messageIndex + 1).toString()} role=${message.role}`);
5104
- for (const [partIndex, part] of message.parts.entries()) {
5276
+ for (const part of message.parts) {
5105
5277
  if (part.type === "text") {
5106
5278
  const channel = part.thought === true ? "thought" : "response";
5107
5279
  sections.push(`[text:${channel}]`);
5108
5280
  sections.push(part.text);
5109
5281
  continue;
5110
5282
  }
5111
- const filename = `message-${(messageIndex + 1).toString()}-part-${(partIndex + 1).toString()}.${resolveAttachmentExtension(part.mimeType)}`;
5283
+ const filename = buildLoggedAttachmentFilename("input", attachmentIndex, part.mimeType);
5112
5284
  attachments.push({
5113
5285
  filename,
5114
5286
  bytes: decodeInlineDataBuffer(part.data)
5115
5287
  });
5288
+ attachmentIndex += 1;
5116
5289
  sections.push(
5117
5290
  `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
5118
5291
  );
@@ -5156,11 +5329,18 @@ function startLlmCallLoggerFromPayload(options) {
5156
5329
  }
5157
5330
  const attachments = [];
5158
5331
  collectPayloadAttachments(options.requestPayload, {
5159
- prefix: `step-${options.step.toString()}`,
5332
+ prefix: "input",
5160
5333
  attachments,
5161
5334
  seen: /* @__PURE__ */ new WeakSet(),
5162
5335
  counter: 1
5163
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
+ );
5164
5344
  return session.startLlmCall({
5165
5345
  provider: options.provider,
5166
5346
  modelId: options.modelId,
@@ -5169,7 +5349,8 @@ function startLlmCallLoggerFromPayload(options) {
5169
5349
  step: options.step,
5170
5350
  ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
5171
5351
  },
5172
- attachments
5352
+ attachments,
5353
+ toolCallResponseText
5173
5354
  });
5174
5355
  }
5175
5356
  async function runTextCall(params) {
@@ -5474,6 +5655,7 @@ async function runTextCall(params) {
5474
5655
  const mergedParts = mergeConsecutiveTextParts(responseParts);
5475
5656
  const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
5476
5657
  const { text, thoughts } = extractTextByChannel(content);
5658
+ const outputAttachments = collectLoggedAttachmentsFromLlmParts(mergedParts, "output");
5477
5659
  const costUsd = estimateCallCostUsd({
5478
5660
  modelId: modelVersion,
5479
5661
  tokens: latestUsage,
@@ -5484,16 +5666,20 @@ async function runTextCall(params) {
5484
5666
  queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5485
5667
  }
5486
5668
  callLogger?.complete({
5487
- provider,
5488
- model: request.model,
5489
- modelVersion,
5490
- blocked,
5491
- costUsd,
5492
- usage: latestUsage,
5493
- grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5494
- responseChars: text.length,
5495
- thoughtChars: thoughts.length,
5496
- 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
+ }
5497
5683
  });
5498
5684
  return {
5499
5685
  provider,
@@ -5508,14 +5694,21 @@ async function runTextCall(params) {
5508
5694
  grounding
5509
5695
  };
5510
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);
5511
5700
  callLogger?.fail(error, {
5512
- provider,
5513
- model: request.model,
5514
- modelVersion,
5515
- blocked,
5516
- usage: latestUsage,
5517
- partialResponseParts: responseParts.length,
5518
- 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
+ }
5519
5712
  });
5520
5713
  throw error;
5521
5714
  }
@@ -5982,6 +6175,9 @@ async function runToolLoop(request) {
5982
6175
  let usageTokens;
5983
6176
  let thoughtDeltaEmitted = false;
5984
6177
  let blocked = false;
6178
+ let responseText = "";
6179
+ let reasoningSummary = "";
6180
+ let stepToolCallText;
5985
6181
  const stepRequestPayload = {
5986
6182
  model: providerInfo.model,
5987
6183
  input,
@@ -6074,8 +6270,8 @@ async function runToolLoop(request) {
6074
6270
  throw new Error(message);
6075
6271
  }
6076
6272
  usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
6077
- const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
6078
- 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();
6079
6275
  if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
6080
6276
  stepCallLogger?.appendThoughtDelta(reasoningSummary);
6081
6277
  emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
@@ -6091,6 +6287,23 @@ async function runToolLoop(request) {
6091
6287
  emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
6092
6288
  }
6093
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
+ );
6094
6307
  const stepToolCalls = [];
6095
6308
  if (responseToolCalls.length === 0) {
6096
6309
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
@@ -6118,17 +6331,20 @@ async function runToolLoop(request) {
6118
6331
  timing: timing2
6119
6332
  });
6120
6333
  stepCallLogger?.complete({
6121
- provider: "openai",
6122
- model: request.model,
6123
- modelVersion,
6124
- step: turn,
6125
- usage: usageTokens,
6126
- costUsd: stepCostUsd,
6127
- blocked,
6128
- responseChars: responseText.length,
6129
- thoughtChars: reasoningSummary.length,
6130
- toolCalls: 0,
6131
- 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
+ }
6132
6348
  });
6133
6349
  if (steeringItems2.length === 0) {
6134
6350
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6251,28 +6467,36 @@ async function runToolLoop(request) {
6251
6467
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6252
6468
  const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
6253
6469
  stepCallLogger?.complete({
6254
- provider: "openai",
6255
- model: request.model,
6256
- modelVersion,
6257
- step: turn,
6258
- usage: usageTokens,
6259
- costUsd: stepCostUsd,
6260
- blocked,
6261
- responseChars: responseText.length,
6262
- thoughtChars: reasoningSummary.length,
6263
- toolCalls: stepToolCalls.length,
6264
- 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
+ }
6265
6485
  });
6266
6486
  previousResponseId = finalResponse.id;
6267
6487
  input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
6268
6488
  } catch (error) {
6269
6489
  stepCallLogger?.fail(error, {
6270
- provider: "openai",
6271
- model: request.model,
6272
- modelVersion,
6273
- step: turn,
6274
- usage: usageTokens,
6275
- 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
+ }
6276
6500
  });
6277
6501
  throw error;
6278
6502
  }
@@ -6298,6 +6522,7 @@ async function runToolLoop(request) {
6298
6522
  let usageTokens;
6299
6523
  let responseText = "";
6300
6524
  let reasoningSummaryText = "";
6525
+ let stepToolCallText;
6301
6526
  const markFirstModelEvent = () => {
6302
6527
  if (firstModelEventAtMs === void 0) {
6303
6528
  firstModelEventAtMs = Date.now();
@@ -6366,6 +6591,23 @@ async function runToolLoop(request) {
6366
6591
  stepCallLogger?.appendResponseDelta(responseText);
6367
6592
  }
6368
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
+ );
6369
6611
  if (responseToolCalls.length === 0) {
6370
6612
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6371
6613
  const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
@@ -6391,16 +6633,19 @@ async function runToolLoop(request) {
6391
6633
  timing: timing2
6392
6634
  });
6393
6635
  stepCallLogger?.complete({
6394
- provider: "chatgpt",
6395
- model: request.model,
6396
- modelVersion,
6397
- step: turn,
6398
- usage: usageTokens,
6399
- costUsd: stepCostUsd,
6400
- responseChars: responseText.length,
6401
- thoughtChars: reasoningSummaryText.length,
6402
- toolCalls: 0,
6403
- 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
+ }
6404
6649
  });
6405
6650
  if (steeringItems2.length === 0) {
6406
6651
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6533,25 +6778,33 @@ async function runToolLoop(request) {
6533
6778
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6534
6779
  const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
6535
6780
  stepCallLogger?.complete({
6536
- provider: "chatgpt",
6537
- model: request.model,
6538
- modelVersion,
6539
- step: turn,
6540
- usage: usageTokens,
6541
- costUsd: stepCostUsd,
6542
- responseChars: responseText.length,
6543
- thoughtChars: reasoningSummaryText.length,
6544
- toolCalls: toolCalls.length,
6545
- 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
+ }
6546
6795
  });
6547
6796
  input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
6548
6797
  } catch (error) {
6549
6798
  stepCallLogger?.fail(error, {
6550
- provider: "chatgpt",
6551
- model: request.model,
6552
- modelVersion,
6553
- step: turn,
6554
- 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
+ }
6555
6808
  });
6556
6809
  throw error;
6557
6810
  }
@@ -6574,6 +6827,7 @@ async function runToolLoop(request) {
6574
6827
  let usageTokens;
6575
6828
  let responseText = "";
6576
6829
  let blocked = false;
6830
+ let stepToolCallText;
6577
6831
  const stepRequestPayload = {
6578
6832
  model: providerInfo.model,
6579
6833
  messages,
@@ -6638,6 +6892,14 @@ async function runToolLoop(request) {
6638
6892
  });
6639
6893
  }
6640
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
+ );
6641
6903
  if (responseToolCalls.length === 0) {
6642
6904
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6643
6905
  const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
@@ -6663,17 +6925,20 @@ async function runToolLoop(request) {
6663
6925
  timing: timing2
6664
6926
  });
6665
6927
  stepCallLogger?.complete({
6666
- provider: "fireworks",
6667
- model: request.model,
6668
- modelVersion,
6669
- step: turn,
6670
- usage: usageTokens,
6671
- costUsd: stepCostUsd,
6672
- blocked,
6673
- responseChars: responseText.length,
6674
- thoughtChars: 0,
6675
- toolCalls: 0,
6676
- 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
+ }
6677
6942
  });
6678
6943
  if (steeringMessages.length === 0) {
6679
6944
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6785,17 +7050,21 @@ async function runToolLoop(request) {
6785
7050
  timing
6786
7051
  });
6787
7052
  stepCallLogger?.complete({
6788
- provider: "fireworks",
6789
- model: request.model,
6790
- modelVersion,
6791
- step: turn,
6792
- usage: usageTokens,
6793
- costUsd: stepCostUsd,
6794
- blocked,
6795
- responseChars: responseText.length,
6796
- thoughtChars: 0,
6797
- toolCalls: stepToolCalls.length,
6798
- 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
+ }
6799
7068
  });
6800
7069
  messages.push({
6801
7070
  role: "assistant",
@@ -6809,12 +7078,16 @@ async function runToolLoop(request) {
6809
7078
  }
6810
7079
  } catch (error) {
6811
7080
  stepCallLogger?.fail(error, {
6812
- provider: "fireworks",
6813
- model: request.model,
6814
- modelVersion,
6815
- step: turn,
6816
- usage: usageTokens,
6817
- 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
+ }
6818
7091
  });
6819
7092
  throw error;
6820
7093
  }
@@ -6834,6 +7107,7 @@ async function runToolLoop(request) {
6834
7107
  let usageTokens;
6835
7108
  let responseText = "";
6836
7109
  let thoughtsText = "";
7110
+ let stepToolCallText;
6837
7111
  const markFirstModelEvent = () => {
6838
7112
  if (firstModelEventAtMs === void 0) {
6839
7113
  firstModelEventAtMs = Date.now();
@@ -6954,12 +7228,17 @@ async function runToolLoop(request) {
6954
7228
  modelVersion = response.modelVersion ?? request.model;
6955
7229
  responseText = response.responseText.trim();
6956
7230
  thoughtsText = response.thoughtsText.trim();
7231
+ const responseOutputAttachments = collectLoggedAttachmentsFromGeminiParts(
7232
+ response.modelParts,
7233
+ "output"
7234
+ );
6957
7235
  const stepCostUsd = estimateCallCostUsd({
6958
7236
  modelId: modelVersion,
6959
7237
  tokens: usageTokens,
6960
7238
  responseImages: 0
6961
7239
  });
6962
7240
  totalCostUsd += stepCostUsd;
7241
+ stepToolCallText = serialiseGeminiToolCallsForLogging(response.functionCalls);
6963
7242
  if (response.functionCalls.length === 0) {
6964
7243
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6965
7244
  finalText = responseText;
@@ -6985,16 +7264,20 @@ async function runToolLoop(request) {
6985
7264
  timing: timing2
6986
7265
  });
6987
7266
  stepCallLogger?.complete({
6988
- provider: "gemini",
6989
- model: request.model,
6990
- modelVersion,
6991
- step: turn,
6992
- usage: usageTokens,
6993
- costUsd: stepCostUsd,
6994
- responseChars: responseText.length,
6995
- thoughtChars: thoughtsText.length,
6996
- toolCalls: 0,
6997
- 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
+ }
6998
7281
  });
6999
7282
  if (steeringInput2.length === 0) {
7000
7283
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -7121,16 +7404,21 @@ async function runToolLoop(request) {
7121
7404
  timing
7122
7405
  });
7123
7406
  stepCallLogger?.complete({
7124
- provider: "gemini",
7125
- model: request.model,
7126
- modelVersion,
7127
- step: turn,
7128
- usage: usageTokens,
7129
- costUsd: stepCostUsd,
7130
- responseChars: responseText.length,
7131
- thoughtChars: thoughtsText.length,
7132
- toolCalls: toolCalls.length,
7133
- 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
+ }
7134
7422
  });
7135
7423
  geminiContents.push({ role: "user", parts: responseParts });
7136
7424
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
@@ -7139,13 +7427,17 @@ async function runToolLoop(request) {
7139
7427
  }
7140
7428
  } catch (error) {
7141
7429
  stepCallLogger?.fail(error, {
7142
- provider: "gemini",
7143
- model: request.model,
7144
- modelVersion,
7145
- step: turn,
7146
- usage: usageTokens,
7147
- responseChars: responseText.length,
7148
- 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
+ }
7149
7441
  });
7150
7442
  throw error;
7151
7443
  }