@jaypie/llm 1.3.6 → 1.3.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.
@@ -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
  //
@@ -6133,6 +6145,25 @@ class InputProcessor {
6133
6145
  // Export singleton instance for convenience
6134
6146
  const inputProcessor = new InputProcessor();
6135
6147
 
6148
+ /**
6149
+ * Deliver a progress event to the caller's onProgress callback.
6150
+ * Errors thrown by the callback are logged and swallowed — progress
6151
+ * reporting must never interrupt the operate loop.
6152
+ */
6153
+ async function emitProgress({ event, onProgress, }) {
6154
+ if (!onProgress) {
6155
+ return;
6156
+ }
6157
+ try {
6158
+ await onProgress(event);
6159
+ }
6160
+ catch (error) {
6161
+ const log = getLogger$6();
6162
+ log.warn(`[operate] onProgress callback threw on "${event.type}" event`);
6163
+ log.var({ error });
6164
+ }
6165
+ }
6166
+
6136
6167
  //
6137
6168
  //
6138
6169
  // Main
@@ -6558,12 +6589,21 @@ class OperateLoop {
6558
6589
  const log = getLogger$6();
6559
6590
  // Log what was passed to operate
6560
6591
  log.trace("[operate] Starting operate loop");
6561
- log.var({ "operate.input": input });
6562
- log.var({ "operate.options": options });
6592
+ log.trace.var({ "operate.input": input });
6593
+ log.trace.var({ "operate.options": options });
6563
6594
  // Initialize state
6564
6595
  const state = await this.initializeState(input, options);
6565
6596
  const context = this.createContext(options);
6566
6597
  const modelName = options.model ?? this.adapter.defaultModel;
6598
+ await emitProgress({
6599
+ event: {
6600
+ maxTurns: state.maxTurns,
6601
+ model: modelName,
6602
+ provider: this.adapter.name,
6603
+ type: exports.LlmProgressEventType.Start,
6604
+ },
6605
+ onProgress: options.onProgress,
6606
+ });
6567
6607
  // Enclosing LLM Observability span (no-op when DD_LLMOBS_ENABLED is unset).
6568
6608
  // Child llm/tool spans nest under it via the SDK's active-span context.
6569
6609
  return withLlmObsSpan({
@@ -6601,6 +6641,15 @@ class OperateLoop {
6601
6641
  metrics: usageToLlmObsMetrics(response.usage),
6602
6642
  outputData: response.content,
6603
6643
  });
6644
+ await emitProgress({
6645
+ event: {
6646
+ content: response.content,
6647
+ turn: state.currentTurn,
6648
+ type: exports.LlmProgressEventType.Done,
6649
+ usage: response.usage,
6650
+ },
6651
+ onProgress: options.onProgress,
6652
+ });
6604
6653
  return response;
6605
6654
  });
6606
6655
  }
@@ -6695,18 +6744,54 @@ class OperateLoop {
6695
6744
  });
6696
6745
  // Build provider-specific request
6697
6746
  const providerRequest = this.adapter.buildRequest(request);
6698
- // Log what was passed to the model
6747
+ // Log a draconian subset of the request; the full payload is available
6748
+ // via the beforeEachModelRequest hook
6699
6749
  log.trace("[operate] Calling model");
6700
- log.var({ "operate.request": providerRequest });
6750
+ log.trace.var({
6751
+ "operate.request": {
6752
+ latest: this.summarizeHistoryItem(request.messages[request.messages.length - 1]),
6753
+ messages: request.messages.length,
6754
+ model: request.model,
6755
+ turn: state.currentTurn,
6756
+ },
6757
+ });
6758
+ await emitProgress({
6759
+ event: {
6760
+ model: request.model,
6761
+ turn: state.currentTurn,
6762
+ type: exports.LlmProgressEventType.ModelRequest,
6763
+ },
6764
+ onProgress: options.onProgress,
6765
+ });
6701
6766
  // Execute beforeEachModelRequest hook
6702
6767
  await this.hookRunnerInstance.runBeforeModelRequest(context.hooks, {
6703
6768
  input: state.currentInput,
6704
6769
  options,
6705
6770
  providerRequest,
6706
6771
  });
6772
+ // Emit retry progress alongside the caller's onRetryableModelError hook
6773
+ const hooks = context.hooks;
6774
+ const hooksWithProgress = options.onProgress
6775
+ ? {
6776
+ ...hooks,
6777
+ onRetryableModelError: async (retryContext) => {
6778
+ await emitProgress({
6779
+ event: {
6780
+ error: retryContext.error instanceof Error
6781
+ ? retryContext.error.message
6782
+ : String(retryContext.error),
6783
+ turn: state.currentTurn,
6784
+ type: exports.LlmProgressEventType.Retry,
6785
+ },
6786
+ onProgress: options.onProgress,
6787
+ });
6788
+ return hooks?.onRetryableModelError?.(retryContext);
6789
+ },
6790
+ }
6791
+ : hooks;
6707
6792
  // Execute with retry inside a child llm span (no-op when llmobs disabled).
6708
6793
  // RetryExecutor handles error hooks and throws appropriate errors.
