@jaypie/llm 1.3.6 → 1.3.8

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.
@@ -490,6 +490,18 @@ var LlmResponseStatus;
490
490
  LlmResponseStatus["Incomplete"] = "incomplete";
491
491
  LlmResponseStatus["InProgress"] = "in_progress";
492
492
  })(LlmResponseStatus || (LlmResponseStatus = {}));
493
+ // Progress
494
+ exports.LlmProgressEventType = void 0;
495
+ (function (LlmProgressEventType) {
496
+ LlmProgressEventType["Done"] = "done";
497
+ LlmProgressEventType["ModelRequest"] = "model_request";
498
+ LlmProgressEventType["ModelResponse"] = "model_response";
499
+ LlmProgressEventType["Retry"] = "retry";
500
+ LlmProgressEventType["Start"] = "start";
501
+ LlmProgressEventType["ToolCall"] = "tool_call";
502
+ LlmProgressEventType["ToolError"] = "tool_error";
503
+ LlmProgressEventType["ToolResult"] = "tool_result";
504
+ })(exports.LlmProgressEventType || (exports.LlmProgressEventType = {}));
493
505
 
494
506
  //
495
507
  //
@@ -5408,11 +5420,7 @@ class Toolkit {
5408
5420
  return toolCopy;
5409
5421
  });
5410
5422
  }
