@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.
- package/dist/cjs/constants.d.ts +17 -1
- package/dist/cjs/index.cjs +725 -109
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/operate/adapters/AnthropicAdapter.d.ts +1 -1
- package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +96 -0
- package/dist/cjs/operate/adapters/index.d.ts +1 -0
- package/dist/cjs/operate/index.d.ts +1 -1
- package/dist/cjs/providers/anthropic/utils.d.ts +3 -37
- package/dist/cjs/providers/gemini/utils.d.ts +3 -37
- package/dist/cjs/providers/openai/utils.d.ts +1 -36
- package/dist/cjs/providers/openrouter/OpenRouterProvider.class.d.ts +17 -0
- package/dist/cjs/providers/openrouter/index.d.ts +2 -0
- package/dist/cjs/providers/openrouter/utils.d.ts +15 -0
- package/dist/cjs/util/logger.d.ts +2 -71
- package/dist/esm/constants.d.ts +17 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +709 -94
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/operate/adapters/AnthropicAdapter.d.ts +1 -1
- package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +96 -0
- package/dist/esm/operate/adapters/index.d.ts +1 -0
- package/dist/esm/operate/index.d.ts +1 -1
- package/dist/esm/providers/anthropic/utils.d.ts +3 -37
- package/dist/esm/providers/gemini/utils.d.ts +3 -37
- package/dist/esm/providers/openai/utils.d.ts +1 -36
- package/dist/esm/providers/openrouter/OpenRouterProvider.class.d.ts +17 -0
- package/dist/esm/providers/openrouter/index.d.ts +2 -0
- package/dist/esm/providers/openrouter/utils.d.ts +15 -0
- package/dist/esm/util/logger.d.ts +2 -71
- package/package.json +24 -7
- package/dist/cjs/providers/anthropic/operate.d.ts +0 -16
- package/dist/cjs/providers/openai/operate.d.ts +0 -26
- package/dist/esm/providers/anthropic/operate.d.ts +0 -16
- 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,
|
|
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
|
|
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$
|
|
344
|
-
const log$1 = getLogger$
|
|
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$
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
|
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$
|
|
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$
|
|
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 (
|
|
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
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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$
|
|
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$
|
|
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
|
|
1462
|
-
APIConnectionTimeoutError
|
|
1463
|
-
InternalServerError
|
|
1502
|
+
APIConnectionError,
|
|
1503
|
+
APIConnectionTimeoutError,
|
|
1504
|
+
InternalServerError,
|
|
1464
1505
|
];
|
|
1465
1506
|
const NOT_RETRYABLE_ERROR_TYPES = [
|
|
1466
1507
|
APIUserAbortError,
|
|
1467
|
-
AuthenticationError
|
|
1468
|
-
BadRequestError
|
|
1508
|
+
AuthenticationError,
|
|
1509
|
+
BadRequestError,
|
|
1469
1510
|
ConflictError,
|
|
1470
|
-
NotFoundError
|
|
1471
|
-
PermissionDeniedError
|
|
1472
|
-
RateLimitError
|
|
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
|
|
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$
|
|
3166
|
+
const getLogger$3 = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
|
|
2710
3167
|
// Client initialization
|
|
2711
|
-
async function initializeClient$
|
|
2712
|
-
const logger = getLogger$
|
|
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
|
|
3172
|
+
throw new ConfigurationError("The application could not resolve the required API key: ANTHROPIC_API_KEY");
|
|
2716
3173
|
}
|
|
2717
|
-
const
|
|
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$
|
|
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$
|
|
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$
|
|
2737
|
-
const logger = getLogger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3359
|
+
const getLogger$2 = () => log$2.lib({ lib: JAYPIE.LIB.LLM });
|
|
2889
3360
|
// Client initialization
|
|
2890
|
-
async function initializeClient$
|
|
2891
|
-
const logger = getLogger$
|
|
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
|
|
3365
|
+
throw new ConfigurationError("The application could not resolve the requested keys");
|
|
2895
3366
|
}
|
|
2896
|
-
const
|
|
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$
|
|
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$
|
|
2910
|
-
const logger = getLogger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
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
|