@jaypie/llm 1.3.2 → 1.3.4
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/index.cjs +877 -132
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/operate/OperateLoop.d.ts +6 -3
- package/dist/cjs/operate/adapters/AnthropicAdapter.d.ts +1 -1
- package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +8 -1
- package/dist/cjs/providers/anthropic/client.d.ts +54 -0
- package/dist/cjs/providers/anthropic/types.d.ts +127 -0
- package/dist/cjs/providers/anthropic/utils.d.ts +5 -5
- package/dist/cjs/providers/google/client.d.ts +44 -0
- package/dist/cjs/providers/google/utils.d.ts +2 -3
- package/dist/cjs/providers/openai/client.d.ts +74 -0
- package/dist/cjs/providers/openai/responseFormat.d.ts +17 -0
- package/dist/cjs/providers/openai/utils.d.ts +4 -4
- package/dist/cjs/providers/openrouter/client.d.ts +40 -0
- package/dist/cjs/providers/openrouter/utils.d.ts +2 -3
- package/dist/cjs/providers/xai/utils.d.ts +2 -2
- package/dist/cjs/util/index.d.ts +2 -0
- package/dist/cjs/util/repairFormatKeys.d.ts +19 -0
- package/dist/cjs/util/sse.d.ts +12 -0
- package/dist/esm/index.js +870 -125
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/operate/OperateLoop.d.ts +6 -3
- package/dist/esm/operate/adapters/AnthropicAdapter.d.ts +1 -1
- package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +8 -1
- package/dist/esm/providers/anthropic/client.d.ts +54 -0
- package/dist/esm/providers/anthropic/types.d.ts +127 -0
- package/dist/esm/providers/anthropic/utils.d.ts +5 -5
- package/dist/esm/providers/google/client.d.ts +44 -0
- package/dist/esm/providers/google/utils.d.ts +2 -3
- package/dist/esm/providers/openai/client.d.ts +74 -0
- package/dist/esm/providers/openai/responseFormat.d.ts +17 -0
- package/dist/esm/providers/openai/utils.d.ts +4 -4
- package/dist/esm/providers/openrouter/client.d.ts +40 -0
- package/dist/esm/providers/openrouter/utils.d.ts +2 -3
- package/dist/esm/providers/xai/utils.d.ts +2 -2
- package/dist/esm/util/index.d.ts +2 -0
- package/dist/esm/util/repairFormatKeys.d.ts +19 -0
- package/dist/esm/util/sse.d.ts +12 -0
- package/package.json +2 -18
package/dist/cjs/index.cjs
CHANGED
|
@@ -5,8 +5,6 @@ var log$1 = require('@jaypie/logger');
|
|
|
5
5
|
var v4 = require('zod/v4');
|
|
6
6
|
var kit = require('@jaypie/kit');
|
|
7
7
|
var RandomLib = require('random');
|
|
8
|
-
var openai = require('openai');
|
|
9
|
-
var zod = require('openai/helpers/zod');
|
|
10
8
|
var module$1 = require('module');
|
|
11
9
|
var url = require('url');
|
|
12
10
|
var pdfLib = require('pdf-lib');
|
|
@@ -641,7 +639,7 @@ function naturalZodSchema(definition) {
|
|
|
641
639
|
//
|
|
642
640
|
// Helpers
|
|
643
641
|
//
|
|
644
|
-
function isPlainObject(value) {
|
|
642
|
+
function isPlainObject$1(value) {
|
|
645
643
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
646
644
|
}
|
|
647
645
|
/**
|
|
@@ -650,11 +648,11 @@ function isPlainObject(value) {
|
|
|
650
648
|
* provider adapters perform in `formatOutputSchema`, but without any
|
|
651
649
|
* provider-specific sanitization.
|
|
652
650
|
*/
|
|
653
|
-
function formatToJsonSchema(format) {
|
|
651
|
+
function formatToJsonSchema$1(format) {
|
|
654
652
|
if (format instanceof v4.z.ZodType) {
|
|
655
653
|
return v4.z.toJSONSchema(format);
|
|
656
654
|
}
|
|
657
|
-
if (isPlainObject(format) && format.type === "json_schema") {
|
|
655
|
+
if (isPlainObject$1(format) && format.type === "json_schema") {
|
|
658
656
|
const clone = structuredClone(format);
|
|
659
657
|
clone.type = "object";
|
|
660
658
|
return clone;
|
|
@@ -672,7 +670,7 @@ function formatToJsonSchema(format) {
|
|
|
672
670
|
* properties and array items so nested declared arrays are also backfilled.
|
|
673
671
|
*/
|
|
674
672
|
function fillFromSchema(schema, value) {
|
|
675
|
-
if (!isPlainObject(schema)) {
|
|
673
|
+
if (!isPlainObject$1(schema)) {
|
|
676
674
|
return value;
|
|
677
675
|
}
|
|
678
676
|
const type = schema.type;
|
|
@@ -682,18 +680,18 @@ function fillFromSchema(schema, value) {
|
|
|
682
680
|
return [];
|
|
683
681
|
}
|
|
684
682
|
const items = schema.items;
|
|
685
|
-
if (Array.isArray(value) && isPlainObject(items)) {
|
|
683
|
+
if (Array.isArray(value) && isPlainObject$1(items)) {
|
|
686
684
|
return value.map((entry) => fillFromSchema(items, entry));
|
|
687
685
|
}
|
|
688
686
|
return value;
|
|
689
687
|
}
|
|
690
688
|
const isObject = type === "object" || (type === undefined && "properties" in schema);
|
|
691
689
|
if (isObject) {
|
|
692
|
-
if (!isPlainObject(value)) {
|
|
690
|
+
if (!isPlainObject$1(value)) {
|
|
693
691
|
return value;
|
|
694
692
|
}
|
|
695
693
|
const properties = schema.properties;
|
|
696
|
-
if (isPlainObject(properties)) {
|
|
694
|
+
if (isPlainObject$1(properties)) {
|
|
697
695
|
for (const [key, propSchema] of Object.entries(properties)) {
|
|
698
696
|
value[key] = fillFromSchema(propSchema, value[key]);
|
|
699
697
|
}
|
|
@@ -716,7 +714,7 @@ function fillFromSchema(schema, value) {
|
|
|
716
714
|
* through untouched.
|
|
717
715
|
*/
|
|
718
716
|
function fillFormatArrays({ content, format, }) {
|
|
719
|
-
const schema = formatToJsonSchema(format);
|
|
717
|
+
const schema = formatToJsonSchema$1(format);
|
|
720
718
|
if (!schema) {
|
|
721
719
|
return content;
|
|
722
720
|
}
|
|
@@ -978,6 +976,155 @@ function random$1(defaultSeed) {
|
|
|
978
976
|
return rngFn;
|
|
979
977
|
}
|
|
980
978
|
|
|
979
|
+
//
|
|
980
|
+
//
|
|
981
|
+
// Helpers
|
|
982
|
+
//
|
|
983
|
+
function isPlainObject(value) {
|
|
984
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Convert a `format` declaration (Zod schema, NaturalSchema, or a JSON Schema
|
|
988
|
+
* JsonObject) into a plain JSON Schema we can walk. Mirrors the conversion the
|
|
989
|
+
* provider adapters perform in `formatOutputSchema`, but without any
|
|
990
|
+
* provider-specific sanitization.
|
|
991
|
+
*/
|
|
992
|
+
function formatToJsonSchema(format) {
|
|
993
|
+
if (format instanceof v4.z.ZodType) {
|
|
994
|
+
return v4.z.toJSONSchema(format);
|
|
995
|
+
}
|
|
996
|
+
if (isPlainObject(format) && format.type === "json_schema") {
|
|
997
|
+
const clone = structuredClone(format);
|
|
998
|
+
clone.type = "object";
|
|
999
|
+
return clone;
|
|
1000
|
+
}
|
|
1001
|
+
try {
|
|
1002
|
+
return v4.z.toJSONSchema(naturalZodSchema(format));
|
|
1003
|
+
}
|
|
1004
|
+
catch {
|
|
1005
|
+
return undefined;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Strip a single pair of surrounding double quotes from a key, if present.
|
|
1010
|
+
* `"Merchant Request"` -> `Merchant Request`; `Confidence` -> `Confidence`.
|
|
1011
|
+
*/
|
|
1012
|
+
function dequoteKey(key) {
|
|
1013
|
+
if (key.length >= 2 && key.startsWith('"') && key.endsWith('"')) {
|
|
1014
|
+
return key.slice(1, -1);
|
|
1015
|
+
}
|
|
1016
|
+
return key;
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Walk a JSON Schema alongside a parsed value, renaming any object key whose
|
|
1020
|
+
* de-quoted form matches a declared property name. Recurses into declared
|
|
1021
|
+
* object properties and array items so nested corrupted keys are also repaired.
|
|
1022
|
+
*/
|
|
1023
|
+
function repairFromSchema(schema, value) {
|
|
1024
|
+
if (!isPlainObject(schema)) {
|
|
1025
|
+
return value;
|
|
1026
|
+
}
|
|
1027
|
+
const type = schema.type;
|
|
1028
|
+
const isArray = type === "array" || (type === undefined && "items" in schema);
|
|
1029
|
+
if (isArray) {
|
|
1030
|
+
const items = schema.items;
|
|
1031
|
+
if (Array.isArray(value) && isPlainObject(items)) {
|
|
1032
|
+
return value.map((entry) => repairFromSchema(items, entry));
|
|
1033
|
+
}
|
|
1034
|
+
return value;
|
|
1035
|
+
}
|
|
1036
|
+
const isObject = type === "object" || (type === undefined && "properties" in schema);
|
|
1037
|
+
if (isObject && isPlainObject(value)) {
|
|
1038
|
+
const properties = schema.properties;
|
|
1039
|
+
if (isPlainObject(properties)) {
|
|
1040
|
+
const declared = new Set(Object.keys(properties));
|
|
1041
|
+
// Repair keys: rename a quote-wrapped key to its declared form, unless
|
|
1042
|
+
// the correct key is already present (then drop the corrupted duplicate).
|
|
1043
|
+
for (const key of Object.keys(value)) {
|
|
1044
|
+
if (declared.has(key)) {
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
const repaired = dequoteKey(key);
|
|
1048
|
+
if (repaired !== key && declared.has(repaired)) {
|
|
1049
|
+
if (!(repaired in value)) {
|
|
1050
|
+
value[repaired] = value[key];
|
|
1051
|
+
}
|
|
1052
|
+
delete value[key];
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
// Recurse into declared properties now that keys are aligned.
|
|
1056
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
1057
|
+
if (key in value) {
|
|
1058
|
+
value[key] = repairFromSchema(propSchema, value[key]);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
return value;
|
|
1063
|
+
}
|
|
1064
|
+
return value;
|
|
1065
|
+
}
|
|
1066
|
+
//
|
|
1067
|
+
//
|
|
1068
|
+
// Main
|
|
1069
|
+
//
|
|
1070
|
+
/**
|
|
1071
|
+
* Repair structured-output keys that a provider/model corrupted by wrapping
|
|
1072
|
+
* them in literal double quotes (observed on OpenAI for multi-word `format`
|
|
1073
|
+
* keys: `Merchant Request` returned as `"Merchant Request"`). A declared
|
|
1074
|
+
* `format` is a schema contract: returned keys should match the declared names
|
|
1075
|
+
* exactly. Any returned key whose de-quoted form matches a declared key is
|
|
1076
|
+
* renamed back to the declared key.
|
|
1077
|
+
*
|
|
1078
|
+
* Only mutates a (cloned) structured object; strings and non-objects pass
|
|
1079
|
+
* through untouched.
|
|
1080
|
+
*/
|
|
1081
|
+
function repairFormatKeys({ content, format, }) {
|
|
1082
|
+
const schema = formatToJsonSchema(format);
|
|
1083
|
+
if (!schema) {
|
|
1084
|
+
return content;
|
|
1085
|
+
}
|
|
1086
|
+
return repairFromSchema(schema, structuredClone(content));
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const DEFAULT_DONE_SENTINEL = "[DONE]";
|
|
1090
|
+
/**
|
|
1091
|
+
* Parse a Server-Sent Events stream into decoded JSON chunks. Buffers across
|
|
1092
|
+
* network reads, dispatches on newline-delimited `data:` lines, and stops at
|
|
1093
|
+
* the done sentinel (OpenAI-style `[DONE]`; Gemini omits it and simply ends
|
|
1094
|
+
* the stream). Comment lines (`: ...`, used for keep-alive) are ignored.
|
|
1095
|
+
*
|
|
1096
|
+
* Shared by the provider HTTP clients that replaced their vendor SDKs.
|
|
1097
|
+
*/
|
|
1098
|
+
async function* parseSseStream(body, { doneSentinel = DEFAULT_DONE_SENTINEL } = {}) {
|
|
1099
|
+
const reader = body.getReader();
|
|
1100
|
+
const decoder = new TextDecoder();
|
|
1101
|
+
let buffer = "";
|
|
1102
|
+
try {
|
|
1103
|
+
for (;;) {
|
|
1104
|
+
const { done, value } = await reader.read();
|
|
1105
|
+
if (done)
|
|
1106
|
+
break;
|
|
1107
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1108
|
+
let newlineIndex;
|
|
1109
|
+
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
1110
|
+
const line = buffer.slice(0, newlineIndex).replace(/\r$/, "");
|
|
1111
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
1112
|
+
if (line === "" || line.startsWith(":"))
|
|
1113
|
+
continue;
|
|
1114
|
+
if (!line.startsWith("data:"))
|
|
1115
|
+
continue;
|
|
1116
|
+
const data = line.slice("data:".length).trim();
|
|
1117
|
+
if (data === doneSentinel)
|
|
1118
|
+
return;
|
|
1119
|
+
yield JSON.parse(data);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
finally {
|
|
1124
|
+
reader.releaseLock();
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
981
1128
|
/**
|
|
982
1129
|
* Helper function to safely call a function that might throw
|
|
983
1130
|
* @param fn Function to call safely
|
|
@@ -1505,7 +1652,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
1505
1652
|
? [...request.tools]
|
|
1506
1653
|
: [];
|
|
1507
1654
|
if (useFallbackStructuredOutput && request.format) {
|
|
1508
|
-
log$1.warn(`[AnthropicAdapter] Using legacy structured_output tool fallback for model ${anthropicRequest.model}; native output_config previously rejected for this model.`);
|
|
1655
|
+
log$1.log.warn(`[AnthropicAdapter] Using legacy structured_output tool fallback for model ${anthropicRequest.model}; native output_config previously rejected for this model.`);
|
|
1509
1656
|
allTools.push({
|
|
1510
1657
|
name: STRUCTURED_OUTPUT_TOOL_NAME$3,
|
|
1511
1658
|
description: "Output a structured JSON object, " +
|
|
@@ -1620,7 +1767,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
1620
1767
|
if (wantsStructuredOutput && isStructuredOutputUnsupportedError$1(error)) {
|
|
1621
1768
|
const model = anthropicRequest.model;
|
|
1622
1769
|
this.rememberModelRejectsStructuredOutput(model);
|
|
1623
|
-
log$1.warn(`[AnthropicAdapter] Model ${model} rejected native output_config; falling back to legacy structured_output tool emulation.`);
|
|
1770
|
+
log$1.log.warn(`[AnthropicAdapter] Model ${model} rejected native output_config; falling back to legacy structured_output tool emulation.`);
|
|
1624
1771
|
const fallbackRequest = this.toFallbackStructuredOutputRequest(anthropicRequest);
|
|
1625
1772
|
return (await anthropic.messages.create(fallbackRequest, signal ? { signal } : undefined));
|
|
1626
1773
|
}
|
|
@@ -2683,7 +2830,7 @@ class GoogleAdapter extends BaseProviderAdapter {
|
|
|
2683
2830
|
? [...request.tools]
|
|
2684
2831
|
: [];
|
|
2685
2832
|
if (request.format && hasUserTools && !useNativeCombo) {
|
|
2686
|
-
log$1.warn(`[GoogleAdapter] Using legacy structured_output tool fallback for model ${geminiRequest.model}; native responseJsonSchema + tools combo is only available on Gemini 3.`);
|
|
2833
|
+
log$1.log.warn(`[GoogleAdapter] Using legacy structured_output tool fallback for model ${geminiRequest.model}; native responseJsonSchema + tools combo is only available on Gemini 3.`);
|
|
2687
2834
|
allTools.push({
|
|
2688
2835
|
name: STRUCTURED_OUTPUT_TOOL_NAME$1,
|
|
2689
2836
|
description: "Output a structured JSON object, " +
|
|
@@ -2814,7 +2961,7 @@ class GoogleAdapter extends BaseProviderAdapter {
|
|
|
2814
2961
|
if (wantsNativeCombo && isStructuredOutputComboUnsupportedError(error)) {
|
|
2815
2962
|
const model = geminiRequest.model;
|
|
2816
2963
|
this.rememberModelRejectsStructuredOutputCombo(model);
|
|
2817
|
-
log$1.warn(`[GoogleAdapter] Model ${model} rejected native responseJsonSchema + tools combo; falling back to legacy structured_output tool emulation.`);
|
|
2964
|
+
log$1.log.warn(`[GoogleAdapter] Model ${model} rejected native responseJsonSchema + tools combo; falling back to legacy structured_output tool emulation.`);
|
|
2818
2965
|
const fallbackRequest = this.toFallbackStructuredOutputRequest(geminiRequest);
|
|
2819
2966
|
const response = await genAI.models.generateContent({
|
|
2820
2967
|
model: fallbackRequest.model,
|
|
@@ -3398,6 +3545,206 @@ class GoogleAdapter extends BaseProviderAdapter {
|
|
|
3398
3545
|
// Export singleton instance
|
|
3399
3546
|
const googleAdapter = new GoogleAdapter();
|
|
3400
3547
|
|
|
3548
|
+
//
|
|
3549
|
+
//
|
|
3550
|
+
// Constants
|
|
3551
|
+
//
|
|
3552
|
+
const OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
3553
|
+
//
|
|
3554
|
+
//
|
|
3555
|
+
// Errors
|
|
3556
|
+
//
|
|
3557
|
+
// The adapter's `classifyError` uses `instanceof` against these classes, so the
|
|
3558
|
+
// client throws them directly. Status → class mapping mirrors the SDK; the
|
|
3559
|
+
// non-HTTP classes (connection/abort) are thrown when `fetch` itself rejects.
|
|
3560
|
+
//
|
|
3561
|
+
class OpenAiApiError extends Error {
|
|
3562
|
+
constructor(status, message, error) {
|
|
3563
|
+
super(message);
|
|
3564
|
+
this.name = this.constructor.name;
|
|
3565
|
+
this.status = status;
|
|
3566
|
+
this.error = error;
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
let BadRequestError$1 = class BadRequestError extends OpenAiApiError {
|
|
3570
|
+
};
|
|
3571
|
+
let AuthenticationError$1 = class AuthenticationError extends OpenAiApiError {
|
|
3572
|
+
};
|
|
3573
|
+
let PermissionDeniedError$1 = class PermissionDeniedError extends OpenAiApiError {
|
|
3574
|
+
};
|
|
3575
|
+
let NotFoundError$1 = class NotFoundError extends OpenAiApiError {
|
|
3576
|
+
};
|
|
3577
|
+
class ConflictError extends OpenAiApiError {
|
|
3578
|
+
}
|
|
3579
|
+
class UnprocessableEntityError extends OpenAiApiError {
|
|
3580
|
+
}
|
|
3581
|
+
let RateLimitError$1 = class RateLimitError extends OpenAiApiError {
|
|
3582
|
+
};
|
|
3583
|
+
let InternalServerError$1 = class InternalServerError extends OpenAiApiError {
|
|
3584
|
+
};
|
|
3585
|
+
class APIConnectionError extends OpenAiApiError {
|
|
3586
|
+
}
|
|
3587
|
+
class APIConnectionTimeoutError extends APIConnectionError {
|
|
3588
|
+
}
|
|
3589
|
+
class APIUserAbortError extends OpenAiApiError {
|
|
3590
|
+
}
|
|
3591
|
+
function errorForStatus$1(status, message, error) {
|
|
3592
|
+
if (status === 400)
|
|
3593
|
+
return new BadRequestError$1(status, message, error);
|
|
3594
|
+
if (status === 401)
|
|
3595
|
+
return new AuthenticationError$1(status, message, error);
|
|
3596
|
+
if (status === 403)
|
|
3597
|
+
return new PermissionDeniedError$1(status, message, error);
|
|
3598
|
+
if (status === 404)
|
|
3599
|
+
return new NotFoundError$1(status, message, error);
|
|
3600
|
+
if (status === 409)
|
|
3601
|
+
return new ConflictError(status, message, error);
|
|
3602
|
+
if (status === 422) {
|
|
3603
|
+
return new UnprocessableEntityError(status, message, error);
|
|
3604
|
+
}
|
|
3605
|
+
if (status === 429)
|
|
3606
|
+
return new RateLimitError$1(status, message, error);
|
|
3607
|
+
if (status >= 500)
|
|
3608
|
+
return new InternalServerError$1(status, message, error);
|
|
3609
|
+
return new OpenAiApiError(status, message, error);
|
|
3610
|
+
}
|
|
3611
|
+
//
|
|
3612
|
+
//
|
|
3613
|
+
// Main
|
|
3614
|
+
//
|
|
3615
|
+
/**
|
|
3616
|
+
* Minimal `fetch`-based client for the OpenAI API. Replaces the `openai` SDK —
|
|
3617
|
+
* the adapters and provider utilities only need the Responses API
|
|
3618
|
+
* (`/responses`, streaming and non-streaming) for `operate`/`stream` and the
|
|
3619
|
+
* Chat Completions API (`/chat/completions`, plus a `parse` helper for
|
|
3620
|
+
* structured output) for `send`. Mirrors the SDK's `responses.create` and
|
|
3621
|
+
* `chat.completions.create` / `chat.completions.parse` surface so call sites are
|
|
3622
|
+
* unchanged. Also serves xAI via a custom `baseURL`.
|
|
3623
|
+
*/
|
|
3624
|
+
class OpenAIClient {
|
|
3625
|
+
constructor({ apiKey, baseURL = OPENAI_BASE_URL }) {
|
|
3626
|
+
this.apiKey = apiKey;
|
|
3627
|
+
this.baseURL = baseURL;
|
|
3628
|
+
this.responses = {
|
|
3629
|
+
create: ((params, options) => params.stream
|
|
3630
|
+
? this.createResponseStream(params, options)
|
|
3631
|
+
: this.createResponse(params, options)),
|
|
3632
|
+
};
|
|
3633
|
+
this.chat = {
|
|
3634
|
+
completions: {
|
|
3635
|
+
create: (params, options) => this.chatCompletion(params, options, { parse: false }),
|
|
3636
|
+
parse: (params, options) => this.chatCompletion(params, options, { parse: true }),
|
|
3637
|
+
},
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3640
|
+
headers(extra) {
|
|
3641
|
+
return {
|
|
3642
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
3643
|
+
"Content-Type": "application/json",
|
|
3644
|
+
...extra,
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3647
|
+
async post(path, body, { signal } = {}, { stream = false } = {}) {
|
|
3648
|
+
let response;
|
|
3649
|
+
try {
|
|
3650
|
+
response = await fetch(`${this.baseURL}${path}`, {
|
|
3651
|
+
method: "POST",
|
|
3652
|
+
headers: this.headers(stream ? { Accept: "text/event-stream" } : undefined),
|
|
3653
|
+
body: JSON.stringify(body),
|
|
3654
|
+
signal,
|
|
3655
|
+
});
|
|
3656
|
+
}
|
|
3657
|
+
catch (cause) {
|
|
3658
|
+
if (signal?.aborted) {
|
|
3659
|
+
throw new APIUserAbortError(0, "Request was aborted");
|
|
3660
|
+
}
|
|
3661
|
+
const message = cause instanceof Error ? cause.message : "Connection error";
|
|
3662
|
+
const error = new APIConnectionError(0, message);
|
|
3663
|
+
error.cause = cause;
|
|
3664
|
+
throw error;
|
|
3665
|
+
}
|
|
3666
|
+
if (!response.ok)
|
|
3667
|
+
throw await this.toError(response);
|
|
3668
|
+
return response;
|
|
3669
|
+
}
|
|
3670
|
+
async toError(response) {
|
|
3671
|
+
let message = `OpenAI request failed with status ${response.status}`;
|
|
3672
|
+
let error;
|
|
3673
|
+
try {
|
|
3674
|
+
const body = (await response.json());
|
|
3675
|
+
if (body?.error?.message) {
|
|
3676
|
+
message = body.error.message;
|
|
3677
|
+
error = body.error;
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
catch {
|
|
3681
|
+
// Non-JSON error body; keep the status-based message.
|
|
3682
|
+
}
|
|
3683
|
+
return errorForStatus$1(response.status, message, error);
|
|
3684
|
+
}
|
|
3685
|
+
async createResponse(params, options) {
|
|
3686
|
+
const response = await this.post("/responses", params, options);
|
|
3687
|
+
return (await response.json());
|
|
3688
|
+
}
|
|
3689
|
+
async createResponseStream(params, options) {
|
|
3690
|
+
const response = await this.post("/responses", params, options, {
|
|
3691
|
+
stream: true,
|
|
3692
|
+
});
|
|
3693
|
+
if (!response.body)
|
|
3694
|
+
return (async function* () { })();
|
|
3695
|
+
// The Responses API streams typed events and does not emit a `[DONE]`
|
|
3696
|
+
// sentinel; the empty default sentinel never matches, so the stream ends
|
|
3697
|
+
// when the connection closes.
|
|
3698
|
+
return parseSseStream(response.body);
|
|
3699
|
+
}
|
|
3700
|
+
async chatCompletion(params, options, { parse }) {
|
|
3701
|
+
const response = await this.post("/chat/completions", params, options);
|
|
3702
|
+
const json = (await response.json());
|
|
3703
|
+
// `chat.completions.parse` surfaces parsed JSON on each message; replicate
|
|
3704
|
+
// the SDK by JSON-parsing the assistant content into `message.parsed`.
|
|
3705
|
+
if (parse) {
|
|
3706
|
+
const choices = json.choices;
|
|
3707
|
+
if (Array.isArray(choices)) {
|
|
3708
|
+
for (const choice of choices) {
|
|
3709
|
+
const message = choice.message;
|
|
3710
|
+
if (message &&
|
|
3711
|
+
typeof message.content === "string" &&
|
|
3712
|
+
!message.refusal) {
|
|
3713
|
+
try {
|
|
3714
|
+
message.parsed = JSON.parse(message.content);
|
|
3715
|
+
}
|
|
3716
|
+
catch {
|
|
3717
|
+
message.parsed = null;
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
return json;
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
//
|
|
3728
|
+
//
|
|
3729
|
+
// Main
|
|
3730
|
+
//
|
|
3731
|
+
/**
|
|
3732
|
+
* Local replacement for `openai/helpers/zod`'s `zodResponseFormat`. Builds the
|
|
3733
|
+
* `response_format` payload OpenAI's Chat Completions / Responses APIs expect
|
|
3734
|
+
* from a Zod schema. The SDK always emits `strict: true`; callers re-derive or
|
|
3735
|
+
* sanitize `schema` as needed (e.g. forcing `additionalProperties: false`).
|
|
3736
|
+
*/
|
|
3737
|
+
function zodResponseFormat(schema, name) {
|
|
3738
|
+
return {
|
|
3739
|
+
type: "json_schema",
|
|
3740
|
+
json_schema: {
|
|
3741
|
+
name,
|
|
3742
|
+
strict: true,
|
|
3743
|
+
schema: v4.z.toJSONSchema(schema),
|
|
3744
|
+
},
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3401
3748
|
// Patterns for OpenAI reasoning models that support extended thinking
|
|
3402
3749
|
const REASONING_MODEL_PATTERNS = [
|
|
3403
3750
|
/^gpt-[5-9]/, // GPT-5 and above (gpt-5, gpt-5.1, gpt-5.2, gpt-6, etc.)
|
|
@@ -3414,19 +3761,19 @@ function isReasoningModel(model) {
|
|
|
3414
3761
|
// Constants
|
|
3415
3762
|
//
|
|
3416
3763
|
const RETRYABLE_ERROR_TYPES = [
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3764
|
+
APIConnectionError,
|
|
3765
|
+
APIConnectionTimeoutError,
|
|
3766
|
+
InternalServerError$1,
|
|
3420
3767
|
];
|
|
3421
3768
|
const NOT_RETRYABLE_ERROR_TYPES = [
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3769
|
+
APIUserAbortError,
|
|
3770
|
+
AuthenticationError$1,
|
|
3771
|
+
BadRequestError$1,
|
|
3772
|
+
ConflictError,
|
|
3773
|
+
NotFoundError$1,
|
|
3774
|
+
PermissionDeniedError$1,
|
|
3775
|
+
RateLimitError$1,
|
|
3776
|
+
UnprocessableEntityError,
|
|
3430
3777
|
];
|
|
3431
3778
|
// Models known not to accept `temperature`.
|
|
3432
3779
|
// Patterns (not exact names) so dated variants and future releases are covered
|
|
@@ -3439,7 +3786,7 @@ const MODELS_WITHOUT_TEMPERATURE$1 = [
|
|
|
3439
3786
|
function isTemperatureDeprecationError$1(error) {
|
|
3440
3787
|
if (!error || typeof error !== "object")
|
|
3441
3788
|
return false;
|
|
3442
|
-
if (!(error instanceof
|
|
3789
|
+
if (!(error instanceof BadRequestError$1) &&
|
|
3443
3790
|
error.status !== 400) {
|
|
3444
3791
|
return false;
|
|
3445
3792
|
}
|
|
@@ -3546,8 +3893,17 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
3546
3893
|
const zodSchema = schema instanceof v4.z.ZodType
|
|
3547
3894
|
? schema
|
|
3548
3895
|
: naturalZodSchema(schema);
|
|
3549
|
-
const responseFormat =
|
|
3550
|
-
|
|
3896
|
+
const responseFormat = zodResponseFormat(zodSchema, "response");
|
|
3897
|
+
// Re-spread to drop zod v4's non-enumerable `~standard` Standard-Schema
|
|
3898
|
+
// interop marker, then strip `$schema`: OpenAI's strict json_schema subset
|
|
3899
|
+
// does not recognize the draft-2020-12 `$schema` keyword, and shipping it
|
|
3900
|
+
// can leave strict structured-output enforcement silently disabled (the
|
|
3901
|
+
// model then free-forms, omitting required fields and corrupting keys).
|
|
3902
|
+
// Mirrors the OpenRouter adapter's sanitization.
|
|
3903
|
+
const jsonSchema = {
|
|
3904
|
+
...v4.z.toJSONSchema(zodSchema),
|
|
3905
|
+
};
|
|
3906
|
+
delete jsonSchema.$schema;
|
|
3551
3907
|
// OpenAI requires additionalProperties to be false on all objects
|
|
3552
3908
|
const checks = [jsonSchema];
|
|
3553
3909
|
while (checks.length > 0) {
|
|
@@ -3784,7 +4140,7 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
3784
4140
|
//
|
|
3785
4141
|
classifyError(error) {
|
|
3786
4142
|
// Check for rate limit error
|
|
3787
|
-
if (error instanceof
|
|
4143
|
+
if (error instanceof RateLimitError$1) {
|
|
3788
4144
|
return {
|
|
3789
4145
|
error,
|
|
3790
4146
|
category: ErrorCategory.RateLimit,
|
|
@@ -4002,7 +4358,49 @@ function convertContentToOpenRouter(content) {
|
|
|
4002
4358
|
}
|
|
4003
4359
|
return parts;
|
|
4004
4360
|
}
|
|
4005
|
-
|
|
4361
|
+
/**
|
|
4362
|
+
* Convert internal content parts to the OpenAI-compatible wire shape. The
|
|
4363
|
+
* internal representation uses camelCase keys (`imageUrl`, `fileData`) that the
|
|
4364
|
+
* SDK accepted; the REST API wants snake_case (`image_url`, `file_data`).
|
|
4365
|
+
*/
|
|
4366
|
+
function contentToWire(content) {
|
|
4367
|
+
if (content === null ||
|
|
4368
|
+
content === undefined ||
|
|
4369
|
+
typeof content === "string") {
|
|
4370
|
+
return content;
|
|
4371
|
+
}
|
|
4372
|
+
return content.map((part) => {
|
|
4373
|
+
if (part.type === "image_url") {
|
|
4374
|
+
return { type: "image_url", image_url: part.imageUrl };
|
|
4375
|
+
}
|
|
4376
|
+
if (part.type === "file") {
|
|
4377
|
+
return {
|
|
4378
|
+
type: "file",
|
|
4379
|
+
file: { filename: part.file.filename, file_data: part.file.fileData },
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
return part;
|
|
4383
|
+
});
|
|
4384
|
+
}
|
|
4385
|
+
/**
|
|
4386
|
+
* Serialize an internal message to the OpenAI-compatible wire shape, mapping
|
|
4387
|
+
* camelCase tool fields (`toolCalls`, `toolCallId`) to snake_case. Tool-call
|
|
4388
|
+
* objects are already wire-shaped (`{ id, type, function: { name, arguments } }`).
|
|
4389
|
+
*/
|
|
4390
|
+
function messageToWire(message) {
|
|
4391
|
+
const wire = { role: message.role };
|
|
4392
|
+
if (message.content !== undefined) {
|
|
4393
|
+
wire.content = contentToWire(message.content);
|
|
4394
|
+
}
|
|
4395
|
+
if (message.toolCalls) {
|
|
4396
|
+
wire.tool_calls = message.toolCalls;
|
|
4397
|
+
}
|
|
4398
|
+
if (message.toolCallId) {
|
|
4399
|
+
wire.tool_call_id = message.toolCallId;
|
|
4400
|
+
}
|
|
4401
|
+
return wire;
|
|
4402
|
+
}
|
|
4403
|
+
// OpenRouter error types based on HTTP status codes
|
|
4006
4404
|
const RETRYABLE_STATUS_CODES = [408, 500, 502, 503, 524, 529];
|
|
4007
4405
|
const RATE_LIMIT_STATUS_CODE = 429;
|
|
4008
4406
|
/**
|
|
@@ -4205,7 +4603,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
4205
4603
|
const openRouterRequest = request;
|
|
4206
4604
|
const wantsStructuredOutput = Boolean(openRouterRequest.response_format);
|
|
4207
4605
|
try {
|
|
4208
|
-
const response = (await openRouter.
|
|
4606
|
+
const response = (await openRouter.chatCompletion(this.toWireBody(openRouterRequest), signal ? { signal } : undefined));
|
|
4209
4607
|
if (wantsStructuredOutput) {
|
|
4210
4608
|
response.__jaypieStructuredOutput = true;
|
|
4211
4609
|
}
|
|
@@ -4222,7 +4620,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
4222
4620
|
this.rememberModelRejectsTemperature(openRouterRequest.model);
|
|
4223
4621
|
const retryRequest = { ...openRouterRequest };
|
|
4224
4622
|
delete retryRequest.temperature;
|
|
4225
|
-
const response = (await openRouter.
|
|
4623
|
+
const response = (await openRouter.chatCompletion(this.toWireBody(retryRequest), signal ? { signal } : undefined));
|
|
4226
4624
|
if (wantsStructuredOutput) {
|
|
4227
4625
|
response.__jaypieStructuredOutput = true;
|
|
4228
4626
|
}
|
|
@@ -4235,7 +4633,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
4235
4633
|
this.rememberModelRejectsStructuredOutput(model);
|
|
4236
4634
|
log$1.log.warn(`[OpenRouterAdapter] Model ${model} rejected native response_format; falling back to legacy structured_output tool emulation.`);
|
|
4237
4635
|
const fallbackRequest = this.toFallbackStructuredOutputRequest(openRouterRequest);
|
|
4238
|
-
return (await openRouter.
|
|
4636
|
+
return (await openRouter.chatCompletion(this.toWireBody(fallbackRequest), signal ? { signal } : undefined));
|
|
4239
4637
|
}
|
|
4240
4638
|
throw error;
|
|
4241
4639
|
}
|
|
@@ -4245,31 +4643,18 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
4245
4643
|
* camelCase shape, forwarding only the fields we care about (the SDK
|
|
4246
4644
|
* silently strips unknown fields).
|
|
4247
4645
|
*/
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4646
|
+
/**
|
|
4647
|
+
* Serialize the internal request into the OpenAI-compatible wire body for
|
|
4648
|
+
* OpenRouter's Chat Completions endpoint. Top-level fields (model, tools,
|
|
4649
|
+
* tool_choice, response_format, user, temperature, and any providerOptions)
|
|
4650
|
+
* are already wire-shaped (snake_case); only messages carry camelCase tool
|
|
4651
|
+
* fields that must become snake_case on the wire.
|
|
4652
|
+
*/
|
|
4653
|
+
toWireBody(openRouterRequest) {
|
|
4654
|
+
return {
|
|
4655
|
+
...openRouterRequest,
|
|
4656
|
+
messages: openRouterRequest.messages.map(messageToWire),
|
|
4255
4657
|
};
|
|
4256
|
-
if (openRouterRequest.response_format) {
|
|
4257
|
-
const format = openRouterRequest.response_format;
|
|
4258
|
-
if (format.type === "json_schema") {
|
|
4259
|
-
params.responseFormat = {
|
|
4260
|
-
type: "json_schema",
|
|
4261
|
-
jsonSchema: format.json_schema,
|
|
4262
|
-
};
|
|
4263
|
-
}
|
|
4264
|
-
else {
|
|
4265
|
-
params.responseFormat = format;
|
|
4266
|
-
}
|
|
4267
|
-
}
|
|
4268
|
-
const temperature = openRouterRequest.temperature;
|
|
4269
|
-
if (temperature !== undefined) {
|
|
4270
|
-
params.temperature = temperature;
|
|
4271
|
-
}
|
|
4272
|
-
return params;
|
|
4273
4658
|
}
|
|
4274
4659
|
/**
|
|
4275
4660
|
* Rebuild a structured-output request without `response_format`, swapping in
|
|
@@ -4301,15 +4686,9 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
4301
4686
|
async *executeStreamRequest(client, request, signal) {
|
|
4302
4687
|
const openRouter = client;
|
|
4303
4688
|
const openRouterRequest = request;
|
|
4304
|
-
//
|
|
4305
|
-
//
|
|
4306
|
-
|
|
4307
|
-
// non-stream response.
|
|
4308
|
-
const streamParams = {
|
|
4309
|
-
...this.toSdkChatParams(openRouterRequest),
|
|
4310
|
-
stream: true,
|
|
4311
|
-
};
|
|
4312
|
-
const stream = (await openRouter.chat.send(streamParams, signal ? { signal } : undefined));
|
|
4689
|
+
// streamChatCompletion adds `stream: true` + `stream_options.include_usage`
|
|
4690
|
+
// and yields decoded SSE chunks in OpenAI-compatible (snake_case) shape.
|
|
4691
|
+
const stream = openRouter.streamChatCompletion(this.toWireBody(openRouterRequest), signal ? { signal } : undefined);
|
|
4313
4692
|
// Track current tool call being built
|
|
4314
4693
|
let currentToolCall = null;
|
|
4315
4694
|
// Track usage for final chunk
|
|
@@ -4758,7 +5137,7 @@ const TRANSIENT_INGEST_MESSAGE_PATTERNS = [
|
|
|
4758
5137
|
"failed to ingest inline file bytes",
|
|
4759
5138
|
];
|
|
4760
5139
|
function isTransientIngestError(error) {
|
|
4761
|
-
if (!(error instanceof
|
|
5140
|
+
if (!(error instanceof BadRequestError$1))
|
|
4762
5141
|
return false;
|
|
4763
5142
|
const message = (error.message ?? "").toLowerCase();
|
|
4764
5143
|
return TRANSIENT_INGEST_MESSAGE_PATTERNS.some((pattern) => message.includes(pattern));
|
|
@@ -4950,7 +5329,7 @@ const requireModule = typeof __filename !== "undefined"
|
|
|
4950
5329
|
? module$1.createRequire(url.pathToFileURL(__filename).href)
|
|
4951
5330
|
: module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
4952
5331
|
let resolved = false;
|
|
4953
|
-
let cachedSdk$
|
|
5332
|
+
let cachedSdk$1 = null;
|
|
4954
5333
|
/**
|
|
4955
5334
|
* Lazily resolve the dd-trace `llmobs` SDK. Returns null (and never throws)
|
|
4956
5335
|
* when dd-trace is absent or the SDK surface is unexpected. Cached after the
|
|
@@ -4958,7 +5337,7 @@ let cachedSdk$4 = null;
|
|
|
4958
5337
|
*/
|
|
4959
5338
|
function resolveLlmObs() {
|
|
4960
5339
|
if (resolved) {
|
|
4961
|
-
return cachedSdk$
|
|
5340
|
+
return cachedSdk$1;
|
|
4962
5341
|
}
|
|
4963
5342
|
resolved = true;
|
|
4964
5343
|
try {
|
|
@@ -4968,13 +5347,13 @@ function resolveLlmObs() {
|
|
|
4968
5347
|
if (llmobs &&
|
|
4969
5348
|
typeof llmobs.trace === "function" &&
|
|
4970
5349
|
typeof llmobs.annotate === "function") {
|
|
4971
|
-
cachedSdk$
|
|
5350
|
+
cachedSdk$1 = llmobs;
|
|
4972
5351
|
}
|
|
4973
5352
|
}
|
|
4974
5353
|
catch {
|
|
4975
|
-
cachedSdk$
|
|
5354
|
+
cachedSdk$1 = null;
|
|
4976
5355
|
}
|
|
4977
|
-
return cachedSdk$
|
|
5356
|
+
return cachedSdk$1;
|
|
4978
5357
|
}
|
|
4979
5358
|
//
|
|
4980
5359
|
//
|
|
@@ -6313,9 +6692,12 @@ class OperateLoop {
|
|
|
6313
6692
|
return false; // Stop loop
|
|
6314
6693
|
}
|
|
6315
6694
|
/**
|
|
6316
|
-
*
|
|
6317
|
-
* `format` is a schema contract:
|
|
6318
|
-
*
|
|
6695
|
+
* Reconcile structured output against the declared `format` contract. A
|
|
6696
|
+
* declared `format` is a schema contract: returned keys should match the
|
|
6697
|
+
* declared names exactly and every declared array field should be present.
|
|
6698
|
+
* First repairs keys a provider/model corrupted by wrapping them in literal
|
|
6699
|
+
* double quotes (observed on OpenAI for multi-word keys), then backfills any
|
|
6700
|
+
* declared array field a provider/model omitted, surfacing it as `[]`.
|
|
6319
6701
|
*/
|
|
6320
6702
|
applyFormatArrayDefaults(content, options) {
|
|
6321
6703
|
if (!options.format) {
|
|
@@ -6324,7 +6706,8 @@ class OperateLoop {
|
|
|
6324
6706
|
if (typeof content !== "object" || content === null) {
|
|
6325
6707
|
return content;
|
|
6326
6708
|
}
|
|
6327
|
-
|
|
6709
|
+
const repaired = repairFormatKeys({ content, format: options.format });
|
|
6710
|
+
return fillFormatArrays({ content: repaired, format: options.format });
|
|
6328
6711
|
}
|
|
6329
6712
|
/**
|
|
6330
6713
|
* Sync the current input state from the updated provider request.
|
|
@@ -6884,19 +7267,125 @@ function createStreamLoop(config) {
|
|
|
6884
7267
|
return new StreamLoop(config);
|
|
6885
7268
|
}
|
|
6886
7269
|
|
|
6887
|
-
//
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
7270
|
+
//
|
|
7271
|
+
//
|
|
7272
|
+
// Constants
|
|
7273
|
+
//
|
|
7274
|
+
const ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
|
|
7275
|
+
const ANTHROPIC_VERSION = "2023-06-01";
|
|
7276
|
+
//
|
|
7277
|
+
//
|
|
7278
|
+
// Errors
|
|
7279
|
+
//
|
|
7280
|
+
// The adapter's `classifyError` keys off `error.constructor.name` (the SDK's
|
|
7281
|
+
// class names), so the client throws errors whose class names match. Status →
|
|
7282
|
+
// class mapping mirrors the SDK.
|
|
7283
|
+
//
|
|
7284
|
+
class AnthropicApiError extends Error {
|
|
7285
|
+
constructor(status, message, error) {
|
|
7286
|
+
super(message);
|
|
7287
|
+
this.name = this.constructor.name;
|
|
7288
|
+
this.status = status;
|
|
7289
|
+
this.error = error;
|
|
6895
7290
|
}
|
|
6896
|
-
|
|
6897
|
-
|
|
7291
|
+
}
|
|
7292
|
+
class BadRequestError extends AnthropicApiError {
|
|
7293
|
+
}
|
|
7294
|
+
class AuthenticationError extends AnthropicApiError {
|
|
7295
|
+
}
|
|
7296
|
+
class PermissionDeniedError extends AnthropicApiError {
|
|
7297
|
+
}
|
|
7298
|
+
class NotFoundError extends AnthropicApiError {
|
|
7299
|
+
}
|
|
7300
|
+
class RateLimitError extends AnthropicApiError {
|
|
7301
|
+
}
|
|
7302
|
+
class InternalServerError extends AnthropicApiError {
|
|
7303
|
+
}
|
|
7304
|
+
function errorForStatus(status, message, error) {
|
|
7305
|
+
if (status === 400)
|
|
7306
|
+
return new BadRequestError(status, message, error);
|
|
7307
|
+
if (status === 401)
|
|
7308
|
+
return new AuthenticationError(status, message, error);
|
|
7309
|
+
if (status === 403)
|
|
7310
|
+
return new PermissionDeniedError(status, message, error);
|
|
7311
|
+
if (status === 404)
|
|
7312
|
+
return new NotFoundError(status, message, error);
|
|
7313
|
+
if (status === 429)
|
|
7314
|
+
return new RateLimitError(status, message, error);
|
|
7315
|
+
if (status >= 500)
|
|
7316
|
+
return new InternalServerError(status, message, error);
|
|
7317
|
+
return new AnthropicApiError(status, message, error);
|
|
7318
|
+
}
|
|
7319
|
+
//
|
|
7320
|
+
//
|
|
7321
|
+
// Main
|
|
7322
|
+
//
|
|
7323
|
+
/**
|
|
7324
|
+
* Minimal `fetch`-based client for Anthropic's Messages API. Replaces
|
|
7325
|
+
* `@anthropic-ai/sdk` — the adapter only needs `messages.create` (streaming and
|
|
7326
|
+
* non-streaming), header auth, and HTTP errors whose class names drive
|
|
7327
|
+
* `classifyError`. Internal request params are already Anthropic's wire shape,
|
|
7328
|
+
* so they serialize verbatim. Exposes a `messages` facade mirroring the SDK so
|
|
7329
|
+
* the adapter call sites are unchanged.
|
|
7330
|
+
*/
|
|
7331
|
+
class AnthropicClient {
|
|
7332
|
+
constructor({ apiKey, baseURL = ANTHROPIC_BASE_URL, }) {
|
|
7333
|
+
this.apiKey = apiKey;
|
|
7334
|
+
this.baseURL = baseURL;
|
|
7335
|
+
this.messages = {
|
|
7336
|
+
create: ((params, options) => params.stream
|
|
7337
|
+
? this.createStream(params, options)
|
|
7338
|
+
: this.createMessage(params, options)),
|
|
7339
|
+
};
|
|
7340
|
+
}
|
|
7341
|
+
headers() {
|
|
7342
|
+
return {
|
|
7343
|
+
"anthropic-version": ANTHROPIC_VERSION,
|
|
7344
|
+
"content-type": "application/json",
|
|
7345
|
+
"x-api-key": this.apiKey,
|
|
7346
|
+
};
|
|
7347
|
+
}
|
|
7348
|
+
async toError(response) {
|
|
7349
|
+
let message = `Anthropic request failed with status ${response.status}`;
|
|
7350
|
+
let error;
|
|
7351
|
+
try {
|
|
7352
|
+
const body = (await response.json());
|
|
7353
|
+
if (body?.error?.message) {
|
|
7354
|
+
message = body.error.message;
|
|
7355
|
+
error = body.error;
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
7358
|
+
catch {
|
|
7359
|
+
// Non-JSON error body; keep the status-based message.
|
|
7360
|
+
}
|
|
7361
|
+
return errorForStatus(response.status, message, error);
|
|
7362
|
+
}
|
|
7363
|
+
async createMessage(params, { signal } = {}) {
|
|
7364
|
+
const response = await fetch(`${this.baseURL}/messages`, {
|
|
7365
|
+
method: "POST",
|
|
7366
|
+
headers: this.headers(),
|
|
7367
|
+
body: JSON.stringify(params),
|
|
7368
|
+
signal,
|
|
7369
|
+
});
|
|
7370
|
+
if (!response.ok)
|
|
7371
|
+
throw await this.toError(response);
|
|
7372
|
+
return (await response.json());
|
|
7373
|
+
}
|
|
7374
|
+
async createStream(params, { signal } = {}) {
|
|
7375
|
+
const response = await fetch(`${this.baseURL}/messages`, {
|
|
7376
|
+
method: "POST",
|
|
7377
|
+
headers: { ...this.headers(), accept: "text/event-stream" },
|
|
7378
|
+
body: JSON.stringify(params),
|
|
7379
|
+
signal,
|
|
7380
|
+
});
|
|
7381
|
+
if (!response.ok)
|
|
7382
|
+
throw await this.toError(response);
|
|
7383
|
+
if (!response.body)
|
|
7384
|
+
return (async function* () { })();
|
|
7385
|
+
return parseSseStream(response.body);
|
|
6898
7386
|
}
|
|
6899
7387
|
}
|
|
7388
|
+
|
|
6900
7389
|
// Logger
|
|
6901
7390
|
const getLogger$5 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
|
|
6902
7391
|
// Client initialization
|
|
@@ -6906,8 +7395,7 @@ async function initializeClient$5({ apiKey, } = {}) {
|
|
|
6906
7395
|
if (!resolvedApiKey) {
|
|
6907
7396
|
throw new errors.ConfigurationError("The application could not resolve the required API key: ANTHROPIC_API_KEY");
|
|
6908
7397
|
}
|
|
6909
|
-
const
|
|
6910
|
-
const client = new sdk.default({ apiKey: resolvedApiKey });
|
|
7398
|
+
const client = new AnthropicClient({ apiKey: resolvedApiKey });
|
|
6911
7399
|
logger.trace("Initialized Anthropic client");
|
|
6912
7400
|
return client;
|
|
6913
7401
|
}
|
|
@@ -7007,6 +7495,7 @@ async function createStructuredCompletion$1(client, messages, model, responseSch
|
|
|
7007
7495
|
[exports.LlmMessageRole.Assistant]: PROVIDER.ANTHROPIC.ROLE.ASSISTANT,
|
|
7008
7496
|
[exports.LlmMessageRole.Developer]: PROVIDER.ANTHROPIC.ROLE.SYSTEM,
|
|
7009
7497
|
});
|
|
7498
|
+
/* eslint-enable @typescript-eslint/no-namespace */
|
|
7010
7499
|
|
|
7011
7500
|
// Main class implementation
|
|
7012
7501
|
class AnthropicProvider {
|
|
@@ -7095,13 +7584,13 @@ class AnthropicProvider {
|
|
|
7095
7584
|
}
|
|
7096
7585
|
}
|
|
7097
7586
|
|
|
7098
|
-
let cachedSdk
|
|
7099
|
-
async function loadSdk
|
|
7100
|
-
if (cachedSdk
|
|
7101
|
-
return cachedSdk
|
|
7587
|
+
let cachedSdk = null;
|
|
7588
|
+
async function loadSdk() {
|
|
7589
|
+
if (cachedSdk)
|
|
7590
|
+
return cachedSdk;
|
|
7102
7591
|
try {
|
|
7103
|
-
cachedSdk
|
|
7104
|
-
return cachedSdk
|
|
7592
|
+
cachedSdk = await import('@aws-sdk/client-bedrock-runtime');
|
|
7593
|
+
return cachedSdk;
|
|
7105
7594
|
}
|
|
7106
7595
|
catch {
|
|
7107
7596
|
throw new errors.ConfigurationError("@aws-sdk/client-bedrock-runtime is required but not installed. Run: npm install @aws-sdk/client-bedrock-runtime");
|
|
@@ -7111,7 +7600,7 @@ const getLogger$4 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
|
|
|
7111
7600
|
async function initializeClient$4({ region, } = {}) {
|
|
7112
7601
|
const logger = getLogger$4();
|
|
7113
7602
|
const resolvedRegion = region || process.env.AWS_REGION || "us-east-1";
|
|
7114
|
-
const sdk = await loadSdk
|
|
7603
|
+
const sdk = await loadSdk();
|
|
7115
7604
|
const client = new sdk.BedrockRuntimeClient({ region: resolvedRegion });
|
|
7116
7605
|
logger.trace("Initialized Bedrock client");
|
|
7117
7606
|
return client;
|
|
@@ -7176,19 +7665,156 @@ class BedrockProvider {
|
|
|
7176
7665
|
}
|
|
7177
7666
|
}
|
|
7178
7667
|
|
|
7179
|
-
//
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7668
|
+
//
|
|
7669
|
+
//
|
|
7670
|
+
// Constants
|
|
7671
|
+
//
|
|
7672
|
+
const GOOGLE_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
7673
|
+
// Config keys the REST API expects at the top level of the request body. Every
|
|
7674
|
+
// other key in the SDK-style `config` object belongs under `generationConfig`.
|
|
7675
|
+
const TOP_LEVEL_CONFIG_KEYS = new Set([
|
|
7676
|
+
"systemInstruction",
|
|
7677
|
+
"tools",
|
|
7678
|
+
"toolConfig",
|
|
7679
|
+
"safetySettings",
|
|
7680
|
+
"cachedContent",
|
|
7681
|
+
]);
|
|
7682
|
+
/**
|
|
7683
|
+
* HTTP error carrying the upstream status and parsed API message. The
|
|
7684
|
+
* GoogleAdapter classifies errors by reading `.status` / `.code` and
|
|
7685
|
+
* `.message`, so this shape keeps `classifyError` working unchanged after
|
|
7686
|
+
* dropping the SDK.
|
|
7687
|
+
*/
|
|
7688
|
+
class GoogleHttpError extends Error {
|
|
7689
|
+
constructor(status, message) {
|
|
7690
|
+
super(message);
|
|
7691
|
+
this.name = "GoogleHttpError";
|
|
7692
|
+
this.status = status;
|
|
7693
|
+
this.code = status;
|
|
7187
7694
|
}
|
|
7188
|
-
|
|
7189
|
-
|
|
7695
|
+
}
|
|
7696
|
+
//
|
|
7697
|
+
//
|
|
7698
|
+
// Helpers
|
|
7699
|
+
//
|
|
7700
|
+
/**
|
|
7701
|
+
* Translate the SDK-style `{ model, contents, config }` request into the REST
|
|
7702
|
+
* `generateContent` body. `systemInstruction` (a string) becomes a Content;
|
|
7703
|
+
* top-level config keys pass through; all remaining config keys (temperature,
|
|
7704
|
+
* responseMimeType, responseSchema, responseJsonSchema, ...) move under
|
|
7705
|
+
* `generationConfig`.
|
|
7706
|
+
*/
|
|
7707
|
+
function toRestBody(params) {
|
|
7708
|
+
const body = {
|
|
7709
|
+
contents: params.contents,
|
|
7710
|
+
};
|
|
7711
|
+
const config = params.config;
|
|
7712
|
+
if (!config)
|
|
7713
|
+
return body;
|
|
7714
|
+
const generationConfig = {};
|
|
7715
|
+
for (const [key, value] of Object.entries(config)) {
|
|
7716
|
+
if (value === undefined)
|
|
7717
|
+
continue;
|
|
7718
|
+
if (key === "systemInstruction") {
|
|
7719
|
+
body.systemInstruction =
|
|
7720
|
+
typeof value === "string"
|
|
7721
|
+
? { parts: [{ text: value }] }
|
|
7722
|
+
: value;
|
|
7723
|
+
}
|
|
7724
|
+
else if (TOP_LEVEL_CONFIG_KEYS.has(key)) {
|
|
7725
|
+
body[key] = value;
|
|
7726
|
+
}
|
|
7727
|
+
else {
|
|
7728
|
+
generationConfig[key] = value;
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7731
|
+
if (Object.keys(generationConfig).length > 0) {
|
|
7732
|
+
body.generationConfig = generationConfig;
|
|
7733
|
+
}
|
|
7734
|
+
return body;
|
|
7735
|
+
}
|
|
7736
|
+
/**
|
|
7737
|
+
* Concatenate the non-thought text parts of the first candidate, matching the
|
|
7738
|
+
* SDK's `response.text` convenience getter (consumed by `GoogleProvider.send`).
|
|
7739
|
+
*/
|
|
7740
|
+
function responseText(response) {
|
|
7741
|
+
const parts = response.candidates?.[0]?.content?.parts;
|
|
7742
|
+
if (!parts)
|
|
7743
|
+
return undefined;
|
|
7744
|
+
const text = parts
|
|
7745
|
+
.filter((part) => part.text && !part.thought)
|
|
7746
|
+
.map((part) => part.text)
|
|
7747
|
+
.join("");
|
|
7748
|
+
return text.length > 0 ? text : undefined;
|
|
7749
|
+
}
|
|
7750
|
+
//
|
|
7751
|
+
//
|
|
7752
|
+
// Main
|
|
7753
|
+
//
|
|
7754
|
+
/**
|
|
7755
|
+
* Minimal `fetch`-based client for Google's Generative Language (Gemini) REST
|
|
7756
|
+
* API. Replaces `@google/genai` — the adapter only needs `generateContent`
|
|
7757
|
+
* (streaming and non-streaming), an api-key header, and HTTP error surfacing.
|
|
7758
|
+
* Exposes a `models` facade mirroring the SDK so the adapter call sites are
|
|
7759
|
+
* unchanged.
|
|
7760
|
+
*/
|
|
7761
|
+
class GoogleClient {
|
|
7762
|
+
constructor({ apiKey, baseURL = GOOGLE_BASE_URL }) {
|
|
7763
|
+
this.apiKey = apiKey;
|
|
7764
|
+
this.baseURL = baseURL;
|
|
7765
|
+
this.models = {
|
|
7766
|
+
generateContent: (params, options) => this.generateContent(params, options),
|
|
7767
|
+
generateContentStream: (params, options) => this.generateContentStream(params, options),
|
|
7768
|
+
};
|
|
7769
|
+
}
|
|
7770
|
+
headers() {
|
|
7771
|
+
return {
|
|
7772
|
+
"Content-Type": "application/json",
|
|
7773
|
+
"x-goog-api-key": this.apiKey,
|
|
7774
|
+
};
|
|
7775
|
+
}
|
|
7776
|
+
async toError(response) {
|
|
7777
|
+
let message = `Google request failed with status ${response.status}`;
|
|
7778
|
+
try {
|
|
7779
|
+
const body = (await response.json());
|
|
7780
|
+
if (body?.error?.message)
|
|
7781
|
+
message = body.error.message;
|
|
7782
|
+
}
|
|
7783
|
+
catch {
|
|
7784
|
+
// Non-JSON error body; keep the status-based message.
|
|
7785
|
+
}
|
|
7786
|
+
return new GoogleHttpError(response.status, message);
|
|
7787
|
+
}
|
|
7788
|
+
async generateContent(params, { signal } = {}) {
|
|
7789
|
+
const url = `${this.baseURL}/models/${params.model}:generateContent`;
|
|
7790
|
+
const response = await fetch(url, {
|
|
7791
|
+
method: "POST",
|
|
7792
|
+
headers: this.headers(),
|
|
7793
|
+
body: JSON.stringify(toRestBody(params)),
|
|
7794
|
+
signal,
|
|
7795
|
+
});
|
|
7796
|
+
if (!response.ok)
|
|
7797
|
+
throw await this.toError(response);
|
|
7798
|
+
const json = (await response.json());
|
|
7799
|
+
json.text = responseText(json);
|
|
7800
|
+
return json;
|
|
7801
|
+
}
|
|
7802
|
+
async generateContentStream(params, { signal } = {}) {
|
|
7803
|
+
const url = `${this.baseURL}/models/${params.model}:streamGenerateContent?alt=sse`;
|
|
7804
|
+
const response = await fetch(url, {
|
|
7805
|
+
method: "POST",
|
|
7806
|
+
headers: { ...this.headers(), Accept: "text/event-stream" },
|
|
7807
|
+
body: JSON.stringify(toRestBody(params)),
|
|
7808
|
+
signal,
|
|
7809
|
+
});
|
|
7810
|
+
if (!response.ok)
|
|
7811
|
+
throw await this.toError(response);
|
|
7812
|
+
if (!response.body)
|
|
7813
|
+
return (async function* () { })();
|
|
7814
|
+
return parseSseStream(response.body);
|
|
7190
7815
|
}
|
|
7191
7816
|
}
|
|
7817
|
+
|
|
7192
7818
|
// Logger
|
|
7193
7819
|
const getLogger$3 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
|
|
7194
7820
|
// Client initialization
|
|
@@ -7198,8 +7824,7 @@ async function initializeClient$3({ apiKey, } = {}) {
|
|
|
7198
7824
|
if (!resolvedApiKey) {
|
|
7199
7825
|
throw new errors.ConfigurationError("The application could not resolve the requested keys");
|
|
7200
7826
|
}
|
|
7201
|
-
const
|
|
7202
|
-
const client = new sdk.GoogleGenAI({ apiKey: resolvedApiKey });
|
|
7827
|
+
const client = new GoogleClient({ apiKey: resolvedApiKey });
|
|
7203
7828
|
logger.trace("Initialized Gemini client");
|
|
7204
7829
|
return client;
|
|
7205
7830
|
}
|
|
@@ -7348,7 +7973,7 @@ async function initializeClient$2({ apiKey, } = {}) {
|
|
|
7348
7973
|
if (!resolvedApiKey) {
|
|
7349
7974
|
throw new errors.ConfigurationError("The application could not resolve the requested keys");
|
|
7350
7975
|
}
|
|
7351
|
-
const client = new
|
|
7976
|
+
const client = new OpenAIClient({ apiKey: resolvedApiKey });
|
|
7352
7977
|
logger.trace("Initialized OpenAI client");
|
|
7353
7978
|
return client;
|
|
7354
7979
|
}
|
|
@@ -7390,7 +8015,7 @@ async function createStructuredCompletion(client, { messages, responseSchema, mo
|
|
|
7390
8015
|
const zodSchema = responseSchema instanceof v4.z.ZodType
|
|
7391
8016
|
? responseSchema
|
|
7392
8017
|
: naturalZodSchema(responseSchema);
|
|
7393
|
-
const responseFormat =
|
|
8018
|
+
const responseFormat = zodResponseFormat(zodSchema, "response");
|
|
7394
8019
|
const jsonSchema = v4.z.toJSONSchema(zodSchema);
|
|
7395
8020
|
// Temporary hack because OpenAI requires additional_properties to be false on all objects
|
|
7396
8021
|
const checks = [jsonSchema];
|
|
@@ -7407,23 +8032,23 @@ async function createStructuredCompletion(client, { messages, responseSchema, mo
|
|
|
7407
8032
|
checks.shift();
|
|
7408
8033
|
}
|
|
7409
8034
|
responseFormat.json_schema.schema = jsonSchema;
|
|
7410
|
-
const completion = await client.chat.completions.parse({
|
|
8035
|
+
const completion = (await client.chat.completions.parse({
|
|
7411
8036
|
messages,
|
|
7412
8037
|
model,
|
|
7413
8038
|
response_format: responseFormat,
|
|
7414
|
-
});
|
|
8039
|
+
}));
|
|
7415
8040
|
logger.var({ assistantReply: completion.choices[0].message.parsed });
|
|
7416
8041
|
return completion.choices[0].message.parsed;
|
|
7417
8042
|
}
|
|
7418
8043
|
async function createTextCompletion(client, { messages, model, }) {
|
|
7419
8044
|
const logger = getLogger$2();
|
|
7420
8045
|
logger.trace("Using text output (unstructured)");
|
|
7421
|
-
const completion = await client.chat.completions.create({
|
|
8046
|
+
const completion = (await client.chat.completions.create({
|
|
7422
8047
|
messages,
|
|
7423
8048
|
model,
|
|
7424
|
-
});
|
|
7425
|
-
logger.trace(`Assistant reply: ${completion.choices[0]?.message?.content?.length} characters`);
|
|
7426
|
-
return completion.choices[0]?.message?.content || "";
|
|
8049
|
+
}));
|
|
8050
|
+
logger.trace(`Assistant reply: ${completion.choices?.[0]?.message?.content?.length} characters`);
|
|
8051
|
+
return completion.choices?.[0]?.message?.content || "";
|
|
7427
8052
|
}
|
|
7428
8053
|
|
|
7429
8054
|
class OpenAiProvider {
|
|
@@ -7510,19 +8135,139 @@ class OpenAiProvider {
|
|
|
7510
8135
|
}
|
|
7511
8136
|
}
|
|
7512
8137
|
|
|
7513
|
-
//
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
8138
|
+
//
|
|
8139
|
+
//
|
|
8140
|
+
// Constants
|
|
8141
|
+
//
|
|
8142
|
+
const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
8143
|
+
/**
|
|
8144
|
+
* HTTP error carrying the upstream status and parsed API message. The
|
|
8145
|
+
* OpenRouterAdapter classifies errors by reading `.status` / `.statusCode` and
|
|
8146
|
+
* `.message` / `.error.message`, so this shape keeps `classifyError`,
|
|
8147
|
+
* `isTemperatureDeprecationError`, and `isStructuredOutputUnsupportedError`
|
|
8148
|
+
* working unchanged after dropping the SDK.
|
|
8149
|
+
*/
|
|
8150
|
+
class OpenRouterHttpError extends Error {
|
|
8151
|
+
constructor(status, message, error) {
|
|
8152
|
+
super(message);
|
|
8153
|
+
this.name = "OpenRouterHttpError";
|
|
8154
|
+
this.status = status;
|
|
8155
|
+
this.statusCode = status;
|
|
8156
|
+
this.error = error;
|
|
7521
8157
|
}
|
|
7522
|
-
|
|
7523
|
-
|
|
8158
|
+
}
|
|
8159
|
+
//
|
|
8160
|
+
//
|
|
8161
|
+
// Helpers
|
|
8162
|
+
//
|
|
8163
|
+
/**
|
|
8164
|
+
* Normalize the snake_case wire response into the camelCase shape the adapter
|
|
8165
|
+
* readers expect (the SDK previously returned camelCase). Only protocol fields
|
|
8166
|
+
* are touched — user content (schema property names, tool argument JSON) is
|
|
8167
|
+
* left untouched.
|
|
8168
|
+
*/
|
|
8169
|
+
function normalizeResponse(json) {
|
|
8170
|
+
const choices = json.choices;
|
|
8171
|
+
if (Array.isArray(choices)) {
|
|
8172
|
+
for (const choice of choices) {
|
|
8173
|
+
if (choice.finish_reason !== undefined &&
|
|
8174
|
+
choice.finishReason === undefined) {
|
|
8175
|
+
choice.finishReason = choice.finish_reason;
|
|
8176
|
+
}
|
|
8177
|
+
const message = choice.message;
|
|
8178
|
+
if (message?.tool_calls !== undefined &&
|
|
8179
|
+
message.toolCalls === undefined) {
|
|
8180
|
+
message.toolCalls = message.tool_calls;
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
}
|
|
8184
|
+
const usage = json.usage;
|
|
8185
|
+
if (usage) {
|
|
8186
|
+
if (usage.promptTokens === undefined)
|
|
8187
|
+
usage.promptTokens = usage.prompt_tokens;
|
|
8188
|
+
if (usage.completionTokens === undefined) {
|
|
8189
|
+
usage.completionTokens = usage.completion_tokens;
|
|
8190
|
+
}
|
|
8191
|
+
if (usage.totalTokens === undefined)
|
|
8192
|
+
usage.totalTokens = usage.total_tokens;
|
|
8193
|
+
const details = usage.completion_tokens_details;
|
|
8194
|
+
if (details?.reasoning_tokens !== undefined &&
|
|
8195
|
+
!usage.completionTokensDetails) {
|
|
8196
|
+
usage.completionTokensDetails = {
|
|
8197
|
+
reasoningTokens: details.reasoning_tokens,
|
|
8198
|
+
};
|
|
8199
|
+
}
|
|
8200
|
+
}
|
|
8201
|
+
return json;
|
|
8202
|
+
}
|
|
8203
|
+
//
|
|
8204
|
+
//
|
|
8205
|
+
// Main
|
|
8206
|
+
//
|
|
8207
|
+
/**
|
|
8208
|
+
* Minimal `fetch`-based client for OpenRouter's OpenAI-compatible Chat
|
|
8209
|
+
* Completions endpoint. Replaces `@openrouter/sdk` — the adapter only needs a
|
|
8210
|
+
* single POST (streaming and non-streaming), header auth, and HTTP error
|
|
8211
|
+
* surfacing.
|
|
8212
|
+
*/
|
|
8213
|
+
class OpenRouterClient {
|
|
8214
|
+
constructor({ apiKey, baseURL = OPENROUTER_BASE_URL, }) {
|
|
8215
|
+
this.apiKey = apiKey;
|
|
8216
|
+
this.baseURL = baseURL;
|
|
8217
|
+
}
|
|
8218
|
+
headers() {
|
|
8219
|
+
return {
|
|
8220
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
8221
|
+
"Content-Type": "application/json",
|
|
8222
|
+
};
|
|
8223
|
+
}
|
|
8224
|
+
async toError(response) {
|
|
8225
|
+
let message = `OpenRouter request failed with status ${response.status}`;
|
|
8226
|
+
let error;
|
|
8227
|
+
try {
|
|
8228
|
+
const body = (await response.json());
|
|
8229
|
+
if (body?.error?.message) {
|
|
8230
|
+
message = body.error.message;
|
|
8231
|
+
error = body.error;
|
|
8232
|
+
}
|
|
8233
|
+
}
|
|
8234
|
+
catch {
|
|
8235
|
+
// Non-JSON error body; keep the status-based message.
|
|
8236
|
+
}
|
|
8237
|
+
return new OpenRouterHttpError(response.status, message, error);
|
|
8238
|
+
}
|
|
8239
|
+
async chatCompletion(body, { signal } = {}) {
|
|
8240
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
8241
|
+
method: "POST",
|
|
8242
|
+
headers: this.headers(),
|
|
8243
|
+
body: JSON.stringify(body),
|
|
8244
|
+
signal,
|
|
8245
|
+
});
|
|
8246
|
+
if (!response.ok)
|
|
8247
|
+
throw await this.toError(response);
|
|
8248
|
+
const json = (await response.json());
|
|
8249
|
+
return normalizeResponse(json);
|
|
8250
|
+
}
|
|
8251
|
+
async *streamChatCompletion(body, { signal } = {}) {
|
|
8252
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
8253
|
+
method: "POST",
|
|
8254
|
+
headers: { ...this.headers(), Accept: "text/event-stream" },
|
|
8255
|
+
// OpenAI-style streams only include usage when explicitly requested.
|
|
8256
|
+
body: JSON.stringify({
|
|
8257
|
+
...body,
|
|
8258
|
+
stream: true,
|
|
8259
|
+
stream_options: { include_usage: true },
|
|
8260
|
+
}),
|
|
8261
|
+
signal,
|
|
8262
|
+
});
|
|
8263
|
+
if (!response.ok)
|
|
8264
|
+
throw await this.toError(response);
|
|
8265
|
+
if (!response.body)
|
|
8266
|
+
return;
|
|
8267
|
+
yield* parseSseStream(response.body);
|
|
7524
8268
|
}
|
|
7525
8269
|
}
|
|
8270
|
+
|
|
7526
8271
|
// Logger
|
|
7527
8272
|
const getLogger$1 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
|
|
7528
8273
|
// Client initialization
|
|
@@ -7532,8 +8277,7 @@ async function initializeClient$1({ apiKey, } = {}) {
|
|
|
7532
8277
|
if (!resolvedApiKey) {
|
|
7533
8278
|
throw new errors.ConfigurationError("The application could not resolve the requested keys");
|
|
7534
8279
|
}
|
|
7535
|
-
const
|
|
7536
|
-
const client = new sdk.OpenRouter({ apiKey: resolvedApiKey });
|
|
8280
|
+
const client = new OpenRouterClient({ apiKey: resolvedApiKey });
|
|
7537
8281
|
logger.trace("Initialized OpenRouter client");
|
|
7538
8282
|
return client;
|
|
7539
8283
|
}
|
|
@@ -7613,12 +8357,13 @@ class OpenRouterProvider {
|
|
|
7613
8357
|
const client = await this.getClient();
|
|
7614
8358
|
const messages = prepareMessages(message, options);
|
|
7615
8359
|
const modelToUse = options?.model || this.model;
|
|
7616
|
-
//
|
|
7617
|
-
const response = await client.
|
|
8360
|
+
// OpenAI-compatible Chat Completions body; messages are already wire-shaped.
|
|
8361
|
+
const response = await client.chatCompletion({
|
|
7618
8362
|
model: modelToUse,
|
|
7619
|
-
messages
|
|
8363
|
+
messages,
|
|
7620
8364
|
});
|
|
7621
|
-
const
|
|
8365
|
+
const choices = response.choices;
|
|
8366
|
+
const rawContent = choices?.[0]?.message?.content;
|
|
7622
8367
|
// Extract text content - content could be string or array of content items
|
|
7623
8368
|
const content = typeof rawContent === "string"
|
|
7624
8369
|
? rawContent
|
|
@@ -7681,7 +8426,7 @@ async function initializeClient({ apiKey, } = {}) {
|
|
|
7681
8426
|
if (!resolvedApiKey) {
|
|
7682
8427
|
throw new errors.ConfigurationError("The application could not resolve the requested keys");
|
|
7683
8428
|
}
|
|
7684
|
-
const client = new
|
|
8429
|
+
const client = new OpenAIClient({
|
|
7685
8430
|
apiKey: resolvedApiKey,
|
|
7686
8431
|
baseURL: PROVIDER.XAI.BASE_URL,
|
|
7687
8432
|
});
|