@jaypie/llm 1.1.30-rc.0 → 1.2.0-rc.0

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.
Files changed (35) hide show
  1. package/dist/cjs/constants.d.ts +17 -1
  2. package/dist/cjs/index.cjs +725 -109
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/cjs/index.d.ts +1 -0
  5. package/dist/cjs/operate/adapters/AnthropicAdapter.d.ts +1 -1
  6. package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +96 -0
  7. package/dist/cjs/operate/adapters/index.d.ts +1 -0
  8. package/dist/cjs/operate/index.d.ts +1 -1
  9. package/dist/cjs/providers/anthropic/utils.d.ts +3 -37
  10. package/dist/cjs/providers/gemini/utils.d.ts +3 -37
  11. package/dist/cjs/providers/openai/utils.d.ts +1 -36
  12. package/dist/cjs/providers/openrouter/OpenRouterProvider.class.d.ts +17 -0
  13. package/dist/cjs/providers/openrouter/index.d.ts +2 -0
  14. package/dist/cjs/providers/openrouter/utils.d.ts +15 -0
  15. package/dist/cjs/util/logger.d.ts +2 -71
  16. package/dist/esm/constants.d.ts +17 -1
  17. package/dist/esm/index.d.ts +1 -0
  18. package/dist/esm/index.js +709 -94
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/operate/adapters/AnthropicAdapter.d.ts +1 -1
  21. package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +96 -0
  22. package/dist/esm/operate/adapters/index.d.ts +1 -0
  23. package/dist/esm/operate/index.d.ts +1 -1
  24. package/dist/esm/providers/anthropic/utils.d.ts +3 -37
  25. package/dist/esm/providers/gemini/utils.d.ts +3 -37
  26. package/dist/esm/providers/openai/utils.d.ts +1 -36
  27. package/dist/esm/providers/openrouter/OpenRouterProvider.class.d.ts +17 -0
  28. package/dist/esm/providers/openrouter/index.d.ts +2 -0
  29. package/dist/esm/providers/openrouter/utils.d.ts +15 -0
  30. package/dist/esm/util/logger.d.ts +2 -71
  31. package/package.json +24 -7
  32. package/dist/cjs/providers/anthropic/operate.d.ts +0 -16
  33. package/dist/cjs/providers/openai/operate.d.ts +0 -26
  34. package/dist/esm/providers/anthropic/operate.d.ts +0 -16
  35. package/dist/esm/providers/openai/operate.d.ts +0 -26
package/dist/esm/index.js CHANGED
@@ -1,15 +1,34 @@
1
1
  import { ConfigurationError, BadGatewayError, TooManyRequestsError, NotImplementedError } from '@jaypie/errors';
2
- import Anthropic, { RateLimitError, APIConnectionError, APIConnectionTimeoutError, InternalServerError, AuthenticationError, BadRequestError, NotFoundError, PermissionDeniedError } from '@anthropic-ai/sdk';
3
2
  import { z } from 'zod/v4';
4
- import { placeholders, log as log$2, JAYPIE, resolveValue, sleep, ConfigurationError as ConfigurationError$1 } from '@jaypie/core';
3
+ import { placeholders, JAYPIE, resolveValue, sleep } from '@jaypie/kit';
4
+ import { log as log$2 } from '@jaypie/logger';
5
5
  import RandomLib from 'random';
6
- import { RateLimitError as RateLimitError$1, APIConnectionError as APIConnectionError$1, APIConnectionTimeoutError as APIConnectionTimeoutError$1, InternalServerError as InternalServerError$1, APIUserAbortError, AuthenticationError as AuthenticationError$1, BadRequestError as BadRequestError$1, ConflictError, NotFoundError as NotFoundError$1, PermissionDeniedError as PermissionDeniedError$1, UnprocessableEntityError, OpenAI } from 'openai';
6
+ import { RateLimitError, APIConnectionError, APIConnectionTimeoutError, InternalServerError, APIUserAbortError, AuthenticationError, BadRequestError, ConflictError, NotFoundError, PermissionDeniedError, UnprocessableEntityError, OpenAI } from 'openai';
7
7
  import { zodResponseFormat } from 'openai/helpers/zod';
8
8
  import { getEnvSecret } from '@jaypie/aws';
9
- import { GoogleGenAI } from '@google/genai';
10
9
  import { fetchWeatherApi } from 'openmeteo';
11
10
 
