@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.
Files changed (39) hide show
  1. package/dist/cjs/index.cjs +877 -132
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/operate/OperateLoop.d.ts +6 -3
  4. package/dist/cjs/operate/adapters/AnthropicAdapter.d.ts +1 -1
  5. package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +8 -1
  6. package/dist/cjs/providers/anthropic/client.d.ts +54 -0
  7. package/dist/cjs/providers/anthropic/types.d.ts +127 -0
  8. package/dist/cjs/providers/anthropic/utils.d.ts +5 -5
  9. package/dist/cjs/providers/google/client.d.ts +44 -0
  10. package/dist/cjs/providers/google/utils.d.ts +2 -3
  11. package/dist/cjs/providers/openai/client.d.ts +74 -0
  12. package/dist/cjs/providers/openai/responseFormat.d.ts +17 -0
  13. package/dist/cjs/providers/openai/utils.d.ts +4 -4
  14. package/dist/cjs/providers/openrouter/client.d.ts +40 -0
  15. package/dist/cjs/providers/openrouter/utils.d.ts +2 -3
  16. package/dist/cjs/providers/xai/utils.d.ts +2 -2
  17. package/dist/cjs/util/index.d.ts +2 -0
  18. package/dist/cjs/util/repairFormatKeys.d.ts +19 -0
  19. package/dist/cjs/util/sse.d.ts +12 -0
  20. package/dist/esm/index.js +870 -125
  21. package/dist/esm/index.js.map +1 -1
  22. package/dist/esm/operate/OperateLoop.d.ts +6 -3
  23. package/dist/esm/operate/adapters/AnthropicAdapter.d.ts +1 -1
  24. package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +8 -1
  25. package/dist/esm/providers/anthropic/client.d.ts +54 -0
  26. package/dist/esm/providers/anthropic/types.d.ts +127 -0
  27. package/dist/esm/providers/anthropic/utils.d.ts +5 -5
  28. package/dist/esm/providers/google/client.d.ts +44 -0
  29. package/dist/esm/providers/google/utils.d.ts +2 -3
  30. package/dist/esm/providers/openai/client.d.ts +74 -0
  31. package/dist/esm/providers/openai/responseFormat.d.ts +17 -0
  32. package/dist/esm/providers/openai/utils.d.ts +4 -4
  33. package/dist/esm/providers/openrouter/client.d.ts +40 -0
  34. package/dist/esm/providers/openrouter/utils.d.ts +2 -3
  35. package/dist/esm/providers/xai/utils.d.ts +2 -2
  36. package/dist/esm/util/index.d.ts +2 -0
  37. package/dist/esm/util/repairFormatKeys.d.ts +19 -0
  38. package/dist/esm/util/sse.d.ts +12 -0
  39. package/package.json +2 -18
@@ -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
- openai.APIConnectionError,
3418
- openai.APIConnectionTimeoutError,
3419
- openai.InternalServerError,
3764
+ APIConnectionError,
3765
+ APIConnectionTimeoutError,
3766
+ InternalServerError$1,
3420
3767
  ];
3421
3768
  const NOT_RETRYABLE_ERROR_TYPES = [
3422
- openai.APIUserAbortError,
3423
- openai.AuthenticationError,
3424
- openai.BadRequestError,
3425
- openai.ConflictError,
3426
- openai.NotFoundError,
3427
- openai.PermissionDeniedError,
3428
- openai.RateLimitError,
3429
- openai.UnprocessableEntityError,
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 openai.BadRequestError) &&
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 = zod.zodResponseFormat(zodSchema, "response");
3550
- const jsonSchema = v4.z.toJSONSchema(zodSchema);
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 openai.RateLimitError) {
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
- // OpenRouter SDK error types based on HTTP status codes
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.chat.send(this.toSdkChatParams(openRouterRequest), signal ? { signal } : undefined));
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.chat.send(this.toSdkChatParams(retryRequest), signal ? { signal } : undefined));
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.chat.send(this.toSdkChatParams(fallbackRequest), signal ? { signal } : undefined));
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
- toSdkChatParams(openRouterRequest) {
4249
- const params = {
4250
- model: openRouterRequest.model,
4251
- messages: openRouterRequest.messages,
4252
- tools: openRouterRequest.tools,
4253
- toolChoice: openRouterRequest.tool_choice,
4254
- user: openRouterRequest.user,
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
- // Use chat.send with stream: true for streaming responses.
4305
- // Cast the result to AsyncIterable: when stream: true, the SDK returns a
4306
- // stream we can iterate, but the typed result is the union with the
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 openai.BadRequestError))
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$4 = null;
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$4;
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$4 = llmobs;
5350
+ cachedSdk$1 = llmobs;
4972
5351
  }
