@jaypie/llm 1.3.5 → 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.
package/dist/esm/index.js CHANGED
@@ -98,7 +98,10 @@ const PROVIDER = {
98
98
  ANTHROPIC: {
99
99
  // https://docs.anthropic.com/en/docs/about-claude/models/overview
100
100
  MAX_TOKENS: {
101
- DEFAULT: 4096,
101
+ // Non-streaming ceiling: responses above ~16K output tokens risk HTTP
102
+ // timeouts; streaming requests resolve to the model maximum instead
103
+ // (see util/maxOutputTokens.ts)
104
+ DEFAULT: 16384,
102
105
  },
103
106
  MODEL: {
104
107
  DEFAULT: FIRST_CLASS_PROVIDER.ANTHROPIC.DEFAULT,
@@ -447,14 +450,12 @@ class BaseProviderAdapter {
447
450
  /**
448
451
  * Default implementation returns false - override for providers with native structured output
449
452
  */
450
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
451
453
  hasStructuredOutput(_response) {
452
454
  return false;
453
455
  }
454
456
  /**
455
457
  * Default implementation returns undefined - override for providers with native structured output
456
458
  */
457
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
458
459
  extractStructuredOutput(_response) {
459
460
  return undefined;
460
461
  }
@@ -486,6 +487,18 @@ var LlmResponseStatus;
486
487
  LlmResponseStatus["Incomplete"] = "incomplete";
487
488
  LlmResponseStatus["InProgress"] = "in_progress";
488
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 = {}));
489
502
 
490
503
  //
491
504
  //
@@ -922,6 +935,52 @@ function jsonSchemaToOpenApi3(schema) {
922
935
 
923
936
  const getLogger$6 = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
924
937
 
938
+ //
939
+ //
940
+ // Constants
941
+ //
942
+ // Non-streaming requests above ~16K output tokens risk HTTP timeouts
943
+ // (Anthropic guidance is to stream anything larger), so non-streaming
944
+ // defaults are capped here and only streaming requests resolve to the
945
+ // full model maximum.
946
+ const NON_STREAMING_MAX_TOKENS = PROVIDER.ANTHROPIC.MAX_TOKENS.DEFAULT;
947
+ // Maximum output tokens by model; first match wins.
948
+ // Providers without low output ceilings (OpenAI, xAI) are intentionally
949
+ // absent — their requests leave the limit unset.
950
+ const MODEL_MAX_OUTPUT_TOKENS = [
951
+ // Anthropic — https://platform.claude.com/docs/en/about-claude/models/overview
952
+ { pattern: /^claude-opus-4-(0$|1$|1-|2025)/, tokens: 32000 },
953
+ { pattern: /^claude-opus-4-5/, tokens: 64000 },
954
+ { pattern: /^claude-sonnet-4-(0$|5$|5-|2025)/, tokens: 64000 },
955
+ { pattern: /haiku/, tokens: 64000 },
956
+ { pattern: /claude|fable|mythos|opus|sonnet/, tokens: 128000 },
957
+ // Google — https://ai.google.dev/gemini-api/docs/models
958
+ { pattern: /gemini-(2\.5|3)/, tokens: 65536 },
959
+ { pattern: /gemini/, tokens: 8192 },
960
+ ];
961
+ //
962
+ //
963
+ // Main
964
+ //
965
+ /**
966
+ * Maximum output tokens the model supports, or undefined when unknown.
967
+ */
968
+ function maxOutputTokens(model) {
969
+ const match = MODEL_MAX_OUTPUT_TOKENS.find(({ pattern }) => pattern.test(model));
970
+ return match?.tokens;
971
+ }
972
+ /**
973
+ * Default output token limit for a request: the model maximum when
974
+ * streaming, capped at the non-streaming maximum otherwise. Returns
975
+ * undefined when the model's maximum is unknown.
976
+ */
977
+ function resolveMaxOutputTokens(model, { stream = false } = {}) {
978
+ const max = maxOutputTokens(model);
979
+ if (max === undefined)
980
+ return undefined;
981
+ return stream ? max : Math.min(max, NON_STREAMING_MAX_TOKENS);
982
+ }
983
+
925
984
  // Turn policy constants
926
985
  const MAX_TURNS_ABSOLUTE_LIMIT = 72;
927
986
  const MAX_TURNS_DEFAULT_LIMIT = 24;
@@ -1746,11 +1805,13 @@ class AnthropicAdapter extends BaseProviderAdapter {
1746
1805
  lastMsg.content = lastMsg.content + "\n\n" + request.instructions;
1747
1806
  }
1748
1807
  }
1808
+ const model = (request.model ||
1809
+ this.defaultModel);
1749
1810
  const anthropicRequest = {
1750
- model: (request.model ||
1751
- this.defaultModel),
1811
+ model,
1752
1812
  messages,
1753
- max_tokens: PROVIDER.ANTHROPIC.MAX_TOKENS.DEFAULT,
1813
+ max_tokens: resolveMaxOutputTokens(model, { stream: request.stream }) ??
1814
+ PROVIDER.ANTHROPIC.MAX_TOKENS.DEFAULT,
1754
1815
  stream: false,
1755
1816
  };
1756
1817
  if (request.system) {
@@ -1813,7 +1874,6 @@ class AnthropicAdapter extends BaseProviderAdapter {
1813
1874
  // outputSchema is part of the interface contract but Anthropic now uses
1814
1875
  // native `output_format` (set in buildRequest), so we no longer inject a
1815
1876
  // synthetic structured-output tool here.
1816
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1817
1877
  _outputSchema) {
1818
1878
  return toolkit.tools.map((tool) => ({
1819
1879
  name: tool.name,
@@ -3000,6 +3060,18 @@ class GoogleAdapter extends BaseProviderAdapter {
3000
3060
  : structuredOutputInstruction,
3001
3061
  };
3002
3062
  }
3063
+ // Default output ceiling: Google's own default (8,192) silently
3064
+ // truncates long generations, so resolve to the model maximum (capped
3065
+ // for non-streaming); providerOptions below override
3066
+ const maxOutputTokens = resolveMaxOutputTokens(geminiRequest.model, {
3067
+ stream: request.stream,
3068
+ });
3069
+ if (maxOutputTokens !== undefined) {
3070
+ geminiRequest.config = {
3071
+ ...geminiRequest.config,
3072
+ maxOutputTokens,
3073
+ };
3074
+ }
3003
3075
  // Add provider-specific options
3004
3076
  if (request.providerOptions) {
3005
3077
  geminiRequest.config = {
@@ -3021,7 +3093,6 @@ class GoogleAdapter extends BaseProviderAdapter {
3021
3093
  // structured output via `responseJsonSchema`/`responseSchema` (or the
3022
3094
  // legacy fake-tool injected in buildRequest as a fallback). We no longer
3023
3095
  // inject a synthetic structured-output tool here.
3024
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3025
3096
  _outputSchema) {
3026
3097
  return toolkit.tools.map((tool) => ({
3027
3098
  name: tool.name,
@@ -4686,7 +4757,6 @@ class OpenRouterAdapter extends BaseProviderAdapter {
4686
4757
  // native `response_format` (set in buildRequest), so we no longer inject a
4687
4758
  // synthetic structured-output tool here. The legacy fake-tool injection
4688
4759
  // happens in buildRequest only as a runtime fallback.
4689
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
4690
4760
  _outputSchema) {
4691
4761
  return toolkit.tools.map((tool) => ({
4692
4762
  name: tool.name,
@@ -6072,6 +6142,25 @@ class InputProcessor {
6072
6142
  // Export singleton instance for convenience
6073
6143
  const inputProcessor = new InputProcessor();
6074
6144
 
6145
+ /**
6146
+ * Deliver a progress event to the caller's onProgress callback.
6147
+ * Errors thrown by the callback are logged and swallowed — progress
6148
+ * reporting must never interrupt the operate loop.
6149
+ */
6150
+ async function emitProgress({ event, onProgress, }) {
6151
+ if (!onProgress) {
6152
+ return;
6153
+ }
6154
+ try {
6155
+ await onProgress(event);
6156
+ }
6157
+ catch (error) {
6158
+ const log = getLogger$6();
6159
+ log.warn(`[operate] onProgress callback threw on "${event.type}" event`);
6160
+ log.var({ error });
6161
+ }
6162
+ }
6163
+
6075
6164
  //
6076
6165
  //
6077
6166
  // Main
@@ -6497,12 +6586,21 @@ class OperateLoop {
6497
6586
  const log = getLogger$6();
6498
6587
  // Log what was passed to operate
6499
6588
  log.trace("[operate] Starting operate loop");
6500
- log.var({ "operate.input": input });
6501
- log.var({ "operate.options": options });
6589
+ log.trace.var({ "operate.input": input });
6590
+ log.trace.var({ "operate.options": options });
6502
6591
  // Initialize state
6503
6592
  const state = await this.initializeState(input, options);
6504
6593
  const context = this.createContext(options);
6505
6594
  const modelName = options.model ?? this.adapter.defaultModel;
6595
+ await emitProgress({
6596
+ event: {
6597
+ maxTurns: state.maxTurns,
6598
+ model: modelName,
6599
+ provider: this.adapter.name,
6600
+ type: LlmProgressEventType.Start,
6601
+ },
6602
+ onProgress: options.onProgress,
6603
+ });
6506
6604
  // Enclosing LLM Observability span (no-op when DD_LLMOBS_ENABLED is unset).
6507
6605
  // Child llm/tool spans nest under it via the SDK's active-span context.
6508
6606
  return withLlmObsSpan({
@@ -6540,6 +6638,15 @@ class OperateLoop {
6540
6638
  metrics: usageToLlmObsMetrics(response.usage),
6541
6639
  outputData: response.content,
6542
6640
  });
6641
+ await emitProgress({
6642
+ event: {
6643
+ content: response.content,
6644
+ turn: state.currentTurn,
6645
+ type: LlmProgressEventType.Done,
6646
+ usage: response.usage,
6647
+ },
6648
+ onProgress: options.onProgress,
6649
+ });
6543
6650
  return response;
6544
6651
  });
6545
6652
  }
@@ -6634,18 +6741,54 @@ class OperateLoop {
6634
6741
  });
6635
6742
  // Build provider-specific request
6636
6743
  const providerRequest = this.adapter.buildRequest(request);
6637
- // Log what was passed to the model
6744
+ // Log a draconian subset of the request; the full payload is available
6745
+ // via the beforeEachModelRequest hook
6638
6746
  log.trace("[operate] Calling model");
6639
- log.var({ "operate.request": providerRequest });
6747
+ log.trace.var({
6748
+ "operate.request": {
6749
+ latest: this.summarizeHistoryItem(request.messages[request.messages.length - 1]),
6750
+ messages: request.messages.length,
6751
+ model: request.model,
6752
+ turn: state.currentTurn,
6753
+ },
6754
+ });
6755
+ await emitProgress({
6756
+ event: {
6757
+ model: request.model,
6758
+ turn: state.currentTurn,
6759
+ type: LlmProgressEventType.ModelRequest,
6760
+ },
6761
+ onProgress: options.onProgress,
6762
+ });
6640
6763
  // Execute beforeEachModelRequest hook
6641
6764
  await this.hookRunnerInstance.runBeforeModelRequest(context.hooks, {
6642
6765
  input: state.currentInput,
6643
6766
  options,
6644
6767
  providerRequest,
6645
6768
  });
6769
+ // Emit retry progress alongside the caller's onRetryableModelError hook
6770
+ const hooks = context.hooks;
6771
+ const hooksWithProgress = options.onProgress
6772
+ ? {
6773
+ ...hooks,
6774
+ onRetryableModelError: async (retryContext) => {
6775
+ await emitProgress({
6776
+ event: {
6777
+ error: retryContext.error instanceof Error
6778
+ ? retryContext.error.message
6779
+ : String(retryContext.error),
6780
+ turn: state.currentTurn,
6781
+ type: LlmProgressEventType.Retry,
6782
+ },
6783
+ onProgress: options.onProgress,
6784
+ });
6785
+ return hooks?.onRetryableModelError?.(retryContext);
6786
+ },
6787
+ }
6788
+ : hooks;
6646
6789
  // Execute with retry inside a child llm span (no-op when llmobs disabled).
6647
6790
  // RetryExecutor handles error hooks and throws appropriate errors.
6648
- const { parsed, response } = await withLlmObsSpan({
6791
+ const { parsed, response, toolCalls } = await withLlmObsSpan({
6649
6792
  kind: "llm",
6650
6793
  modelName: options.model ?? this.adapter.defaultModel,
6651
6794
  modelProvider: this.adapter.name,
@@ -6657,19 +6800,43 @@ class OperateLoop {
6657
6800
  options,
6658
6801
  providerRequest,
6659
6802
  },
6660
- hooks: context.hooks,
6803
+ hooks: hooksWithProgress,
6661
6804
  });
6662
- // Log what was returned from the model
6663
- log.trace("[operate] Model response received");
6664
- log.var({ "operate.response": response });
6665
6805
  // Parse response
6666
6806
  const parsed = this.adapter.parseResponse(response, options);
6807
+ const toolCalls = parsed.hasToolCalls
6808
+ ? this.adapter.extractToolCalls(response)
6809
+ : [];
6810
+ // Log only the text or tool calls; the full payload is available
6811
+ // via the afterEachModelResponse hook
6812
+ log.trace("[operate] Model response received");
6813
+ log.trace.var({
6814
+ "operate.response": toolCalls.length > 0
6815
+ ? toolCalls.map(({ arguments: args, name }) => ({
6816
+ arguments: args,
6817
+ name,
6818
+ }))
6819
+ : parsed.content,
6820
+ });
6667
6821
  annotateLlmObs({
6668
6822
  inputData: state.currentInput,
6669
6823
  metrics: usageToLlmObsMetrics(parsed.usage ? [parsed.usage] : undefined),
6670
6824
  outputData: parsed.content ?? "",
6671
6825
  });
6672
- return { parsed, response };
6826
+ return { parsed, response, toolCalls };
6827
+ });
6828
+ await emitProgress({
6829
+ event: {
6830
+ content: parsed.content,
6831
+ toolCalls: toolCalls.map(({ arguments: args, name }) => ({
6832
+ arguments: args,
6833
+ name,
6834
+ })),
6835
+ turn: state.currentTurn,
6836
+ type: LlmProgressEventType.ModelResponse,
6837
+ usage: parsed.usage ? [parsed.usage] : undefined,
6838
+ },
6839
+ onProgress: options.onProgress,
6673
6840
  });
6674
6841
  // Track usage
6675
6842
  if (parsed.usage) {
@@ -6698,7 +6865,6 @@ class OperateLoop {
6698
6865
  }
6699
6866
  // Handle tool calls
6700
6867
  if (parsed.hasToolCalls) {
6701
- const toolCalls = this.adapter.extractToolCalls(response);
6702
6868
  if (toolCalls.length > 0 && state.toolkit && state.maxTurns > 1) {
6703
6869
  // Track updated provider request for tool results
6704
6870
  let currentProviderRequest = providerRequest;
@@ -6710,6 +6876,14 @@ class OperateLoop {
6710
6876
  // Process each tool call
6711
6877
  for (const toolCall of toolCalls) {
6712
6878
  try {
6879
+ await emitProgress({
6880
+ event: {
6881
+ tool: { arguments: toolCall.arguments, name: toolCall.name },
6882
+ turn: state.currentTurn,
6883
+ type: LlmProgressEventType.ToolCall,
6884
+ },
6885
+ onProgress: options.onProgress,
6886
+ });
6713
6887
  // Execute beforeEachTool hook
6714
6888
  await this.hookRunnerInstance.runBeforeTool(context.hooks, {
6715
6889
  args: toolCall.arguments,
@@ -6729,6 +6903,14 @@ class OperateLoop {
6729
6903
  });
6730
6904
  return result;
6731
6905
  });
6906
+ await emitProgress({
6907
+ event: {
6908
+ tool: { name: toolCall.name },
6909
+ turn: state.currentTurn,
6910
+ type: LlmProgressEventType.ToolResult,
6911
+ },
6912
+ onProgress: options.onProgress,
6913
+ });
6732
6914
  // Execute afterEachTool hook
6733
6915
  await this.hookRunnerInstance.runAfterTool(context.hooks, {
6734
6916
  args: toolCall.arguments,
@@ -6752,6 +6934,15 @@ class OperateLoop {
6752
6934
  state.responseBuilder.appendToHistory(toolResultFormatted);
6753
6935
  }
6754
6936
  catch (error) {
6937
+ await emitProgress({
6938
+ event: {
6939
+ error: error.message,
6940
+ tool: { name: toolCall.name },
6941
+ turn: state.currentTurn,
6942
+ type: LlmProgressEventType.ToolError,
6943
+ },
6944
+ onProgress: options.onProgress,
6945
+ });
6755
6946
  // Execute onToolError hook
6756
6947
  await this.hookRunnerInstance.runOnToolError(context.hooks, {
6757
6948
  args: toolCall.arguments,
@@ -6825,6 +7016,20 @@ class OperateLoop {
6825
7016
  }
6826
7017
  return false; // Stop loop
6827
7018
  }
7019
+ /**
7020
+ * Draconian summary of a history item for trace logging: string message
7021
+ * content is kept; everything else is reduced to its type.
7022
+ */
7023
+ summarizeHistoryItem(item) {
7024
+ if (!item) {
7025
+ return undefined;
7026
+ }
7027
+ const record = item;
7028
+ if (typeof record.content === "string") {
7029
+ return { content: record.content, role: record.role };
7030
+ }
7031
+ return { type: record.type ?? "message" };
7032
+ }
6828
7033
  /**
6829
7034
  * Reconcile structured output against the declared `format` contract. A
6830
7035
  * declared `format` is a schema contract: returned keys should match the
@@ -7018,6 +7223,7 @@ class StreamLoop {
7018
7223
  messages: state.currentInput,
7019
7224
  model: options.model ?? this.adapter.defaultModel,
7020
7225
  providerOptions: options.providerOptions,
7226
+ stream: true,
7021
7227
  system: options.system,
7022
7228
  temperature: options.temperature,
7023
7229
  tools: state.formattedTools,
@@ -7086,6 +7292,7 @@ class StreamLoop {
7086
7292
  messages: state.currentInput,
7087
7293
  model: options.model ?? this.adapter.defaultModel,
7088
7294
  providerOptions: options.providerOptions,
7295
+ stream: true,
7089
7296
  system: options.system,
7090
7297
  temperature: options.temperature,
7091
7298
  tools: state.formattedTools,
@@ -9160,5 +9367,5 @@ const toolkit = new JaypieToolkit(tools);
9160
9367
  [LlmMessageRole.Developer]: "user",
9161
9368
  });
9162
9369
 
9163
- 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 };
9370
+ 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 };
9164
9371
  //# sourceMappingURL=index.js.map