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