4973
5352
  }
4974
5353
  catch {
4975
- cachedSdk$4 = null;
5354
+ cachedSdk$1 = null;
4976
5355
  }
4977
- return cachedSdk$4;
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
- * Backfill declared array fields when a `format` is supplied. A declared
6317
- * `format` is a schema contract: an empty array field should surface as `[]`
6318
- * rather than be dropped by a provider/model that omits empty lists.
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
- return fillFormatArrays({ content, format: options.format });
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
- // SDK loader with caching
6888
- let cachedSdk$3 = null;
6889
- async function loadSdk$3() {
6890
- if (cachedSdk$3)
6891
- return cachedSdk$3;
6892
- try {
6893
- cachedSdk$3 = await import('@anthropic-ai/sdk');
6894
- return cachedSdk$3;
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
- catch {
6897
- throw new errors.ConfigurationError("@anthropic-ai/sdk is required but not installed. Run: npm install @anthropic-ai/sdk");
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 sdk = await loadSdk$3();
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$2 = null;
7099
- async function loadSdk$2() {
7100
- if (cachedSdk$2)
7101
- return cachedSdk$2;
7587
+ let cachedSdk = null;
7588
+ async function loadSdk() {
7589
+ if (cachedSdk)
7590
+ return cachedSdk;
7102
7591
  try {
7103
- cachedSdk$2 = await import('@aws-sdk/client-bedrock-runtime');
7104
- return cachedSdk$2;
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$2();
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
- // SDK loader with caching
7180
- let cachedSdk$1 = null;
7181
- async function loadSdk$1() {
7182
- if (cachedSdk$1)
7183
- return cachedSdk$1;
7184
- try {
7185
- cachedSdk$1 = await import('@google/genai');
7186
- return cachedSdk$1;
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
- catch {
7189
- throw new errors.ConfigurationError("@google/genai is required but not installed. Run: npm install @google/genai");
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 sdk = await loadSdk$1();
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 openai.OpenAI({ apiKey: resolvedApiKey });
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 = zod.zodResponseFormat(zodSchema, "response");
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
- // SDK loader with caching
7514
- let cachedSdk = null;
7515
- async function loadSdk() {
7516
- if (cachedSdk)
7517
- return cachedSdk;
7518
- try {
7519
- cachedSdk = await import('@openrouter/sdk');
7520
- return cachedSdk;
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
- catch {
7523
- throw new errors.ConfigurationError("@openrouter/sdk is required but not installed. Run: npm install @openrouter/sdk");
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 sdk = await loadSdk();
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
- // Build the request
7617
- const response = await client.chat.send({
8360
+ // OpenAI-compatible Chat Completions body; messages are already wire-shaped.
8361
+ const response = await client.chatCompletion({
7618
8362
  model: modelToUse,
7619
- messages: messages,
8363
+ messages,
7620
8364
  });
7621
- const rawContent = response.choices?.[0]?.message?.content;
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 openai.OpenAI({
8429
+ const client = new OpenAIClient({
7685
8430
  apiKey: resolvedApiKey,
7686
8431
  baseURL: PROVIDER.XAI.BASE_URL,
7687
8432
  });