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