5411
- async call({ name, arguments: args }) {
5412
- const tool = this._tools.find((t) => t.name === name);
5413
- if (!tool) {
5414
- throw new Error(`Tool '${name}' not found`);
5415
- }
5423
+ parseArgs(args) {
5416
5424
  let parsedArgs;
5417
5425
  try {
5418
5426
  parsedArgs = JSON.parse(args);
@@ -5423,35 +5431,53 @@ class Toolkit {
5423
5431
  catch {
5424
5432
  parsedArgs = args;
5425
5433
  }
5434
+ return parsedArgs;
5435
+ }
5436
+ /**
5437
+ * Resolve a tool's `message` (static string or function of args) without
5438
+ * calling the tool. Returns undefined when the tool is missing or defines
5439
+ * no message. Never throws; resolution errors log at warn.
5440
+ */
5441
+ async resolveMessage({ name, arguments: args, }) {
5442
+ const tool = this._tools.find((t) => t.name === name);
5443
+ if (!tool || !tool.message) {
5444
+ return undefined;
5445
+ }
5446
+ try {
5447
+ if (typeof tool.message === "string") {
5448
+ return tool.message;
5449
+ }
5450
+ const parsedArgs = this.parseArgs(args);
5451
+ if (typeof tool.message === "function") {
5452
+ return await kit.resolveValue(tool.message(parsedArgs, { name }));
5453
+ }
5454
+ log.warn("[Toolkit] Tool provided unknown message type");
5455
+ return String(tool.message);
5456
+ }
5457
+ catch (error) {
5458
+ log.warn("[Toolkit] Caught error resolving tool message");
5459
+ log.var({ error });
5460
+ return undefined;
5461
+ }
5462
+ }
5463
+ async call({ name, arguments: args, message: resolvedMessage, }) {
5464
+ const tool = this._tools.find((t) => t.name === name);
5465
+ if (!tool) {
5466
+ throw new Error(`Tool '${name}' not found`);
5467
+ }
5468
+ const parsedArgs = this.parseArgs(args);
5426
5469
  if (this.log !== false) {
5427
5470
  try {
5428
5471
  const context = {
5429
5472
  name,
5430
5473
  args: parsedArgs,
5431
5474
  };
5432
- let message;
5433
5475
  if (this.explain) {
5434
5476
  context.explanation = parsedArgs.__Explanation;
5435
5477
  }
5436
- if (tool.message) {
5437
- if (typeof tool.message === "string") {
5438
- log.trace("[Toolkit] Tool provided string message");
5439
- message = tool.message;
5440
- }
5441
- else if (typeof tool.message === "function") {
5442
- log.trace("[Toolkit] Tool provided function message");
5443
- log.trace("[Toolkit] Resolving message result");
5444
- message = await kit.resolveValue(tool.message(parsedArgs, { name }));
5445
- }
5446
- else {
5447
- log.warn("[Toolkit] Tool provided unknown message type");
5448
- message = String(tool.message);
5449
- }
5450
- }
5451
- else {
5452
- log.trace("[Toolkit] Log tool call with default message");
5453
- message = `${tool.name}:${JSON.stringify(parsedArgs)}`;
5454
- }
5478
+ const message = resolvedMessage ??
5479
+ (await this.resolveMessage({ arguments: args, name })) ??
5480
+ `${tool.name}:${JSON.stringify(parsedArgs)}`;
5455
5481
  if (typeof this.log === "function") {
5456
5482
  log.trace("[Toolkit] Log tool call with custom logger");
5457
5483
  await kit.resolveValue(this.log(message, context));
@@ -6133,6 +6159,25 @@ class InputProcessor {
6133
6159
  // Export singleton instance for convenience
6134
6160
  const inputProcessor = new InputProcessor();
6135
6161
 
6162
+ /**
6163
+ * Deliver a progress event to the caller's onProgress callback.
6164
+ * Errors thrown by the callback are logged and swallowed — progress
6165
+ * reporting must never interrupt the operate loop.
6166
+ */
6167
+ async function emitProgress({ event, onProgress, }) {
6168
+ if (!onProgress) {
6169
+ return;
6170
+ }
6171
+ try {
6172
+ await onProgress(event);
6173
+ }
6174
+ catch (error) {
6175
+ const log = getLogger$6();
6176
+ log.warn(`[operate] onProgress callback threw on "${event.type}" event`);
6177
+ log.var({ error });
6178
+ }
6179
+ }
6180
+
6136
6181
  //
6137
6182
  //
6138
6183
  // Main
@@ -6558,12 +6603,21 @@ class OperateLoop {
6558
6603
  const log = getLogger$6();
6559
6604
  // Log what was passed to operate
6560
6605
  log.trace("[operate] Starting operate loop");
6561
- log.var({ "operate.input": input });
6562
- log.var({ "operate.options": options });
6606
+ log.trace.var({ "operate.input": input });
6607
+ log.trace.var({ "operate.options": options });
6563
6608
  // Initialize state
6564
6609
  const state = await this.initializeState(input, options);
6565
6610
  const context = this.createContext(options);
6566
6611
  const modelName = options.model ?? this.adapter.defaultModel;
6612
+ await emitProgress({
6613
+ event: {
6614
+ maxTurns: state.maxTurns,
6615
+ model: modelName,
6616
+ provider: this.adapter.name,
6617
+ type: exports.LlmProgressEventType.Start,
6618
+ },
6619
+ onProgress: options.onProgress,
6620
+ });
6567
6621
  // Enclosing LLM Observability span (no-op when DD_LLMOBS_ENABLED is unset).
6568
6622
  // Child llm/tool spans nest under it via the SDK's active-span context.
6569
6623
  return withLlmObsSpan({
@@ -6601,6 +6655,15 @@ class OperateLoop {
6601
6655
  metrics: usageToLlmObsMetrics(response.usage),
6602
6656
  outputData: response.content,
6603
6657
  });
6658
+ await emitProgress({
6659
+ event: {
6660
+ content: response.content,
6661
+ turn: state.currentTurn,
6662
+ type: exports.LlmProgressEventType.Done,
6663
+ usage: response.usage,
6664
+ },
6665
+ onProgress: options.onProgress,
6666
+ });
6604
6667
  return response;
6605
6668
  });
6606
6669
  }
@@ -6695,18 +6758,54 @@ class OperateLoop {
6695
6758
  });
6696
6759
  // Build provider-specific request
6697
6760
  const providerRequest = this.adapter.buildRequest(request);
6698
- // Log what was passed to the model
6761
+ // Log a draconian subset of the request; the full payload is available
6762
+ // via the beforeEachModelRequest hook
6699
6763
  log.trace("[operate] Calling model");
6700
- log.var({ "operate.request": providerRequest });
6764
+ log.trace.var({
6765
+ "operate.request": {
6766
+ latest: this.summarizeHistoryItem(request.messages[request.messages.length - 1]),
6767
+ messages: request.messages.length,
6768
+ model: request.model,
6769
+ turn: state.currentTurn,
6770
+ },
6771
+ });
6772
+ await emitProgress({
6773
+ event: {
6774
+ model: request.model,
6775
+ turn: state.currentTurn,
6776
+ type: exports.LlmProgressEventType.ModelRequest,
6777
+ },
6778
+ onProgress: options.onProgress,
6779
+ });
6701
6780
  // Execute beforeEachModelRequest hook
6702
6781
  await this.hookRunnerInstance.runBeforeModelRequest(context.hooks, {
6703
6782
  input: state.currentInput,
6704
6783
  options,
6705
6784
  providerRequest,
6706
6785
  });
6786
+ // Emit retry progress alongside the caller's onRetryableModelError hook
6787
+ const hooks = context.hooks;
6788
+ const hooksWithProgress = options.onProgress
6789
+ ? {
6790
+ ...hooks,
6791
+ onRetryableModelError: async (retryContext) => {
6792
+ await emitProgress({
6793
+ event: {
6794
+ error: retryContext.error instanceof Error
6795
+ ? retryContext.error.message
6796
+ : String(retryContext.error),
6797
+ turn: state.currentTurn,
6798
+ type: exports.LlmProgressEventType.Retry,
6799
+ },
6800
+ onProgress: options.onProgress,
6801
+ });
6802
+ return hooks?.onRetryableModelError?.(retryContext);
6803
+ },
6804
+ }
6805
+ : hooks;
6707
6806
  // Execute with retry inside a child llm span (no-op when llmobs disabled).
6708
6807
  // RetryExecutor handles error hooks and throws appropriate errors.
6709
- const { parsed, response } = await withLlmObsSpan({
6808
+ const { parsed, response, toolCalls } = await withLlmObsSpan({
6710
6809
  kind: "llm",
6711
6810
  modelName: options.model ?? this.adapter.defaultModel,
6712
6811
  modelProvider: this.adapter.name,
@@ -6718,19 +6817,43 @@ class OperateLoop {
6718
6817
  options,
6719
6818
  providerRequest,
6720
6819
  },
6721
- hooks: context.hooks,
6820
+ hooks: hooksWithProgress,
6722
6821
  });
6723
- // Log what was returned from the model
6724
- log.trace("[operate] Model response received");
6725
- log.var({ "operate.response": response });
6726
6822
  // Parse response
6727
6823
  const parsed = this.adapter.parseResponse(response, options);
6824
+ const toolCalls = parsed.hasToolCalls
6825
+ ? this.adapter.extractToolCalls(response)
6826
+ : [];
6827
+ // Log only the text or tool calls; the full payload is available
6828
+ // via the afterEachModelResponse hook
6829
+ log.trace("[operate] Model response received");
6830
+ log.trace.var({
6831
+ "operate.response": toolCalls.length > 0
6832
+ ? toolCalls.map(({ arguments: args, name }) => ({
6833
+ arguments: args,
6834
+ name,
6835
+ }))
6836
+ : parsed.content,
6837
+ });
6728
6838
  annotateLlmObs({
6729
6839
  inputData: state.currentInput,
6730
6840
  metrics: usageToLlmObsMetrics(parsed.usage ? [parsed.usage] : undefined),
6731
6841
  outputData: parsed.content ?? "",
6732
6842
  });
6733
- return { parsed, response };
6843
+ return { parsed, response, toolCalls };
6844
+ });
6845
+ await emitProgress({
6846
+ event: {
6847
+ content: parsed.content,
6848
+ toolCalls: toolCalls.map(({ arguments: args, name }) => ({
6849
+ arguments: args,
6850
+ name,
6851
+ })),
6852
+ turn: state.currentTurn,
6853
+ type: exports.LlmProgressEventType.ModelResponse,
6854
+ usage: parsed.usage ? [parsed.usage] : undefined,
6855
+ },
6856
+ onProgress: options.onProgress,
6734
6857
  });
6735
6858
  // Track usage
6736
6859
  if (parsed.usage) {
@@ -6759,7 +6882,6 @@ class OperateLoop {
6759
6882
  }
6760
6883
  // Handle tool calls
6761
6884
  if (parsed.hasToolCalls) {
6762
- const toolCalls = this.adapter.extractToolCalls(response);
6763
6885
  if (toolCalls.length > 0 && state.toolkit && state.maxTurns > 1) {
6764
6886
  // Track updated provider request for tool results
6765
6887
  let currentProviderRequest = providerRequest;
@@ -6770,10 +6892,28 @@ class OperateLoop {
6770
6892
  this.appendResponseItemsToRequest(currentProviderRequest, responseItems);
6771
6893
  // Process each tool call
6772
6894
  for (const toolCall of toolCalls) {
6895
+ // Resolved once per call; never throws (undefined when tool has no message)
6896
+ const toolMessage = await state.toolkit.resolveMessage({
6897
+ arguments: toolCall.arguments,
6898
+ name: toolCall.name,
6899
+ });
6773
6900
  try {
6901
+ await emitProgress({
6902
+ event: {
6903
+ tool: {
6904
+ arguments: toolCall.arguments,
6905
+ message: toolMessage,
6906
+ name: toolCall.name,
6907
+ },
6908
+ turn: state.currentTurn,
6909
+ type: exports.LlmProgressEventType.ToolCall,
6910
+ },
6911
+ onProgress: options.onProgress,
6912
+ });
6774
6913
  // Execute beforeEachTool hook
6775
6914
  await this.hookRunnerInstance.runBeforeTool(context.hooks, {
6776
6915
  args: toolCall.arguments,
6916
+ message: toolMessage,
6777
6917
  toolName: toolCall.name,
6778
6918
  });
6779
6919
  // Call the tool inside a child tool span (no-op when disabled)
@@ -6781,6 +6921,7 @@ class OperateLoop {
6781
6921
  const result = await withLlmObsSpan({ kind: "tool", name: toolCall.name }, async () => {
6782
6922
  const result = await state.toolkit.call({
6783
6923
  arguments: toolCall.arguments,
6924
+ message: toolMessage,
6784
6925
  name: toolCall.name,
6785
6926
  });
6786
6927
  annotateLlmObs({
@@ -6790,9 +6931,18 @@ class OperateLoop {
6790
6931
  });
6791
6932
  return result;
6792
6933
  });
6934
+ await emitProgress({
6935
+ event: {
6936
+ tool: { name: toolCall.name },
6937
+ turn: state.currentTurn,
6938
+ type: exports.LlmProgressEventType.ToolResult,
6939
+ },
6940
+ onProgress: options.onProgress,
6941
+ });
6793
6942
  // Execute afterEachTool hook
6794
6943
  await this.hookRunnerInstance.runAfterTool(context.hooks, {
6795
6944
  args: toolCall.arguments,
6945
+ message: toolMessage,
6796
6946
  result,
6797
6947
  toolName: toolCall.name,
6798
6948
  });
@@ -6813,10 +6963,20 @@ class OperateLoop {
6813
6963
  state.responseBuilder.appendToHistory(toolResultFormatted);
6814
6964
  }
6815
6965
  catch (error) {
6966
+ await emitProgress({
6967
+ event: {
6968
+ error: error.message,
6969
+ tool: { name: toolCall.name },
6970
+ turn: state.currentTurn,
6971
+ type: exports.LlmProgressEventType.ToolError,
6972
+ },
6973
+ onProgress: options.onProgress,
6974
+ });
6816
6975
  // Execute onToolError hook
6817
6976
  await this.hookRunnerInstance.runOnToolError(context.hooks, {
6818
6977
  args: toolCall.arguments,
6819
6978
  error: error,
6979
+ message: toolMessage,
6820
6980
  toolName: toolCall.name,
6821
6981
  });
6822
6982
  // Set error on response
@@ -6886,6 +7046,20 @@ class OperateLoop {
6886
7046
  }
6887
7047
  return false; // Stop loop
6888
7048
  }
7049
+ /**
7050
+ * Draconian summary of a history item for trace logging: string message
7051
+ * content is kept; everything else is reduced to its type.
7052
+ */
7053
+ summarizeHistoryItem(item) {
7054
+ if (!item) {
7055
+ return undefined;
7056
+ }
7057
+ const record = item;
7058
+ if (typeof record.content === "string") {
7059
+ return { content: record.content, role: record.role };
7060
+ }
7061
+ return { type: record.type ?? "message" };
7062
+ }
6889
7063
  /**
6890
7064
  * Reconcile structured output against the declared `format` contract. A
6891
7065
  * declared `format` is a schema contract: returned keys should match the
@@ -7313,10 +7487,16 @@ class StreamLoop {
7313
7487
  async *processToolCalls(toolCalls, state, context) {
7314
7488
  const log = getLogger$6();
7315
7489
  for (const toolCall of toolCalls) {
7490
+ // Resolved once per call; never throws (undefined when tool has no message)
7491
+ const toolMessage = await state.toolkit.resolveMessage({
7492
+ arguments: toolCall.arguments,
7493
+ name: toolCall.name,
7494
+ });
7316
7495
  try {
7317
7496
  // Execute beforeEachTool hook
7318
7497
  await this.hookRunnerInstance.runBeforeTool(context.hooks, {
7319
7498
  args: toolCall.arguments,
7499
+ message: toolMessage,
7320
7500
  toolName: toolCall.name,
7321
7501
  });
7322
7502
  // Call the tool inside a child tool span (no-op when disabled)
@@ -7324,6 +7504,7 @@ class StreamLoop {
7324
7504
  const result = await withLlmObsSpan({ kind: "tool", name: toolCall.name }, async () => {
7325
7505
  const result = await state.toolkit.call({
7326
7506
  arguments: toolCall.arguments,
7507
+ message: toolMessage,
7327
7508
  name: toolCall.name,
7328
7509
  });
7329
7510
  annotateLlmObs({
@@ -7336,6 +7517,7 @@ class StreamLoop {
7336
7517
  // Execute afterEachTool hook
7337
7518
  await this.hookRunnerInstance.runAfterTool(context.hooks, {
7338
7519
  args: toolCall.arguments,
7520
+ message: toolMessage,
7339
7521
  result,
7340
7522
  toolName: toolCall.name,
7341
7523
  });
@@ -7363,6 +7545,7 @@ class StreamLoop {
7363
7545
  await this.hookRunnerInstance.runOnToolError(context.hooks, {
7364
7546
  args: toolCall.arguments,
7365
7547
  error: error,
7548
+ message: toolMessage,
7366
7549
  toolName: toolCall.name,
7367
7550
  });
7368
7551
  // Yield error chunk