12
11
  const PROVIDER = {
12
+ OPENROUTER: {
13
+ // https://openrouter.ai/models
14
+ // OpenRouter provides access to hundreds of models from various providers
15
+ // The model format is: provider/model-name (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
16
+ MODEL: {
17
+ // Default uses env var OPENROUTER_MODEL if set, otherwise a reasonable default
18
+ DEFAULT: "openai/gpt-4o",
19
+ SMALL: "openai/gpt-4o-mini",
20
+ LARGE: "anthropic/claude-3-opus",
21
+ TINY: "openai/gpt-4o-mini",
22
+ },
23
+ MODEL_MATCH_WORDS: ["openrouter"],
24
+ NAME: "openrouter",
25
+ ROLE: {
26
+ ASSISTANT: "assistant",
27
+ SYSTEM: "system",
28
+ TOOL: "tool",
29
+ USER: "user",
30
+ },
31
+ },
13
32
  GEMINI: {
14
33
  // https://ai.google.dev/gemini-api/docs/models
15
34
  MODEL: {
@@ -156,6 +175,12 @@ function determineModelProvider(input) {
156
175
  provider: PROVIDER.OPENAI.NAME,
157
176
  };
158
177
  }
178
+ if (input === PROVIDER.OPENROUTER.NAME) {
179
+ return {
180
+ model: PROVIDER.OPENROUTER.MODEL.DEFAULT,
181
+ provider: PROVIDER.OPENROUTER.NAME,
182
+ };
183
+ }
159
184
  // Check if input matches an Anthropic model exactly
160
185
  for (const [, modelValue] of Object.entries(PROVIDER.ANTHROPIC.MODEL)) {
161
186
  if (input === modelValue) {
@@ -183,6 +208,15 @@ function determineModelProvider(input) {
183
208
  };
184
209
  }
185
210
  }
211
+ // Check if input matches an OpenRouter model exactly
212
+ for (const [, modelValue] of Object.entries(PROVIDER.OPENROUTER.MODEL)) {
213
+ if (input === modelValue) {
214
+ return {
215
+ model: input,
216
+ provider: PROVIDER.OPENROUTER.NAME,
217
+ };
218
+ }
219
+ }
186
220
  // Check Anthropic match words
187
221
  const lowerInput = input.toLowerCase();
188
222
  for (const matchWord of PROVIDER.ANTHROPIC.MODEL_MATCH_WORDS) {
@@ -221,6 +255,15 @@ function determineModelProvider(input) {
221
255
  }
222
256
  }
223
257
  }
258
+ // Check OpenRouter match words
259
+ for (const matchWord of PROVIDER.OPENROUTER.MODEL_MATCH_WORDS) {
260
+ if (lowerInput.includes(matchWord)) {
261
+ return {
262
+ model: input,
263
+ provider: PROVIDER.OPENROUTER.NAME,
264
+ };
265
+ }
266
+ }
224
267
  // Default fallback if model not recognized
225
268
  return {
226
269
  model: input,
@@ -340,8 +383,8 @@ function formatOperateInput(input, options) {
340
383
  return [input];
341
384
  }
342
385
 
343
- const getLogger$3 = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
344
- const log$1 = getLogger$3();
386
+ const getLogger$4 = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
387
+ const log$1 = getLogger$4();
345
388
 
346
389
  // Turn policy constants
347
390
  const MAX_TURNS_ABSOLUTE_LIMIT = 72;
@@ -644,17 +687,18 @@ var ErrorCategory;
644
687
  //
645
688
  // Constants
646
689
  //
647
- const STRUCTURED_OUTPUT_TOOL_NAME$1 = "structured_output";
648
- const RETRYABLE_ERROR_TYPES$1 = [
649
- APIConnectionError,
650
- APIConnectionTimeoutError,
651
- InternalServerError,
690
+ const STRUCTURED_OUTPUT_TOOL_NAME$2 = "structured_output";
691
+ // Error names for classification (using string names since SDK is optional)
692
+ const RETRYABLE_ERROR_NAMES = [
693
+ "APIConnectionError",
694
+ "APIConnectionTimeoutError",
695
+ "InternalServerError",
652
696
  ];
653
- const NOT_RETRYABLE_ERROR_TYPES$1 = [
654
- AuthenticationError,
655
- BadRequestError,
656
- NotFoundError,
657
- PermissionDeniedError,
697
+ const NOT_RETRYABLE_ERROR_NAMES = [
698
+ "AuthenticationError",
699
+ "BadRequestError",
700
+ "NotFoundError",
701
+ "PermissionDeniedError",
658
702
  ];
659
703
  //
660
704
  //
@@ -709,7 +753,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
709
753
  type: "custom",
710
754
  }));
711
755
  // Determine tool choice based on whether structured output is requested
712
- const hasStructuredOutput = request.tools.some((t) => t.name === STRUCTURED_OUTPUT_TOOL_NAME$1);
756
+ const hasStructuredOutput = request.tools.some((t) => t.name === STRUCTURED_OUTPUT_TOOL_NAME$2);
713
757
  anthropicRequest.tool_choice = {
714
758
  type: hasStructuredOutput ? "any" : "auto",
715
759
  };
@@ -731,7 +775,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
731
775
  // Add structured output tool if schema is provided
732
776
  if (outputSchema) {
733
777
  tools.push({
734
- name: STRUCTURED_OUTPUT_TOOL_NAME$1,
778
+ name: STRUCTURED_OUTPUT_TOOL_NAME$2,
735
779
  description: "Output a structured JSON object, " +
736
780
  "use this before your final response to give structured outputs to the user",
737
781
  parameters: outputSchema,
@@ -862,8 +906,9 @@ class AnthropicAdapter extends BaseProviderAdapter {
862
906
  // Error Classification
863
907
  //
864
908
  classifyError(error) {
909
+ const errorName = error?.constructor?.name;
865
910
  // Check for rate limit error
866
- if (error instanceof RateLimitError) {
911
+ if (errorName === "RateLimitError") {
867
912
  return {
868
913
  error,
869
914
  category: ErrorCategory.RateLimit,
@@ -872,24 +917,20 @@ class AnthropicAdapter extends BaseProviderAdapter {
872
917
  };
873
918
  }
874
919
  // Check for retryable errors
875
- for (const ErrorType of RETRYABLE_ERROR_TYPES$1) {
876
- if (error instanceof ErrorType) {
877
- return {
878
- error,
879
- category: ErrorCategory.Retryable,
880
- shouldRetry: true,
881
- };
882
- }
920
+ if (RETRYABLE_ERROR_NAMES.includes(errorName)) {
921
+ return {
922
+ error,
923
+ category: ErrorCategory.Retryable,
924
+ shouldRetry: true,
925
+ };
883
926
  }
884
927
  // Check for non-retryable errors
885
- for (const ErrorType of NOT_RETRYABLE_ERROR_TYPES$1) {
886
- if (error instanceof ErrorType) {
887
- return {
888
- error,
889
- category: ErrorCategory.Unrecoverable,
890
- shouldRetry: false,
891
- };
892
- }
928
+ if (NOT_RETRYABLE_ERROR_NAMES.includes(errorName)) {
929
+ return {
930
+ error,
931
+ category: ErrorCategory.Unrecoverable,
932
+ shouldRetry: false,
933
+ };
893
934
  }
894
935
  // Unknown error - treat as potentially retryable
895
936
  return {
@@ -910,13 +951,13 @@ class AnthropicAdapter extends BaseProviderAdapter {
910
951
  // Check if the last content block is a tool_use with structured_output
911
952
  const lastBlock = anthropicResponse.content[anthropicResponse.content.length - 1];
912
953
  return (lastBlock?.type === "tool_use" &&
913
- lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$1);
954
+ lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$2);
914
955
  }
915
956
  extractStructuredOutput(response) {
916
957
  const anthropicResponse = response;
917
958
  const lastBlock = anthropicResponse.content[anthropicResponse.content.length - 1];
918
959
  if (lastBlock?.type === "tool_use" &&
919
- lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$1) {
960
+ lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$2) {
920
961
  return lastBlock.input;
921
962
  }
922
963
  return undefined;
@@ -941,10 +982,10 @@ const anthropicAdapter = new AnthropicAdapter();
941
982
  //
942
983
  // Constants
943
984
  //
944
- const STRUCTURED_OUTPUT_TOOL_NAME = "structured_output";
985
+ const STRUCTURED_OUTPUT_TOOL_NAME$1 = "structured_output";
945
986
  // Gemini uses HTTP status codes for error classification
946
987
  // Documented at: https://ai.google.dev/api/rest/v1beta/Status
947
- const RETRYABLE_STATUS_CODES = [
988
+ const RETRYABLE_STATUS_CODES$1 = [
948
989
  408, // Request Timeout
949
990
  429, // Too Many Requests (Rate Limit)
950
991
  500, // Internal Server Error
@@ -1058,7 +1099,7 @@ class GeminiAdapter extends BaseProviderAdapter {
1058
1099
  // Add structured output tool if schema is provided
1059
1100
  if (outputSchema) {
1060
1101
  tools.push({
1061
- name: STRUCTURED_OUTPUT_TOOL_NAME,
1102
+ name: STRUCTURED_OUTPUT_TOOL_NAME$1,
1062
1103
  description: "Output a structured JSON object, " +
1063
1104
  "use this before your final response to give structured outputs to the user",
1064
1105
  parameters: outputSchema,
@@ -1259,7 +1300,7 @@ class GeminiAdapter extends BaseProviderAdapter {
1259
1300
  }
1260
1301
  // Check for retryable errors
1261
1302
  if (typeof statusCode === "number" &&
1262
- RETRYABLE_STATUS_CODES.includes(statusCode)) {
1303
+ RETRYABLE_STATUS_CODES$1.includes(statusCode)) {
1263
1304
  return {
1264
1305
  error,
1265
1306
  category: ErrorCategory.Retryable,
@@ -1323,7 +1364,7 @@ class GeminiAdapter extends BaseProviderAdapter {
1323
1364
  }
1324
1365
  // Check if the last part is a function call with structured_output
1325
1366
  const lastPart = candidate.content.parts[candidate.content.parts.length - 1];
1326
- return lastPart?.functionCall?.name === STRUCTURED_OUTPUT_TOOL_NAME;
1367
+ return lastPart?.functionCall?.name === STRUCTURED_OUTPUT_TOOL_NAME$1;
1327
1368
  }
1328
1369
  extractStructuredOutput(response) {
1329
1370
  const geminiResponse = response;
@@ -1332,7 +1373,7 @@ class GeminiAdapter extends BaseProviderAdapter {
1332
1373
  return undefined;
1333
1374
  }
1334
1375
  const lastPart = candidate.content.parts[candidate.content.parts.length - 1];
1335
- if (lastPart?.functionCall?.name === STRUCTURED_OUTPUT_TOOL_NAME) {
1376
+ if (lastPart?.functionCall?.name === STRUCTURED_OUTPUT_TOOL_NAME$1) {
1336
1377
  return lastPart.functionCall.args;
1337
1378
  }
1338
1379
  return undefined;
@@ -1458,18 +1499,18 @@ const geminiAdapter = new GeminiAdapter();
1458
1499
  // Constants
1459
1500
  //
1460
1501
  const RETRYABLE_ERROR_TYPES = [
1461
- APIConnectionError$1,
1462
- APIConnectionTimeoutError$1,
1463
- InternalServerError$1,
1502
+ APIConnectionError,
1503
+ APIConnectionTimeoutError,
1504
+ InternalServerError,
1464
1505
  ];
1465
1506
  const NOT_RETRYABLE_ERROR_TYPES = [
1466
1507
  APIUserAbortError,
1467
- AuthenticationError$1,
1468
- BadRequestError$1,
1508
+ AuthenticationError,
1509
+ BadRequestError,
1469
1510
  ConflictError,
1470
- NotFoundError$1,
1471
- PermissionDeniedError$1,
1472
- RateLimitError$1,
1511
+ NotFoundError,
1512
+ PermissionDeniedError,
1513
+ RateLimitError,
1473
1514
  UnprocessableEntityError,
1474
1515
  ];
1475
1516
  //
@@ -1668,7 +1709,7 @@ class OpenAiAdapter extends BaseProviderAdapter {
1668
1709
  //
1669
1710
  classifyError(error) {
1670
1711
  // Check for rate limit error
1671
- if (error instanceof RateLimitError$1) {
1712
+ if (error instanceof RateLimitError) {
1672
1713
  return {
1673
1714
  error,
1674
1715
  category: ErrorCategory.RateLimit,
@@ -1762,6 +1803,409 @@ class OpenAiAdapter extends BaseProviderAdapter {
1762
1803
  // Export singleton instance
1763
1804
  const openAiAdapter = new OpenAiAdapter();
1764
1805
 
1806
+ //
1807
+ //
1808
+ // Constants
1809
+ //
1810
+ const STRUCTURED_OUTPUT_TOOL_NAME = "structured_output";
1811
+ // OpenRouter SDK error types based on HTTP status codes
1812
+ const RETRYABLE_STATUS_CODES = [408, 500, 502, 503, 524, 529];
1813
+ const RATE_LIMIT_STATUS_CODE = 429;
1814
+ //
1815
+ //
1816
+ // Main
1817
+ //
1818
+ /**
1819
+ * OpenRouterAdapter implements the ProviderAdapter interface for OpenRouter's API.
1820
+ * OpenRouter provides a unified API to access hundreds of AI models through OpenAI-compatible endpoints.
1821
+ * It handles request building, response parsing, and error classification
1822
+ * specific to OpenRouter's Chat Completions API.
1823
+ */
1824
+ class OpenRouterAdapter extends BaseProviderAdapter {
1825
+ constructor() {
1826
+ super(...arguments);
1827
+ this.name = PROVIDER.OPENROUTER.NAME;
1828
+ this.defaultModel = PROVIDER.OPENROUTER.MODEL.DEFAULT;
1829
+ }
1830
+ //
1831
+ // Request Building
1832
+ //
1833
+ buildRequest(request) {
1834
+ // Convert messages to OpenRouter format (OpenAI-compatible)
1835
+ const messages = this.convertMessagesToOpenRouter(request.messages, request.system);
1836
+ // Append instructions to last message if provided
1837
+ if (request.instructions && messages.length > 0) {
1838
+ const lastMsg = messages[messages.length - 1];
1839
+ if (lastMsg.content && typeof lastMsg.content === "string") {
1840
+ lastMsg.content = lastMsg.content + "\n\n" + request.instructions;
1841
+ }
1842
+ }
1843
+ const openRouterRequest = {
1844
+ model: request.model || this.defaultModel,
1845
+ messages,
1846
+ };
1847
+ if (request.user) {
1848
+ openRouterRequest.user = request.user;
1849
+ }
1850
+ if (request.tools && request.tools.length > 0) {
1851
+ openRouterRequest.tools = request.tools.map((tool) => ({
1852
+ type: "function",
1853
+ function: {
1854
+ name: tool.name,
1855
+ description: tool.description,
1856
+ parameters: tool.parameters,
1857
+ },
1858
+ }));
1859
+ // Determine tool choice based on whether structured output is requested
1860
+ const hasStructuredOutput = request.tools.some((t) => t.name === STRUCTURED_OUTPUT_TOOL_NAME);
1861
+ openRouterRequest.tool_choice = hasStructuredOutput ? "required" : "auto";
1862
+ }
1863
+ if (request.providerOptions) {
1864
+ Object.assign(openRouterRequest, request.providerOptions);
1865
+ }
1866
+ return openRouterRequest;
1867
+ }
1868
+ formatTools(toolkit, outputSchema) {
1869
+ const tools = toolkit.tools.map((tool) => ({
1870
+ name: tool.name,
1871
+ description: tool.description,
1872
+ parameters: tool.parameters,
1873
+ }));
1874
+ // Add structured output tool if schema is provided
1875
+ // (OpenRouter doesn't have native structured output like OpenAI, so use tool approach)
1876
+ if (outputSchema) {
1877
+ tools.push({
1878
+ name: STRUCTURED_OUTPUT_TOOL_NAME,
1879
+ description: "REQUIRED: You MUST call this tool to provide your final response. " +
1880
+ "After gathering all necessary information (including results from other tools), " +
1881
+ "call this tool with the structured data to complete the request.",
1882
+ parameters: outputSchema,
1883
+ });
1884
+ }
1885
+ return tools;
1886
+ }
1887
+ formatOutputSchema(schema) {
1888
+ let jsonSchema;
1889
+ // Check if schema is already a JsonObject with type "json_schema"
1890
+ if (typeof schema === "object" &&
1891
+ schema !== null &&
1892
+ !Array.isArray(schema) &&
1893
+ schema.type === "json_schema") {
1894
+ jsonSchema = structuredClone(schema);
1895
+ jsonSchema.type = "object"; // Normalize type
1896
+ }
1897
+ else {
1898
+ // Convert NaturalSchema to JSON schema through Zod
1899
+ const zodSchema = schema instanceof z.ZodType
1900
+ ? schema
1901
+ : naturalZodSchema(schema);
1902
+ jsonSchema = z.toJSONSchema(zodSchema);
1903
+ }
1904
+ // Remove $schema property (can cause issues with some providers)
1905
+ if (jsonSchema.$schema) {
1906
+ delete jsonSchema.$schema;
1907
+ }
1908
+ return jsonSchema;
1909
+ }
1910
+ //
1911
+ // API Execution
1912
+ //
1913
+ async executeRequest(client, request) {
1914
+ const openRouter = client;
1915
+ const openRouterRequest = request;
1916
+ const response = await openRouter.chat.send({
1917
+ model: openRouterRequest.model,
1918
+ messages: openRouterRequest.messages,
1919
+ tools: openRouterRequest.tools,
1920
+ toolChoice: openRouterRequest.tool_choice,
1921
+ user: openRouterRequest.user,
1922
+ });
1923
+ return response;
1924
+ }
1925
+ //
1926
+ // Response Parsing
1927
+ //
1928
+ parseResponse(response, _options) {
1929
+ const openRouterResponse = response;
1930
+ const choice = openRouterResponse.choices[0];
1931
+ const content = this.extractContent(openRouterResponse);
1932
+ const hasToolCalls = this.hasToolCalls(openRouterResponse);
1933
+ // SDK returns camelCase (finishReason), but support snake_case as fallback
1934
+ const stopReason = choice?.finishReason ?? choice?.finish_reason ?? undefined;
1935
+ return {
1936
+ content,
1937
+ hasToolCalls,
1938
+ stopReason,
1939
+ usage: this.extractUsage(openRouterResponse, openRouterResponse.model),
1940
+ raw: openRouterResponse,
1941
+ };
1942
+ }
1943
+ extractToolCalls(response) {
1944
+ const openRouterResponse = response;
1945
+ const toolCalls = [];
1946
+ const choice = openRouterResponse.choices[0];
1947
+ // SDK returns camelCase (toolCalls)
1948
+ if (!choice?.message?.toolCalls) {
1949
+ return toolCalls;
1950
+ }
1951
+ for (const toolCall of choice.message.toolCalls) {
1952
+ toolCalls.push({
1953
+ callId: toolCall.id,
1954
+ name: toolCall.function.name,
1955
+ arguments: toolCall.function.arguments,
1956
+ raw: toolCall,
1957
+ });
1958
+ }
1959
+ return toolCalls;
1960
+ }
1961
+ extractUsage(response, model) {
1962
+ const openRouterResponse = response;
1963
+ if (!openRouterResponse.usage) {
1964
+ return {
1965
+ input: 0,
1966
+ output: 0,
1967
+ reasoning: 0,
1968
+ total: 0,
1969
+ provider: this.name,
1970
+ model,
1971
+ };
1972
+ }
1973
+ // SDK returns camelCase, but support snake_case as fallback
1974
+ const usage = openRouterResponse.usage;
1975
+ return {
1976
+ input: usage.promptTokens || usage.prompt_tokens || 0,
1977
+ output: usage.completionTokens || usage.completion_tokens || 0,
1978
+ reasoning: 0, // OpenRouter doesn't expose reasoning tokens in standard format
1979
+ total: usage.totalTokens || usage.total_tokens || 0,
1980
+ provider: this.name,
1981
+ model,
1982
+ };
1983
+ }
1984
+ //
1985
+ // Tool Result Handling
1986
+ //
1987
+ formatToolResult(toolCall, result) {
1988
+ return {
1989
+ role: "tool",
1990
+ toolCallId: toolCall.callId,
1991
+ content: result.output,
1992
+ };
1993
+ }
1994
+ appendToolResult(request, toolCall, result) {
1995
+ const openRouterRequest = request;
1996
+ const toolCallRaw = toolCall.raw;
1997
+ // Add assistant message with the tool call (SDK uses camelCase)
1998
+ openRouterRequest.messages.push({
1999
+ role: "assistant",
2000
+ content: null,
2001
+ toolCalls: [toolCallRaw],
2002
+ });
2003
+ // Add tool result message
2004
+ openRouterRequest.messages.push(this.formatToolResult(toolCall, result));
2005
+ return openRouterRequest;
2006
+ }
2007
+ //
2008
+ // History Management
2009
+ //
2010
+ responseToHistoryItems(response) {
2011
+ const openRouterResponse = response;
2012
+ const historyItems = [];
2013
+ const choice = openRouterResponse.choices[0];
2014
+ if (!choice?.message) {
2015
+ return historyItems;
2016
+ }
2017
+ // Check if this is a tool use response (SDK returns camelCase)
2018
+ if (choice.message.toolCalls && choice.message.toolCalls.length > 0) {
2019
+ // Don't add to history yet - will be added after tool execution
2020
+ return historyItems;
2021
+ }
2022
+ // Extract text content for non-tool responses
2023
+ if (choice.message.content) {
2024
+ historyItems.push({
2025
+ content: choice.message.content,
2026
+ role: LlmMessageRole.Assistant,
2027
+ type: LlmMessageType.Message,
2028
+ });
2029
+ }
2030
+ return historyItems;
2031
+ }
2032
+ //
2033
+ // Error Classification
2034
+ //
2035
+ classifyError(error) {
2036
+ // Check if error has a status code (HTTP error)
2037
+ const errorWithStatus = error;
2038
+ const statusCode = errorWithStatus.status || errorWithStatus.statusCode;
2039
+ if (statusCode) {
2040
+ // Rate limit error
2041
+ if (statusCode === RATE_LIMIT_STATUS_CODE) {
2042
+ return {
2043
+ error,
2044
+ category: ErrorCategory.RateLimit,
2045
+ shouldRetry: false,
2046
+ suggestedDelayMs: 60000,
2047
+ };
2048
+ }
2049
+ // Retryable errors (server errors, timeouts, etc.)
2050
+ if (RETRYABLE_STATUS_CODES.includes(statusCode)) {
2051
+ return {
2052
+ error,
2053
+ category: ErrorCategory.Retryable,
2054
+ shouldRetry: true,
2055
+ };
2056
+ }
2057
+ // Client errors (4xx except 429) are unrecoverable
2058
+ if (statusCode >= 400 && statusCode < 500) {
2059
+ return {
2060
+ error,
2061
+ category: ErrorCategory.Unrecoverable,
2062
+ shouldRetry: false,
2063
+ };
2064
+ }
2065
+ }
2066
+ // Check error message for rate limit indicators
2067
+ const errorMessage = error instanceof Error ? error.message.toLowerCase() : "";
2068
+ if (errorMessage.includes("rate limit") ||
2069
+ errorMessage.includes("too many requests")) {
2070
+ return {
2071
+ error,
2072
+ category: ErrorCategory.RateLimit,
2073
+ shouldRetry: false,
2074
+ suggestedDelayMs: 60000,
2075
+ };
2076
+ }
2077
+ // Unknown error - treat as potentially retryable
2078
+ return {
2079
+ error,
2080
+ category: ErrorCategory.Unknown,
2081
+ shouldRetry: true,
2082
+ };
2083
+ }
2084
+ //
2085
+ // Provider-Specific Features
2086
+ //
2087
+ isComplete(response) {
2088
+ const openRouterResponse = response;
2089
+ const choice = openRouterResponse.choices[0];
2090
+ // Complete if no tool calls (SDK returns camelCase)
2091
+ if (!choice?.message?.toolCalls?.length) {
2092
+ return true;
2093
+ }
2094
+ return false;
2095
+ }
2096
+ hasStructuredOutput(response) {
2097
+ const openRouterResponse = response;
2098
+ const choice = openRouterResponse.choices[0];
2099
+ // SDK returns camelCase (toolCalls)
2100
+ if (!choice?.message?.toolCalls?.length) {
2101
+ return false;
2102
+ }
2103
+ // Check if the last tool call is structured_output
2104
+ const lastToolCall = choice.message.toolCalls[choice.message.toolCalls.length - 1];
2105
+ return lastToolCall?.function?.name === STRUCTURED_OUTPUT_TOOL_NAME;
2106
+ }
2107
+ extractStructuredOutput(response) {
2108
+ const openRouterResponse = response;
2109
+ const choice = openRouterResponse.choices[0];
2110
+ // SDK returns camelCase (toolCalls)
2111
+ if (!choice?.message?.toolCalls?.length) {
2112
+ return undefined;
2113
+ }
2114
+ const lastToolCall = choice.message.toolCalls[choice.message.toolCalls.length - 1];
2115
+ if (lastToolCall?.function?.name === STRUCTURED_OUTPUT_TOOL_NAME) {
2116
+ try {
2117
+ return JSON.parse(lastToolCall.function.arguments);
2118
+ }
2119
+ catch {
2120
+ return undefined;
2121
+ }
2122
+ }
2123
+ return undefined;
2124
+ }
2125
+ //
2126
+ // Private Helpers
2127
+ //
2128
+ hasToolCalls(response) {
2129
+ const choice = response.choices[0];
2130
+ // SDK returns camelCase (toolCalls)
2131
+ return (choice?.message?.toolCalls?.length ?? 0) > 0;
2132
+ }
2133
+ extractContent(response) {
2134
+ // Check for structured output first
2135
+ if (this.hasStructuredOutput(response)) {
2136
+ return this.extractStructuredOutput(response);
2137
+ }
2138
+ const choice = response.choices[0];
2139
+ return choice?.message?.content ?? undefined;
2140
+ }
2141
+ convertMessagesToOpenRouter(messages, system) {
2142
+ const openRouterMessages = [];
2143
+ // Add system message if provided
2144
+ if (system) {
2145
+ openRouterMessages.push({
2146
+ role: "system",
2147
+ content: system,
2148
+ });
2149
+ }
2150
+ for (const msg of messages) {
2151
+ const message = msg;
2152
+ // Handle different message types
2153
+ if (message.role === "system") {
2154
+ openRouterMessages.push({
2155
+ role: "system",
2156
+ content: message.content,
2157
+ });
2158
+ }
2159
+ else if (message.role === "user") {
2160
+ openRouterMessages.push({
2161
+ role: "user",
2162
+ content: message.content,
2163
+ });
2164
+ }
2165
+ else if (message.role === "assistant") {
2166
+ const assistantMsg = {
2167
+ role: "assistant",
2168
+ content: message.content || null,
2169
+ };
2170
+ // Include toolCalls if present (check both camelCase and snake_case for compatibility)
2171
+ if (message.toolCalls) {
2172
+ assistantMsg.toolCalls = message.toolCalls;
2173
+ }
2174
+ else if (message.tool_calls) {
2175
+ assistantMsg.toolCalls = message.tool_calls;
2176
+ }
2177
+ openRouterMessages.push(assistantMsg);
2178
+ }
2179
+ else if (message.role === "tool") {
2180
+ openRouterMessages.push({
2181
+ role: "tool",
2182
+ toolCallId: message.toolCallId || message.tool_call_id,
2183
+ content: message.content,
2184
+ });
2185
+ }
2186
+ else if (message.type === LlmMessageType.Message) {
2187
+ // Handle internal message format
2188
+ const role = message.role?.toLowerCase();
2189
+ if (role === "assistant") {
2190
+ openRouterMessages.push({
2191
+ role: "assistant",
2192
+ content: message.content,
2193
+ });
2194
+ }
2195
+ else {
2196
+ openRouterMessages.push({
2197
+ role: "user",
2198
+ content: message.content,
2199
+ });
2200
+ }
2201
+ }
2202
+ }
2203
+ return openRouterMessages;
2204
+ }
2205
+ }
2206
+ // Export singleton instance
2207
+ const openRouterAdapter = new OpenRouterAdapter();
2208
+
1765
2209
  const DEFAULT_TOOL_TYPE = "function";
1766
2210
  const log = log$2.lib({ lib: JAYPIE.LIB.LLM });
1767
2211
  function logToolMessage(message, context) {
@@ -2705,26 +3149,40 @@ function createOperateLoop(config) {
2705
3149
  return new OperateLoop(config);
2706
3150
  }
2707
3151
 
3152
+ // SDK loader with caching
3153
+ let cachedSdk$2 = null;
3154
+ async function loadSdk$2() {
3155
+ if (cachedSdk$2)
3156
+ return cachedSdk$2;
3157
+ try {
3158
+ cachedSdk$2 = await import('@anthropic-ai/sdk');
3159
+ return cachedSdk$2;
3160
+ }
3161
+ catch {
3162
+ throw new ConfigurationError("@anthropic-ai/sdk is required but not installed. Run: npm install @anthropic-ai/sdk");
3163
+ }
3164
+ }
2708
3165
  // Logger
2709
- const getLogger$2 = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
3166
+ const getLogger$3 = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
2710
3167
  // Client initialization
2711
- async function initializeClient$2({ apiKey, } = {}) {
2712
- const logger = getLogger$2();
3168
+ async function initializeClient$3({ apiKey, } = {}) {
3169
+ const logger = getLogger$3();
2713
3170
  const resolvedApiKey = apiKey || (await getEnvSecret("ANTHROPIC_API_KEY"));
2714
3171
  if (!resolvedApiKey) {
2715
- throw new ConfigurationError$1("The application could not resolve the required API key: ANTHROPIC_API_KEY");
3172
+ throw new ConfigurationError("The application could not resolve the required API key: ANTHROPIC_API_KEY");
2716
3173
  }
2717
- const client = new Anthropic({ apiKey: resolvedApiKey });
3174
+ const sdk = await loadSdk$2();
3175
+ const client = new sdk.default({ apiKey: resolvedApiKey });
2718
3176
  logger.trace("Initialized Anthropic client");
2719
3177
  return client;
2720
3178
  }
2721
3179
  // Message formatting functions
2722
- function formatSystemMessage$1(systemPrompt, { data, placeholders: placeholders$1 } = {}) {
3180
+ function formatSystemMessage$2(systemPrompt, { data, placeholders: placeholders$1 } = {}) {
2723
3181
  return placeholders$1?.system === false
2724
3182
  ? systemPrompt
2725
3183
  : placeholders(systemPrompt, data);
2726
3184
  }
2727
- function formatUserMessage$2(message, { data, placeholders: placeholders$1 } = {}) {
3185
+ function formatUserMessage$3(message, { data, placeholders: placeholders$1 } = {}) {
2728
3186
  const content = placeholders$1?.message === false
2729
3187
  ? message
2730
3188
  : placeholders(message, data);
@@ -2733,11 +3191,11 @@ function formatUserMessage$2(message, { data, placeholders: placeholders$1 } = {
2733
3191
  content,
2734
3192
  };
2735
3193
  }
2736
- function prepareMessages$2(message, { data, placeholders } = {}) {
2737
- const logger = getLogger$2();
3194
+ function prepareMessages$3(message, { data, placeholders } = {}) {
3195
+ const logger = getLogger$3();
2738
3196
  const messages = [];
2739
3197
  // Add user message (necessary for all requests)
2740
- const userMessage = formatUserMessage$2(message, { data, placeholders });
3198
+ const userMessage = formatUserMessage$3(message, { data, placeholders });
2741
3199
  messages.push(userMessage);
2742
3200
  logger.trace(`User message: ${userMessage.content.length} characters`);
2743
3201
  return messages;
@@ -2823,7 +3281,7 @@ async function createStructuredCompletion$1(client, messages, model, responseSch
2823
3281
  // Main class implementation
2824
3282
  class AnthropicProvider {
2825
3283
  constructor(model = PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey } = {}) {
2826
- this.log = getLogger$2();
3284
+ this.log = getLogger$3();
2827
3285
  this.conversationHistory = [];
2828
3286
  this.model = model;
2829
3287
  this.apiKey = apiKey;
@@ -2832,7 +3290,7 @@ class AnthropicProvider {
2832
3290
  if (this._client) {
2833
3291
  return this._client;
2834
3292
  }
2835
- this._client = await initializeClient$2({ apiKey: this.apiKey });
3293
+ this._client = await initializeClient$3({ apiKey: this.apiKey });
2836
3294
  return this._client;
2837
3295
  }
2838
3296
  async getOperateLoop() {
@@ -2849,12 +3307,12 @@ class AnthropicProvider {
2849
3307
  // Main send method
2850
3308
  async send(message, options) {
2851
3309
  const client = await this.getClient();
2852
- const messages = prepareMessages$2(message, options || {});
3310
+ const messages = prepareMessages$3(message, options || {});
2853
3311
  const modelToUse = options?.model || this.model;
2854
3312
  // Process system message if provided
2855
3313
  let systemMessage;
2856
3314
  if (options?.system) {
2857
- systemMessage = formatSystemMessage$1(options.system, {
3315
+ systemMessage = formatSystemMessage$2(options.system, {
2858
3316
  data: options.data,
2859
3317
  placeholders: options.placeholders,
2860
3318
  });
@@ -2884,20 +3342,34 @@ class AnthropicProvider {
2884
3342
  }
2885
3343
  }
2886
3344
 
3345
+ // SDK loader with caching
3346
+ let cachedSdk$1 = null;
3347
+ async function loadSdk$1() {
3348
+ if (cachedSdk$1)
3349
+ return cachedSdk$1;
3350
+ try {
3351
+ cachedSdk$1 = await import('@google/genai');
3352
+ return cachedSdk$1;
3353
+ }
3354
+ catch {
3355
+ throw new ConfigurationError("@google/genai is required but not installed. Run: npm install @google/genai");
3356
+ }
3357
+ }
2887
3358
  // Logger
2888
- const getLogger$1 = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
3359
+ const getLogger$2 = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
2889
3360
  // Client initialization
2890
- async function initializeClient$1({ apiKey, } = {}) {
2891
- const logger = getLogger$1();
3361
+ async function initializeClient$2({ apiKey, } = {}) {
3362
+ const logger = getLogger$2();
2892
3363
  const resolvedApiKey = apiKey || (await getEnvSecret("GEMINI_API_KEY"));
2893
3364
  if (!resolvedApiKey) {
2894
- throw new ConfigurationError$1("The application could not resolve the requested keys");
3365
+ throw new ConfigurationError("The application could not resolve the requested keys");
2895
3366
  }
2896
- const client = new GoogleGenAI({ apiKey: resolvedApiKey });
3367
+ const sdk = await loadSdk$1();
3368
+ const client = new sdk.GoogleGenAI({ apiKey: resolvedApiKey });
2897
3369
  logger.trace("Initialized Gemini client");
2898
3370
  return client;
2899
3371
  }
2900
- function formatUserMessage$1(message, { data, placeholders: placeholders$1 } = {}) {
3372
+ function formatUserMessage$2(message, { data, placeholders: placeholders$1 } = {}) {
2901
3373
  const content = placeholders$1?.message === false
2902
3374
  ? message
2903
3375
  : placeholders(message, data);
@@ -2906,14 +3378,14 @@ function formatUserMessage$1(message, { data, placeholders: placeholders$1 } = {
2906
3378
  content,
2907
3379
  };
2908
3380
  }
2909
- function prepareMessages$1(message, { data, placeholders } = {}) {
2910
- const logger = getLogger$1();
3381
+ function prepareMessages$2(message, { data, placeholders } = {}) {
3382
+ const logger = getLogger$2();
2911
3383
  const messages = [];
2912
3384
  let systemInstruction;
2913
3385
  // Note: Gemini handles system prompts differently via systemInstruction config
2914
3386
  // This function is kept for compatibility but system prompts should be passed
2915
3387
  // via the systemInstruction parameter in generateContent
2916
- const userMessage = formatUserMessage$1(message, { data, placeholders });
3388
+ const userMessage = formatUserMessage$2(message, { data, placeholders });
2917
3389
  messages.push(userMessage);
2918
3390
  logger.trace(`User message: ${userMessage.content?.length} characters`);
2919
3391
  return { messages, systemInstruction };
@@ -2921,7 +3393,7 @@ function prepareMessages$1(message, { data, placeholders } = {}) {
2921
3393
 
2922
3394
  class GeminiProvider {
2923
3395
  constructor(model = PROVIDER.GEMINI.MODEL.DEFAULT, { apiKey } = {}) {
2924
- this.log = getLogger$1();
3396
+ this.log = getLogger$2();
2925
3397
  this.conversationHistory = [];
2926
3398
  this.model = model;
2927
3399
  this.apiKey = apiKey;
@@ -2930,7 +3402,7 @@ class GeminiProvider {
2930
3402
  if (this._client) {
2931
3403
  return this._client;
2932
3404
  }
2933
- this._client = await initializeClient$1({ apiKey: this.apiKey });
3405
+ this._client = await initializeClient$2({ apiKey: this.apiKey });
2934
3406
  return this._client;
2935
3407
  }
2936
3408
  async getOperateLoop() {
@@ -2946,7 +3418,7 @@ class GeminiProvider {
2946
3418
  }
2947
3419
  async send(message, options) {
2948
3420
  const client = await this.getClient();
2949
- const { messages } = prepareMessages$1(message, options);
3421
+ const { messages } = prepareMessages$2(message, options);
2950
3422
  const modelToUse = options?.model || this.model;
2951
3423
  // Build the request config
2952
3424
  const config = {};
@@ -3001,19 +3473,19 @@ class GeminiProvider {
3001
3473
  }
3002
3474
 
3003
3475
  // Logger
3004
- const getLogger = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
3476
+ const getLogger$1 = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
3005
3477
  // Client initialization
3006
- async function initializeClient({ apiKey, } = {}) {
3007
- const logger = getLogger();
3478
+ async function initializeClient$1({ apiKey, } = {}) {
3479
+ const logger = getLogger$1();
3008
3480
  const resolvedApiKey = apiKey || (await getEnvSecret("OPENAI_API_KEY"));
3009
3481
  if (!resolvedApiKey) {
3010
- throw new ConfigurationError$1("The application could not resolve the requested keys");
3482
+ throw new ConfigurationError("The application could not resolve the requested keys");
3011
3483
  }
3012
3484
  const client = new OpenAI({ apiKey: resolvedApiKey });
3013
3485
  logger.trace("Initialized OpenAI client");
3014
3486
  return client;
3015
3487
  }
3016
- function formatSystemMessage(systemPrompt, { data, placeholders: placeholders$1 } = {}) {
3488
+ function formatSystemMessage$1(systemPrompt, { data, placeholders: placeholders$1 } = {}) {
3017
3489
  const content = placeholders$1?.system === false
3018
3490
  ? systemPrompt
3019
3491
  : placeholders(systemPrompt, data);
@@ -3022,7 +3494,7 @@ function formatSystemMessage(systemPrompt, { data, placeholders: placeholders$1
3022
3494
  content,
3023
3495
  };
3024
3496
  }
3025
- function formatUserMessage(message, { data, placeholders: placeholders$1 } = {}) {
3497
+ function formatUserMessage$1(message, { data, placeholders: placeholders$1 } = {}) {
3026
3498
  const content = placeholders$1?.message === false
3027
3499
  ? message
3028
3500
  : placeholders(message, data);
@@ -3031,22 +3503,22 @@ function formatUserMessage(message, { data, placeholders: placeholders$1 } = {})
3031
3503
  content,
3032
3504
  };
3033
3505
  }
3034
- function prepareMessages(message, { system, data, placeholders } = {}) {
3035
- const logger = getLogger();
3506
+ function prepareMessages$1(message, { system, data, placeholders } = {}) {
3507
+ const logger = getLogger$1();
3036
3508
  const messages = [];
3037
3509
  if (system) {
3038
- const systemMessage = formatSystemMessage(system, { data, placeholders });
3510
+ const systemMessage = formatSystemMessage$1(system, { data, placeholders });
3039
3511
  messages.push(systemMessage);
3040
3512
  logger.trace(`System message: ${systemMessage.content?.length} characters`);
3041
3513
  }
3042
- const userMessage = formatUserMessage(message, { data, placeholders });
3514
+ const userMessage = formatUserMessage$1(message, { data, placeholders });
3043
3515
  messages.push(userMessage);
3044
3516
  logger.trace(`User message: ${userMessage.content?.length} characters`);
3045
3517
  return messages;
3046
3518
  }
3047
3519
  // Completion requests
3048
3520
  async function createStructuredCompletion(client, { messages, responseSchema, model, }) {
3049
- const logger = getLogger();
3521
+ const logger = getLogger$1();
3050
3522
  logger.trace("Using structured output");
3051
3523
  const zodSchema = responseSchema instanceof z.ZodType
3052
3524
  ? responseSchema
@@ -3077,7 +3549,7 @@ async function createStructuredCompletion(client, { messages, responseSchema, mo
3077
3549
  return completion.choices[0].message.parsed;
3078
3550
  }
3079
3551
  async function createTextCompletion(client, { messages, model, }) {
3080
- const logger = getLogger();
3552
+ const logger = getLogger$1();
3081
3553
  logger.trace("Using text output (unstructured)");
3082
3554
  const completion = await client.chat.completions.create({
3083
3555
  messages,
@@ -3089,7 +3561,7 @@ async function createTextCompletion(client, { messages, model, }) {
3089
3561
 
3090
3562
  class OpenAiProvider {
3091
3563
  constructor(model = PROVIDER.OPENAI.MODEL.DEFAULT, { apiKey } = {}) {
3092
- this.log = getLogger();
3564
+ this.log = getLogger$1();
3093
3565
  this.conversationHistory = [];
3094
3566
  this.model = model;
3095
3567
  this.apiKey = apiKey;
@@ -3098,7 +3570,7 @@ class OpenAiProvider {
3098
3570
  if (this._client) {
3099
3571
  return this._client;
3100
3572
  }
3101
- this._client = await initializeClient({ apiKey: this.apiKey });
3573
+ this._client = await initializeClient$1({ apiKey: this.apiKey });
3102
3574
  return this._client;
3103
3575
  }
3104
3576
  async getOperateLoop() {
@@ -3114,7 +3586,7 @@ class OpenAiProvider {
3114
3586
  }
3115
3587
  async send(message, options) {
3116
3588
  const client = await this.getClient();
3117
- const messages = prepareMessages(message, options || {});
3589
+ const messages = prepareMessages$1(message, options || {});
3118
3590
  const modelToUse = options?.model || this.model;
3119
3591
  if (options?.response) {
3120
3592
  return createStructuredCompletion(client, {
@@ -3148,6 +3620,145 @@ class OpenAiProvider {
3148
3620
  }
3149
3621
  }
3150
3622
 
3623
+ // SDK loader with caching
3624
+ let cachedSdk = null;
3625
+ async function loadSdk() {
3626
+ if (cachedSdk)
3627
+ return cachedSdk;
3628
+ try {
3629
+ cachedSdk = await import('@openrouter/sdk');
3630
+ return cachedSdk;
3631
+ }
3632
+ catch {
3633
+ throw new ConfigurationError("@openrouter/sdk is required but not installed. Run: npm install @openrouter/sdk");
3634
+ }
3635
+ }
3636
+ // Logger
3637
+ const getLogger = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
3638
+ // Client initialization
3639
+ async function initializeClient({ apiKey, } = {}) {
3640
+ const logger = getLogger();
3641
+ const resolvedApiKey = apiKey || (await getEnvSecret("OPENROUTER_API_KEY"));
3642
+ if (!resolvedApiKey) {
3643
+ throw new ConfigurationError("The application could not resolve the requested keys");
3644
+ }
3645
+ const sdk = await loadSdk();
3646
+ const client = new sdk.OpenRouter({ apiKey: resolvedApiKey });
3647
+ logger.trace("Initialized OpenRouter client");
3648
+ return client;
3649
+ }
3650
+ // Get default model from environment or constants
3651
+ function getDefaultModel() {
3652
+ return process.env.OPENROUTER_MODEL || PROVIDER.OPENROUTER.MODEL.DEFAULT;
3653
+ }
3654
+ function formatSystemMessage(systemPrompt, { data, placeholders: placeholders$1 } = {}) {
3655
+ const content = placeholders$1?.system === false
3656
+ ? systemPrompt
3657
+ : placeholders(systemPrompt, data);
3658
+ return {
3659
+ role: "system",
3660
+ content,
3661
+ };
3662
+ }
3663
+ function formatUserMessage(message, { data, placeholders: placeholders$1 } = {}) {
3664
+ const content = placeholders$1?.message === false
3665
+ ? message
3666
+ : placeholders(message, data);
3667
+ return {
3668
+ role: "user",
3669
+ content,
3670
+ };
3671
+ }
3672
+ function prepareMessages(message, { system, data, placeholders } = {}) {
3673
+ const logger = getLogger();
3674
+ const messages = [];
3675
+ if (system) {
3676
+ const systemMessage = formatSystemMessage(system, { data, placeholders });
3677
+ messages.push(systemMessage);
3678
+ logger.trace(`System message: ${systemMessage.content?.length} characters`);
3679
+ }
3680
+ const userMessage = formatUserMessage(message, { data, placeholders });
3681
+ messages.push(userMessage);
3682
+ logger.trace(`User message: ${userMessage.content?.length} characters`);
3683
+ return messages;
3684
+ }
3685
+
3686
+ class OpenRouterProvider {
3687
+ constructor(model = getDefaultModel(), { apiKey } = {}) {
3688
+ this.log = getLogger();
3689
+ this.conversationHistory = [];
3690
+ this.model = model;
3691
+ this.apiKey = apiKey;
3692
+ }
3693
+ async getClient() {
3694
+ if (this._client) {
3695
+ return this._client;
3696
+ }
3697
+ this._client = await initializeClient({ apiKey: this.apiKey });
3698
+ return this._client;
3699
+ }
3700
+ async getOperateLoop() {
3701
+ if (this._operateLoop) {
3702
+ return this._operateLoop;
3703
+ }
3704
+ const client = await this.getClient();
3705
+ this._operateLoop = createOperateLoop({
3706
+ adapter: openRouterAdapter,
3707
+ client,
3708
+ });
3709
+ return this._operateLoop;
3710
+ }
3711
+ async send(message, options) {
3712
+ const client = await this.getClient();
3713
+ const messages = prepareMessages(message, options);
3714
+ const modelToUse = options?.model || this.model;
3715
+ // Build the request
3716
+ const response = await client.chat.send({
3717
+ model: modelToUse,
3718
+ messages: messages,
3719
+ });
3720
+ const rawContent = response.choices?.[0]?.message?.content;
3721
+ // Extract text content - content could be string or array of content items
3722
+ const content = typeof rawContent === "string"
3723
+ ? rawContent
3724
+ : Array.isArray(rawContent)
3725
+ ? rawContent
3726
+ .filter((item) => item.type === "text")
3727
+ .map((item) => item.text)
3728
+ .join("")
3729
+ : "";
3730
+ this.log.trace(`Assistant reply: ${content?.length || 0} characters`);
3731
+ // If structured output was requested, try to parse the response
3732
+ if (options?.response && content) {
3733
+ try {
3734
+ return JSON.parse(content);
3735
+ }
3736
+ catch {
3737
+ return content || "";
3738
+ }
3739
+ }
3740
+ return content || "";
3741
+ }
3742
+ async operate(input, options = {}) {
3743
+ const operateLoop = await this.getOperateLoop();
3744
+ const mergedOptions = { ...options, model: options.model ?? this.model };
3745
+ // Create a merged history including both the tracked history and any explicitly provided history
3746
+ if (this.conversationHistory.length > 0) {
3747
+ // If options.history exists, merge with instance history, otherwise use instance history
3748
+ mergedOptions.history = options.history
3749
+ ? [...this.conversationHistory, ...options.history]
3750
+ : [...this.conversationHistory];
3751
+ }
3752
+ // Execute operate loop
3753
+ const response = await operateLoop.execute(input, mergedOptions);
3754
+ // Update conversation history with the new history from the response
3755
+ if (response.history && response.history.length > 0) {
3756
+ this.conversationHistory = response.history;
3757
+ }
3758
+ return response;
3759
+ }
3760
+ }
3761
+
3151
3762
  class Llm {
3152
3763
  constructor(providerName = DEFAULT.PROVIDER.NAME, options = {}) {
3153
3764
  const { model } = options;
@@ -3196,6 +3807,10 @@ class Llm {
3196
3807
  return new OpenAiProvider(model || PROVIDER.OPENAI.MODEL.DEFAULT, {
3197
3808
  apiKey,
3198
3809
  });
3810
+ case PROVIDER.OPENROUTER.NAME:
3811
+ return new OpenRouterProvider(model || PROVIDER.OPENROUTER.MODEL.DEFAULT, {
3812
+ apiKey,
3813
+ });
3199
3814
  default:
3200
3815
  throw new ConfigurationError(`Unsupported provider: ${providerName}`);
3201
3816
  }
@@ -3527,5 +4142,5 @@ const toolkit = new JaypieToolkit(tools);
3527
4142
  [LlmMessageRole.Developer]: "user",
3528
4143
  });
3529
4144
 
3530
- export { GeminiProvider, JaypieToolkit, constants as LLM, Llm, LlmMessageRole, LlmMessageType, Toolkit, toolkit, tools };
4145
+ export { GeminiProvider, JaypieToolkit, constants as LLM, Llm, LlmMessageRole, LlmMessageType, OpenRouterProvider, Toolkit, toolkit, tools };
3531
4146
  //# sourceMappingURL=index.js.map