@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.js CHANGED
@@ -2706,6 +2706,9 @@ function ensureTrailingNewline(value) {
2706
2706
  return value.endsWith("\n") ? value : `${value}
2707
2707
  `;
2708
2708
  }
2709
+ function hasNonEmptyText(value) {
2710
+ return typeof value === "string" && value.length > 0;
2711
+ }
2709
2712
  function redactDataUrlPayload(value) {
2710
2713
  if (!value.toLowerCase().startsWith("data:")) {
2711
2714
  return value;
@@ -2954,6 +2957,25 @@ var AgentLoggingSessionImpl = class {
2954
2957
  }
2955
2958
  this.enqueueLineWrite(timestamped);
2956
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
+ }
2957
2979
  startLlmCall(input) {
2958
2980
  const callNumber = this.callCounter + 1;
2959
2981
  this.callCounter = callNumber;
@@ -2966,6 +2988,9 @@ var AgentLoggingSessionImpl = class {
2966
2988
  );
2967
2989
  const responsePath = path3.join(baseDir, "response.txt");
2968
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");
2969
2994
  const responseMetadataPath = path3.join(baseDir, "response.metadata.json");
2970
2995
  let chain = this.ensureReady.then(async () => {
2971
2996
  await mkdir(baseDir, { recursive: true });
@@ -2987,22 +3012,13 @@ var AgentLoggingSessionImpl = class {
2987
3012
  `,
2988
3013
  "utf8"
2989
3014
  );
2990
- const usedNames = /* @__PURE__ */ new Set();
2991
- for (const attachment of input.attachments ?? []) {
2992
- let filename = normalisePathSegment(attachment.filename);
2993
- if (!filename.includes(".")) {
2994
- filename = `${filename}.bin`;
2995
- }
2996
- const ext = path3.extname(filename);
2997
- const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
2998
- let candidate = filename;
2999
- let duplicateIndex = 2;
3000
- while (usedNames.has(candidate)) {
3001
- candidate = `${base}-${duplicateIndex.toString()}${ext}`;
3002
- duplicateIndex += 1;
3003
- }
3004
- usedNames.add(candidate);
3005
- 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
+ );
3006
3022
  }
3007
3023
  }).catch(() => void 0);
3008
3024
  this.track(chain);
@@ -3030,18 +3046,25 @@ var AgentLoggingSessionImpl = class {
3030
3046
  await appendFile(responsePath, text, "utf8");
3031
3047
  });
3032
3048
  },
3033
- complete: (metadata) => {
3049
+ complete: (options) => {
3034
3050
  if (closed) {
3035
3051
  return;
3036
3052
  }
3037
3053
  closed = true;
3038
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);
3039
3062
  const payload = {
3040
3063
  capturedAt: toIsoNow(),
3041
3064
  status: "completed"
3042
3065
  };
3043
- if (metadata) {
3044
- const sanitised = sanitiseLogValue(metadata);
3066
+ if (options?.metadata) {
3067
+ const sanitised = sanitiseLogValue(options.metadata);
3045
3068
  if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3046
3069
  Object.assign(payload, sanitised);
3047
3070
  } else if (sanitised !== void 0) {
@@ -3052,19 +3075,27 @@ var AgentLoggingSessionImpl = class {
3052
3075
  `, "utf8");
3053
3076
  });
3054
3077
  },
3055
- fail: (error, metadata) => {
3078
+ fail: (error, options) => {
3056
3079
  if (closed) {
3057
3080
  return;
3058
3081
  }
3059
3082
  closed = true;
3060
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");
3061
3092
  const payload = {
3062
3093
  capturedAt: toIsoNow(),
3063
3094
  status: "failed",
3064
3095
  error: toErrorMessage(error)
3065
3096
  };
3066
- if (metadata) {
3067
- const sanitised = sanitiseLogValue(metadata);
3097
+ if (options?.metadata) {
3098
+ const sanitised = sanitiseLogValue(options.metadata);
3068
3099
  if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
3069
3100
  Object.assign(payload, sanitised);
3070
3101
  } else if (sanitised !== void 0) {
@@ -4907,7 +4938,10 @@ function resolveAttachmentExtension(mimeType) {
4907
4938
  }
4908
4939
  }
4909
4940
  }
4910
- function decodeDataUrlAttachment(value, basename) {
4941
+ function buildLoggedAttachmentFilename(prefix, index, mimeType) {
4942
+ return `${prefix}-${index.toString()}.${resolveAttachmentExtension(mimeType)}`;
4943
+ }
4944
+ function decodeDataUrlAttachment(value, options) {
4911
4945
  const trimmed = value.trim();
4912
4946
  if (!trimmed.toLowerCase().startsWith("data:")) {
4913
4947
  return null;
@@ -4923,7 +4957,7 @@ function decodeDataUrlAttachment(value, basename) {
4923
4957
  try {
4924
4958
  const bytes = isBase64 ? Buffer4.from(payload, "base64") : Buffer4.from(decodeURIComponent(payload), "utf8");
4925
4959
  return {
4926
- filename: `${basename}.${resolveAttachmentExtension(mimeType)}`,
4960
+ filename: buildLoggedAttachmentFilename(options.prefix, options.index, mimeType),
4927
4961
  bytes
4928
4962
  };
4929
4963
  } catch {
@@ -4932,10 +4966,10 @@ function decodeDataUrlAttachment(value, basename) {
4932
4966
  }
4933
4967
  function collectPayloadAttachments(value, options) {
4934
4968
  if (typeof value === "string") {
4935
- const attachment = decodeDataUrlAttachment(
4936
- value,
4937
- `${options.prefix}-${options.counter.toString()}`
4938
- );
4969
+ const attachment = decodeDataUrlAttachment(value, {
4970
+ prefix: options.prefix,
4971
+ index: options.counter
4972
+ });
4939
4973
  if (attachment) {
4940
4974
  options.attachments.push(attachment);
4941
4975
  options.counter += 1;
@@ -4960,7 +4994,7 @@ function collectPayloadAttachments(value, options) {
4960
4994
  if (typeof record.data === "string" && mimeType) {
4961
4995
  try {
4962
4996
  options.attachments.push({
4963
- filename: `${options.prefix}-${options.counter.toString()}.${resolveAttachmentExtension(mimeType)}`,
4997
+ filename: buildLoggedAttachmentFilename(options.prefix, options.counter, mimeType),
4964
4998
  bytes: decodeInlineDataBuffer(record.data)
4965
4999
  });
4966
5000
  options.counter += 1;
@@ -4980,27 +5014,166 @@ function serialiseRequestPayloadForLogging(value) {
4980
5014
  `;
4981
5015
  }
4982
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
+ }
4983
5154
  function startLlmCallLoggerFromContents(options) {
4984
5155
  const session = getCurrentAgentLoggingSession();
4985
5156
  if (!session) {
4986
5157
  return void 0;
4987
5158
  }
4988
5159
  const attachments = [];
5160
+ let attachmentIndex = 1;
4989
5161
  const sections = [];
4990
5162
  for (const [messageIndex, message] of options.contents.entries()) {
4991
5163
  sections.push(`### message_${(messageIndex + 1).toString()} role=${message.role}`);
4992
- for (const [partIndex, part] of message.parts.entries()) {
5164
+ for (const part of message.parts) {
4993
5165
  if (part.type === "text") {
4994
5166
  const channel = part.thought === true ? "thought" : "response";
4995
5167
  sections.push(`[text:${channel}]`);
4996
5168
  sections.push(part.text);
4997
5169
  continue;
4998
5170
  }
4999
- const filename = `message-${(messageIndex + 1).toString()}-part-${(partIndex + 1).toString()}.${resolveAttachmentExtension(part.mimeType)}`;
5171
+ const filename = buildLoggedAttachmentFilename("input", attachmentIndex, part.mimeType);
5000
5172
  attachments.push({
5001
5173
  filename,
5002
5174
  bytes: decodeInlineDataBuffer(part.data)
5003
5175
  });
5176
+ attachmentIndex += 1;
5004
5177
  sections.push(
5005
5178
  `[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
5006
5179
  );
@@ -5044,11 +5217,18 @@ function startLlmCallLoggerFromPayload(options) {
5044
5217
  }
5045
5218
  const attachments = [];
5046
5219
  collectPayloadAttachments(options.requestPayload, {
5047
- prefix: `step-${options.step.toString()}`,
5220
+ prefix: "input",
5048
5221
  attachments,
5049
5222
  seen: /* @__PURE__ */ new WeakSet(),
5050
5223
  counter: 1
5051
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
+ );
5052
5232
  return session.startLlmCall({
5053
5233
  provider: options.provider,
5054
5234
  modelId: options.modelId,
@@ -5057,7 +5237,8 @@ function startLlmCallLoggerFromPayload(options) {
5057
5237
  step: options.step,
5058
5238
  ...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
5059
5239
  },
5060
- attachments
5240
+ attachments,
5241
+ toolCallResponseText
5061
5242
  });
5062
5243
  }
5063
5244
  async function runTextCall(params) {
@@ -5362,6 +5543,7 @@ async function runTextCall(params) {
5362
5543
  const mergedParts = mergeConsecutiveTextParts(responseParts);
5363
5544
  const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
5364
5545
  const { text, thoughts } = extractTextByChannel(content);
5546
+ const outputAttachments = collectLoggedAttachmentsFromLlmParts(mergedParts, "output");
5365
5547
  const costUsd = estimateCallCostUsd({
5366
5548
  modelId: modelVersion,
5367
5549
  tokens: latestUsage,
@@ -5372,16 +5554,20 @@ async function runTextCall(params) {
5372
5554
  queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
5373
5555
  }
5374
5556
  callLogger?.complete({
5375
- provider,
5376
- model: request.model,
5377
- modelVersion,
5378
- blocked,
5379
- costUsd,
5380
- usage: latestUsage,
5381
- grounding: grounding ? sanitiseLogValue(grounding) : void 0,
5382
- responseChars: text.length,
5383
- thoughtChars: thoughts.length,
5384
- 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
+ }
5385
5571
  });
5386
5572
  return {
5387
5573
  provider,
@@ -5396,14 +5582,21 @@ async function runTextCall(params) {
5396
5582
  grounding
5397
5583
  };
5398
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);
5399
5588
  callLogger?.fail(error, {
5400
- provider,
5401
- model: request.model,
5402
- modelVersion,
5403
- blocked,
5404
- usage: latestUsage,
5405
- partialResponseParts: responseParts.length,
5406
- 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
+ }
5407
5600
  });
5408
5601
  throw error;
5409
5602
  }
@@ -5870,6 +6063,9 @@ async function runToolLoop(request) {
5870
6063
  let usageTokens;
5871
6064
  let thoughtDeltaEmitted = false;
5872
6065
  let blocked = false;
6066
+ let responseText = "";
6067
+ let reasoningSummary = "";
6068
+ let stepToolCallText;
5873
6069
  const stepRequestPayload = {
5874
6070
  model: providerInfo.model,
5875
6071
  input,
@@ -5962,8 +6158,8 @@ async function runToolLoop(request) {
5962
6158
  throw new Error(message);
5963
6159
  }
5964
6160
  usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
5965
- const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
5966
- 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();
5967
6163
  if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
5968
6164
  stepCallLogger?.appendThoughtDelta(reasoningSummary);
5969
6165
  emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
@@ -5979,6 +6175,23 @@ async function runToolLoop(request) {
5979
6175
  emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
5980
6176
  }
5981
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
+ );
5982
6195
  const stepToolCalls = [];
5983
6196
  if (responseToolCalls.length === 0) {
5984
6197
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
@@ -6006,17 +6219,20 @@ async function runToolLoop(request) {
6006
6219
  timing: timing2
6007
6220
  });
6008
6221
  stepCallLogger?.complete({
6009
- provider: "openai",
6010
- model: request.model,
6011
- modelVersion,
6012
- step: turn,
6013
- usage: usageTokens,
6014
- costUsd: stepCostUsd,
6015
- blocked,
6016
- responseChars: responseText.length,
6017
- thoughtChars: reasoningSummary.length,
6018
- toolCalls: 0,
6019
- 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
+ }
6020
6236
  });
6021
6237
  if (steeringItems2.length === 0) {
6022
6238
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6139,28 +6355,36 @@ async function runToolLoop(request) {
6139
6355
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6140
6356
  const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
6141
6357
  stepCallLogger?.complete({
6142
- provider: "openai",
6143
- model: request.model,
6144
- modelVersion,
6145
- step: turn,
6146
- usage: usageTokens,
6147
- costUsd: stepCostUsd,
6148
- blocked,
6149
- responseChars: responseText.length,
6150
- thoughtChars: reasoningSummary.length,
6151
- toolCalls: stepToolCalls.length,
6152
- 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
+ }
6153
6373
  });
6154
6374
  previousResponseId = finalResponse.id;
6155
6375
  input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
6156
6376
  } catch (error) {
6157
6377
  stepCallLogger?.fail(error, {
6158
- provider: "openai",
6159
- model: request.model,
6160
- modelVersion,
6161
- step: turn,
6162
- usage: usageTokens,
6163
- 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
+ }
6164
6388
  });
6165
6389
  throw error;
6166
6390
  }
@@ -6186,6 +6410,7 @@ async function runToolLoop(request) {
6186
6410
  let usageTokens;
6187
6411
  let responseText = "";
6188
6412
  let reasoningSummaryText = "";
6413
+ let stepToolCallText;
6189
6414
  const markFirstModelEvent = () => {
6190
6415
  if (firstModelEventAtMs === void 0) {
6191
6416
  firstModelEventAtMs = Date.now();
@@ -6254,6 +6479,23 @@ async function runToolLoop(request) {
6254
6479
  stepCallLogger?.appendResponseDelta(responseText);
6255
6480
  }
6256
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
+ );
6257
6499
  if (responseToolCalls.length === 0) {
6258
6500
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6259
6501
  const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
@@ -6279,16 +6521,19 @@ async function runToolLoop(request) {
6279
6521
  timing: timing2
6280
6522
  });
6281
6523
  stepCallLogger?.complete({
6282
- provider: "chatgpt",
6283
- model: request.model,
6284
- modelVersion,
6285
- step: turn,
6286
- usage: usageTokens,
6287
- costUsd: stepCostUsd,
6288
- responseChars: responseText.length,
6289
- thoughtChars: reasoningSummaryText.length,
6290
- toolCalls: 0,
6291
- 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
+ }
6292
6537
  });
6293
6538
  if (steeringItems2.length === 0) {
6294
6539
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6421,25 +6666,33 @@ async function runToolLoop(request) {
6421
6666
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6422
6667
  const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
6423
6668
  stepCallLogger?.complete({
6424
- provider: "chatgpt",
6425
- model: request.model,
6426
- modelVersion,
6427
- step: turn,
6428
- usage: usageTokens,
6429
- costUsd: stepCostUsd,
6430
- responseChars: responseText.length,
6431
- thoughtChars: reasoningSummaryText.length,
6432
- toolCalls: toolCalls.length,
6433
- 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
+ }
6434
6683
  });
6435
6684
  input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
6436
6685
  } catch (error) {
6437
6686
  stepCallLogger?.fail(error, {
6438
- provider: "chatgpt",
6439
- model: request.model,
6440
- modelVersion,
6441
- step: turn,
6442
- 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
+ }
6443
6696
  });
6444
6697
  throw error;
6445
6698
  }
@@ -6462,6 +6715,7 @@ async function runToolLoop(request) {
6462
6715
  let usageTokens;
6463
6716
  let responseText = "";
6464
6717
  let blocked = false;
6718
+ let stepToolCallText;
6465
6719
  const stepRequestPayload = {
6466
6720
  model: providerInfo.model,
6467
6721
  messages,
@@ -6526,6 +6780,14 @@ async function runToolLoop(request) {
6526
6780
  });
6527
6781
  }
6528
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
+ );
6529
6791
  if (responseToolCalls.length === 0) {
6530
6792
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6531
6793
  const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
@@ -6551,17 +6813,20 @@ async function runToolLoop(request) {
6551
6813
  timing: timing2
6552
6814
  });
6553
6815
  stepCallLogger?.complete({
6554
- provider: "fireworks",
6555
- model: request.model,
6556
- modelVersion,
6557
- step: turn,
6558
- usage: usageTokens,
6559
- costUsd: stepCostUsd,
6560
- blocked,
6561
- responseChars: responseText.length,
6562
- thoughtChars: 0,
6563
- toolCalls: 0,
6564
- 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
+ }
6565
6830
  });
6566
6831
  if (steeringMessages.length === 0) {
6567
6832
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -6673,17 +6938,21 @@ async function runToolLoop(request) {
6673
6938
  timing
6674
6939
  });
6675
6940
  stepCallLogger?.complete({
6676
- provider: "fireworks",
6677
- model: request.model,
6678
- modelVersion,
6679
- step: turn,
6680
- usage: usageTokens,
6681
- costUsd: stepCostUsd,
6682
- blocked,
6683
- responseChars: responseText.length,
6684
- thoughtChars: 0,
6685
- toolCalls: stepToolCalls.length,
6686
- 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
+ }
6687
6956
  });
6688
6957
  messages.push({
6689
6958
  role: "assistant",
@@ -6697,12 +6966,16 @@ async function runToolLoop(request) {
6697
6966
  }
6698
6967
  } catch (error) {
6699
6968
  stepCallLogger?.fail(error, {
6700
- provider: "fireworks",
6701
- model: request.model,
6702
- modelVersion,
6703
- step: turn,
6704
- usage: usageTokens,
6705
- 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
+ }
6706
6979
  });
6707
6980
  throw error;
6708
6981
  }
@@ -6722,6 +6995,7 @@ async function runToolLoop(request) {
6722
6995
  let usageTokens;
6723
6996
  let responseText = "";
6724
6997
  let thoughtsText = "";
6998
+ let stepToolCallText;
6725
6999
  const markFirstModelEvent = () => {
6726
7000
  if (firstModelEventAtMs === void 0) {
6727
7001
  firstModelEventAtMs = Date.now();
@@ -6842,12 +7116,17 @@ async function runToolLoop(request) {
6842
7116
  modelVersion = response.modelVersion ?? request.model;
6843
7117
  responseText = response.responseText.trim();
6844
7118
  thoughtsText = response.thoughtsText.trim();
7119
+ const responseOutputAttachments = collectLoggedAttachmentsFromGeminiParts(
7120
+ response.modelParts,
7121
+ "output"
7122
+ );
6845
7123
  const stepCostUsd = estimateCallCostUsd({
6846
7124
  modelId: modelVersion,
6847
7125
  tokens: usageTokens,
6848
7126
  responseImages: 0
6849
7127
  });
6850
7128
  totalCostUsd += stepCostUsd;
7129
+ stepToolCallText = serialiseGeminiToolCallsForLogging(response.functionCalls);
6851
7130
  if (response.functionCalls.length === 0) {
6852
7131
  const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
6853
7132
  finalText = responseText;
@@ -6873,16 +7152,20 @@ async function runToolLoop(request) {
6873
7152
  timing: timing2
6874
7153
  });
6875
7154
  stepCallLogger?.complete({
6876
- provider: "gemini",
6877
- model: request.model,
6878
- modelVersion,
6879
- step: turn,
6880
- usage: usageTokens,
6881
- costUsd: stepCostUsd,
6882
- responseChars: responseText.length,
6883
- thoughtChars: thoughtsText.length,
6884
- toolCalls: 0,
6885
- 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
+ }
6886
7169
  });
6887
7170
  if (steeringInput2.length === 0) {
6888
7171
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
@@ -7009,16 +7292,21 @@ async function runToolLoop(request) {
7009
7292
  timing
7010
7293
  });
7011
7294
  stepCallLogger?.complete({
7012
- provider: "gemini",
7013
- model: request.model,
7014
- modelVersion,
7015
- step: turn,
7016
- usage: usageTokens,
7017
- costUsd: stepCostUsd,
7018
- responseChars: responseText.length,
7019
- thoughtChars: thoughtsText.length,
7020
- toolCalls: toolCalls.length,
7021
- 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
+ }
7022
7310
  });
7023
7311
  geminiContents.push({ role: "user", parts: responseParts });
7024
7312
  const steeringInput = steeringInternal?.drainPendingContents() ?? [];
@@ -7027,13 +7315,17 @@ async function runToolLoop(request) {
7027
7315
  }
7028
7316
  } catch (error) {
7029
7317
  stepCallLogger?.fail(error, {
7030
- provider: "gemini",
7031
- model: request.model,
7032
- modelVersion,
7033
- step: turn,
7034
- usage: usageTokens,
7035
- responseChars: responseText.length,
7036
- 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
+ }
7037
7329
  });
7038
7330
  throw error;
7039
7331
  }