6709
- const { parsed, response } = await withLlmObsSpan({
6794
+ const { parsed, response, toolCalls } = await withLlmObsSpan({
6710
6795
  kind: "llm",
6711
6796
  modelName: options.model ?? this.adapter.defaultModel,
6712
6797
  modelProvider: this.adapter.name,
@@ -6718,19 +6803,43 @@ class OperateLoop {
6718
6803
  options,
6719
6804
  providerRequest,
6720
6805
  },
6721
- hooks: context.hooks,
6806
+ hooks: hooksWithProgress,
6722
6807
  });
6723
- // Log what was returned from the model
6724
- log.trace("[operate] Model response received");
6725
- log.var({ "operate.response": response });
6726
6808
  // Parse response
6727
6809
  const parsed = this.adapter.parseResponse(response, options);
6810
+ const toolCalls = parsed.hasToolCalls
6811
+ ? this.adapter.extractToolCalls(response)
6812
+ : [];
6813
+ // Log only the text or tool calls; the full payload is available
6814
+ // via the afterEachModelResponse hook
6815
+ log.trace("[operate] Model response received");
6816
+ log.trace.var({
6817
+ "operate.response": toolCalls.length > 0
6818
+ ? toolCalls.map(({ arguments: args, name }) => ({
6819
+ arguments: args,
6820
+ name,
6821
+ }))
6822
+ : parsed.content,
6823
+ });
6728
6824
  annotateLlmObs({
6729
6825
  inputData: state.currentInput,
6730
6826
  metrics: usageToLlmObsMetrics(parsed.usage ? [parsed.usage] : undefined),
6731
6827
  outputData: parsed.content ?? "",
6732
6828
  });
6733
- return { parsed, response };
6829
+ return { parsed, response, toolCalls };
6830
+ });
6831
+ await emitProgress({
6832
+ event: {
6833
+ content: parsed.content,
6834
+ toolCalls: toolCalls.map(({ arguments: args, name }) => ({
6835
+ arguments: args,
6836
+ name,
6837
+ })),
6838
+ turn: state.currentTurn,
6839
+ type: exports.LlmProgressEventType.ModelResponse,
6840
+ usage: parsed.usage ? [parsed.usage] : undefined,
6841
+ },
6842
+ onProgress: options.onProgress,
6734
6843
  });
6735
6844
  // Track usage
6736
6845
  if (parsed.usage) {
@@ -6759,7 +6868,6 @@ class OperateLoop {
6759
6868
  }
6760
6869
  // Handle tool calls
6761
6870
  if (parsed.hasToolCalls) {
6762
- const toolCalls = this.adapter.extractToolCalls(response);
6763
6871
  if (toolCalls.length > 0 && state.toolkit && state.maxTurns > 1) {
6764
6872
  // Track updated provider request for tool results
6765
6873
  let currentProviderRequest = providerRequest;
@@ -6771,6 +6879,14 @@ class OperateLoop {
6771
6879
  // Process each tool call
6772
6880
  for (const toolCall of toolCalls) {
6773
6881
  try {
6882
+ await emitProgress({
6883
+ event: {
6884
+ tool: { arguments: toolCall.arguments, name: toolCall.name },
6885
+ turn: state.currentTurn,
6886
+ type: exports.LlmProgressEventType.ToolCall,
6887
+ },
6888
+ onProgress: options.onProgress,
6889
+ });
6774
6890
  // Execute beforeEachTool hook
6775
6891
  await this.hookRunnerInstance.runBeforeTool(context.hooks, {
6776
6892
  args: toolCall.arguments,
@@ -6790,6 +6906,14 @@ class OperateLoop {
6790
6906
  });
6791
6907
  return result;
6792
6908
  });
6909
+ await emitProgress({
6910
+ event: {
6911
+ tool: { name: toolCall.name },
6912
+ turn: state.currentTurn,
6913
+ type: exports.LlmProgressEventType.ToolResult,
6914
+ },
6915
+ onProgress: options.onProgress,
6916
+ });
6793
6917
  // Execute afterEachTool hook
6794
6918
  await this.hookRunnerInstance.runAfterTool(context.hooks, {
6795
6919
  args: toolCall.arguments,
@@ -6813,6 +6937,15 @@ class OperateLoop {
6813
6937
  state.responseBuilder.appendToHistory(toolResultFormatted);
6814
6938
  }
6815
6939
  catch (error) {
6940
+ await emitProgress({
6941
+ event: {
6942
+ error: error.message,
6943
+ tool: { name: toolCall.name },
6944
+ turn: state.currentTurn,
6945
+ type: exports.LlmProgressEventType.ToolError,
6946
+ },
6947
+ onProgress: options.onProgress,
6948
+ });
6816
6949
  // Execute onToolError hook
6817
6950
  await this.hookRunnerInstance.runOnToolError(context.hooks, {
6818
6951
  args: toolCall.arguments,
@@ -6886,6 +7019,20 @@ class OperateLoop {
6886
7019
  }
6887
7020
  return false; // Stop loop
6888
7021
  }
7022
+ /**
7023
+ * Draconian summary of a history item for trace logging: string message
7024
+ * content is kept; everything else is reduced to its type.
7025
+ */
7026
+ summarizeHistoryItem(item) {
7027
+ if (!item) {
7028
+ return undefined;
7029
+ }
7030
+ const record = item;
7031
+ if (typeof record.content === "string") {
7032
+ return { content: record.content, role: record.role };
7033
+ }
7034
+ return { type: record.type ?? "message" };
7035
+ }
6889
7036
  /**
6890
7037
  * Reconcile structured output against the declared `format` contract. A
6891
7038
  * declared `format` is a schema contract: returned keys should match the