@neon/ai-sdk-provider 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/{LICENSE.md → LICENSE} +27 -3
  2. package/README.md +131 -13
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +3 -3
  5. package/dist/lib/neon-anthropic-language-model.d.ts +8 -0
  6. package/dist/lib/neon-anthropic-language-model.d.ts.map +1 -1
  7. package/dist/lib/neon-anthropic-language-model.js +39 -13
  8. package/dist/lib/neon-anthropic-language-model.js.map +1 -1
  9. package/dist/lib/neon-capabilities.d.ts.map +1 -1
  10. package/dist/lib/neon-capabilities.js +17 -11
  11. package/dist/lib/neon-capabilities.js.map +1 -1
  12. package/dist/lib/neon-chat-options.d.ts +4 -11
  13. package/dist/lib/neon-chat-options.d.ts.map +1 -1
  14. package/dist/lib/neon-chat-options.js +23 -28
  15. package/dist/lib/neon-chat-options.js.map +1 -1
  16. package/dist/lib/neon-gateway-error.d.ts +56 -0
  17. package/dist/lib/neon-gateway-error.d.ts.map +1 -0
  18. package/dist/lib/neon-gateway-error.js +102 -0
  19. package/dist/lib/neon-gateway-error.js.map +1 -0
  20. package/dist/lib/neon-model-capabilities.d.ts +7 -0
  21. package/dist/lib/neon-model-capabilities.d.ts.map +1 -1
  22. package/dist/lib/neon-model-capabilities.js +47 -10
  23. package/dist/lib/neon-model-capabilities.js.map +1 -1
  24. package/dist/lib/neon-responses-language-model.d.ts +5 -11
  25. package/dist/lib/neon-responses-language-model.d.ts.map +1 -1
  26. package/dist/lib/neon-responses-language-model.js +8 -29
  27. package/dist/lib/neon-responses-language-model.js.map +1 -1
  28. package/dist/lib/neon-responses-options.d.ts +14 -0
  29. package/dist/lib/neon-responses-options.d.ts.map +1 -0
  30. package/dist/lib/neon-responses-options.js +66 -0
  31. package/dist/lib/neon-responses-options.js.map +1 -0
  32. package/dist/lib/provider.d.ts.map +1 -1
  33. package/dist/lib/provider.js +5 -4
  34. package/dist/lib/provider.js.map +1 -1
  35. package/dist/package.js +1 -1
  36. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"neon-chat-options.js","names":[],"sources":["../../src/lib/neon-chat-options.ts"],"sourcesContent":["// Models served by the Neon AI Gateway. Anthropic and OpenAI models are routed\n// to their provider-native endpoints; everything else uses the unified MLflow\n// (OpenAI-compatible) endpoint.\n//\n// Ids use the canonical Neon (unprefixed) form, matching the `neon` provider on\n// models.dev. The gateway also accepts the legacy `databricks-` prefixed form\n// (the `databricks` provider on models.dev), which still resolves via the\n// `(string & {})` fallback on `NeonChatModelId`.\n//\n// `NEON_MODELS_DEV_IDS` mirrors the models.dev `neon` catalog exactly — the\n// `neon-catalog-drift.test.ts` maintainer check fails if the two diverge.\n// `NEON_EXTRA_MODEL_IDS` are additional ids the gateway serves that models.dev\n// does not (yet) list; they are kept for autocomplete and verified to resolve\n// against the gateway. The authoritative, always-current catalog is shown in\n// the Neon Console under the branch's \"AI Gateway\" tab.\n\n/** The models.dev `neon` catalog (canonical, unprefixed ids). */\nexport const NEON_MODELS_DEV_IDS = [\n\t// Anthropic (native Messages API)\n\t\"claude-opus-4-7\",\n\t\"claude-opus-4-6\",\n\t\"claude-opus-4-5\",\n\t\"claude-opus-4-1\",\n\t\"claude-sonnet-4-6\",\n\t\"claude-sonnet-4-5\",\n\t\"claude-sonnet-4\",\n\t\"claude-haiku-4-5\",\n\t// OpenAI (native Responses API)\n\t\"gpt-5\",\n\t\"gpt-5-mini\",\n\t\"gpt-5-nano\",\n\t\"gpt-5-1\",\n\t\"gpt-5-2\",\n\t\"gpt-5-4\",\n\t\"gpt-5-4-mini\",\n\t\"gpt-5-4-nano\",\n\t\"gpt-5-5\",\n\t// OpenAI open-weight (unified MLflow endpoint)\n\t\"gpt-oss-120b\",\n\t\"gpt-oss-20b\",\n\t// Google (unified MLflow endpoint)\n\t\"gemini-3-pro\",\n\t\"gemini-3-flash\",\n\t\"gemini-3-1-pro\",\n\t\"gemini-3-1-flash-lite\",\n\t\"gemini-2-5-pro\",\n\t\"gemini-2-5-flash\",\n] as const;\n\n/**\n * Ids the gateway serves that the models.dev `neon` provider does not list.\n * Verified to resolve against the gateway; retained for autocomplete. If\n * models.dev later lists one of these, the drift check flags it for promotion\n * into `NEON_MODELS_DEV_IDS`.\n */\nexport const NEON_EXTRA_MODEL_IDS = [\n\t// Anthropic (native Messages API)\n\t\"claude-opus-4-8\",\n\t// OpenAI (native Responses API) — Codex is served only natively\n\t\"gpt-5-2-codex\",\n\t\"gpt-5-3-codex\",\n\t\"gpt-5-5-pro\",\n\t// Google (unified MLflow endpoint)\n\t\"gemini-3-5-flash\",\n\t\"gemma-3-12b\",\n\t// Meta (unified MLflow endpoint)\n\t\"llama-4-maverick\",\n\t\"meta-llama-3-3-70b-instruct\",\n\t\"meta-llama-3-1-8b-instruct\",\n\t// Alibaba (unified MLflow endpoint)\n\t\"qwen3-next-80b-a3b-instruct\",\n\t\"qwen35-122b-a10b\",\n] as const;\n\n/** A known Neon AI Gateway model id (models.dev catalog + gateway extras). */\nexport type NeonKnownModelId =\n\t| (typeof NEON_MODELS_DEV_IDS)[number]\n\t| (typeof NEON_EXTRA_MODEL_IDS)[number];\n\n/**\n * A Neon AI Gateway model id. Known ids are listed for autocomplete; any other\n * id (e.g. a brand-new model, or the legacy `databricks-` prefixed form) is\n * accepted via the `(string & {})` fallback.\n */\nexport type NeonChatModelId = NeonKnownModelId | (string & {});\n"],"mappings":";;AAiBA,MAAa,sBAAsB;CAElC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;AACD;;;;;;;AAQA,MAAa,uBAAuB;CAEnC;CAEA;CACA;CACA;CAEA;CACA;CAEA;CACA;CACA;CAEA;CACA;AACD"}
1
+ {"version":3,"file":"neon-chat-options.js","names":[],"sources":["../../src/lib/neon-chat-options.ts"],"sourcesContent":["// Models served by the Neon AI Gateway. Anthropic and OpenAI models are routed\n// to their provider-native endpoints; everything else uses the unified MLflow\n// (OpenAI-compatible) endpoint.\n//\n// Ids use the canonical Neon (unprefixed) form, matching the `neon` provider on\n// models.dev. The gateway also accepts the legacy `databricks-` prefixed form\n// (the `databricks` provider on models.dev), which still resolves via the\n// `(string & {})` fallback on `NeonChatModelId`.\n//\n// `NEON_MODELS_DEV_IDS` mirrors the models.dev `neon` catalog exactly — the\n// `neon-catalog-drift.test.ts` maintainer check fails if the two diverge. The\n// authoritative, always-current catalog is shown in the Neon Console under the\n// branch's \"AI Gateway\" tab.\n//\n// There is deliberately no second list for ids the gateway serves ahead of\n// models.dev. Such an id already works — `NeonChatModelId` accepts any string —\n// so a hand-maintained list would buy autocomplete for a few days at the cost of\n// a surface that silently rots, which is what happened last time. The fix for a\n// gateway model missing here is to add it to models.dev; Orbit's catalog-parity\n// job reports that gap daily.\n\n/** The models.dev `neon` catalog (canonical, unprefixed ids). */\nexport const NEON_MODELS_DEV_IDS = [\n\t// Anthropic (native Messages API)\n\t\"claude-fable-5\",\n\t\"claude-haiku-4-5\",\n\t\"claude-opus-4-1\",\n\t\"claude-opus-4-5\",\n\t\"claude-opus-4-6\",\n\t\"claude-opus-4-7\",\n\t\"claude-opus-4-8\",\n\t\"claude-opus-5\",\n\t\"claude-sonnet-4-5\",\n\t\"claude-sonnet-4-6\",\n\t\"claude-sonnet-5\",\n\t// OpenAI (native Responses API)\n\t\"gpt-5\",\n\t\"gpt-5-1\",\n\t\"gpt-5-2\",\n\t\"gpt-5-3-codex\",\n\t\"gpt-5-4\",\n\t\"gpt-5-4-mini\",\n\t\"gpt-5-4-nano\",\n\t\"gpt-5-5\",\n\t\"gpt-5-5-pro\",\n\t\"gpt-5-6-luna\",\n\t\"gpt-5-6-sol\",\n\t\"gpt-5-6-terra\",\n\t\"gpt-5-mini\",\n\t\"gpt-5-nano\",\n\t// OpenAI open-weight (unified MLflow endpoint)\n\t\"gpt-oss-120b\",\n\t\"gpt-oss-20b\",\n\t// Google (unified MLflow endpoint)\n\t\"gemini-3-1-flash-lite\",\n\t\"gemini-3-1-pro\",\n\t\"gemini-3-5-flash\",\n\t\"gemini-3-flash\",\n\t\"gemma-3-12b\",\n\t// Meta (unified MLflow endpoint)\n\t\"llama-4-maverick\",\n\t\"meta-llama-3-1-8b-instruct\",\n\t\"meta-llama-3-3-70b-instruct\",\n\t// Alibaba (unified MLflow endpoint)\n\t\"qwen3-next-80b-a3b-instruct\",\n\t\"qwen35-122b-a10b\",\n\t// Zhipu (unified MLflow endpoint)\n\t\"glm-5-2\",\n\t// Thinking Machines (unified MLflow endpoint)\n\t\"inkling\",\n] as const;\n\n/** A known Neon AI Gateway model id. */\nexport type NeonKnownModelId = (typeof NEON_MODELS_DEV_IDS)[number];\n\n/**\n * A Neon AI Gateway model id. Known ids are listed for autocomplete; any other\n * id (e.g. a brand-new model, or the legacy `databricks-` prefixed form) is\n * accepted via the `(string & {})` fallback.\n */\nexport type NeonChatModelId = NeonKnownModelId | (string & {});\n"],"mappings":";;AAsBA,MAAa,sBAAsB;CAElC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CAEA;CACA;CAEA;CAEA;AACD"}
@@ -0,0 +1,56 @@
1
+ import { FetchFunction } from "@ai-sdk/provider-utils";
2
+
3
+ //#region src/lib/neon-gateway-error.d.ts
4
+
5
+ /**
6
+ * Give every route of this provider the error envelope its model knows how to
7
+ * read, so a failed call surfaces the gateway's reason on `error.message`
8
+ * instead of a bare HTTP status line.
9
+ *
10
+ * The gateway emits several error shapes, and which one you get depends on
11
+ * which layer rejected the request rather than on which route you called:
12
+ *
13
+ * 1. The gateway's own rejection, in OpenAI shape:
14
+ * { "error": { "message": "unknown model \"nope\"" } }
15
+ *
16
+ * 2. A Databricks rejection, flat and with its own code:
17
+ * { "error_code": "BAD_REQUEST", "message": "service_tier='flex' is …" }
18
+ *
19
+ * 3. A Databricks rejection wrapping an upstream error as a JSON *string*:
20
+ * { "error_code": "BAD_REQUEST", "message": "{\"error\":{\"message\":…}}" }
21
+ *
22
+ * Meanwhile each underlying model parses a different schema: the OpenAI and
23
+ * OpenAI-compatible models want `{ error: { message } }`, the Anthropic model
24
+ * wants `{ type: "error", error: { type, message } }`. Anything that does not
25
+ * match is dropped and the AI SDK falls back to the status text, which is how
26
+ * `AI_APICallError: Bad Request` reaches a caller with the real explanation
27
+ * stranded on `responseBody`.
28
+ *
29
+ * So the shape a body needs is a property of the route, not of the gateway:
30
+ * this reads any of the above into one reason and re-emits it in the dialect
31
+ * that route's model expects. Cross-dialect conversion is covered too, since
32
+ * nothing guarantees a given layer answers in the dialect of the route it was
33
+ * reached through. A body already valid for the target dialect is left alone,
34
+ * as is any successful response and anything unrecognised.
35
+ *
36
+ * The Anthropic direction is unverified against a live gateway: that endpoint
37
+ * currently answers every request with a plain-text 404, so no JSON envelope
38
+ * can be observed on it at all.
39
+ *
40
+ * None of the models expose an error hook (`OpenAIConfig` has none, and the
41
+ * Anthropic one takes a fixed handler), so this rides on `fetch` — the same
42
+ * lever `wrapFetchWithHarmonyNormalization` uses. The two are disjoint: that
43
+ * one returns early on a failed response, this one only acts on failures.
44
+ */
45
+ /** The error envelope a route's model can parse. */
46
+ type GatewayErrorDialect = "openai" | "anthropic";
47
+ /**
48
+ * Map a gateway error body into `dialect`, or return null when it already
49
+ * parses there (or is not a shape we recognise).
50
+ */
51
+ declare function normalizeGatewayErrorBody(body: unknown, dialect: GatewayErrorDialect): unknown | null;
52
+ /** Wrap a fetch so failed responses reach the model in its own dialect. */
53
+ declare function wrapFetchWithGatewayErrorNormalization(baseFetch: FetchFunction | undefined, dialect: GatewayErrorDialect): FetchFunction;
54
+ //#endregion
55
+ export { GatewayErrorDialect, normalizeGatewayErrorBody, wrapFetchWithGatewayErrorNormalization };
56
+ //# sourceMappingURL=neon-gateway-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neon-gateway-error.d.ts","names":[],"sources":["../../src/lib/neon-gateway-error.ts"],"mappings":";;;;;;AA4CA;AAqHA;AAiBA;AAAsD;AAC1C;AACF;AACP;AAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAzIJ,mBAAA;;;;;iBAqHI,yBAAA,yBAEN;;iBAeM,sCAAA,YACJ,oCACF,sBACP"}
@@ -0,0 +1,102 @@
1
+ //#region src/lib/neon-gateway-error.ts
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function asOpenAIEnvelope(value) {
6
+ if (isRecord(value) && isRecord(value.error) && typeof value.error.message === "string") return value.error;
7
+ return null;
8
+ }
9
+ function asAnthropicEnvelope(value) {
10
+ if (isRecord(value) && value.type === "error" && isRecord(value.error) && typeof value.error.message === "string" && typeof value.error.type === "string") return value.error;
11
+ return null;
12
+ }
13
+ /** Read any envelope the gateway emits into a single reason. */
14
+ function extractReason(body) {
15
+ const anthropic = asAnthropicEnvelope(body);
16
+ if (anthropic) return {
17
+ message: anthropic.message,
18
+ type: anthropic.type
19
+ };
20
+ const openai = asOpenAIEnvelope(body);
21
+ if (openai) return {
22
+ message: openai.message,
23
+ openaiFields: openai,
24
+ type: typeof openai.type === "string" ? openai.type : void 0
25
+ };
26
+ if (!isRecord(body) || typeof body.message !== "string") return null;
27
+ const code = typeof body.error_code === "string" ? body.error_code : void 0;
28
+ if (body.message.trimStart().startsWith("{")) try {
29
+ const nested = extractReason(JSON.parse(body.message));
30
+ if (nested) return {
31
+ ...nested,
32
+ type: nested.type ?? code
33
+ };
34
+ } catch {}
35
+ return {
36
+ message: body.message,
37
+ type: code
38
+ };
39
+ }
40
+ /**
41
+ * Overlay the target envelope on the original body rather than replacing it,
42
+ * so whatever else the gateway sent — `error_code`, request ids, the encoded
43
+ * original message — still reaches the caller on `APICallError.responseBody`.
44
+ */
45
+ function emit(body, reason, dialect) {
46
+ const original = isRecord(body) ? body : {};
47
+ if (dialect === "anthropic") return {
48
+ ...original,
49
+ type: "error",
50
+ error: {
51
+ ...reason.openaiFields,
52
+ type: reason.type ?? "api_error",
53
+ message: reason.message
54
+ }
55
+ };
56
+ return {
57
+ ...original,
58
+ error: {
59
+ ...reason.openaiFields,
60
+ message: reason.message,
61
+ code: reason.openaiFields?.code ?? reason.type ?? null
62
+ }
63
+ };
64
+ }
65
+ /**
66
+ * Map a gateway error body into `dialect`, or return null when it already
67
+ * parses there (or is not a shape we recognise).
68
+ */
69
+ function normalizeGatewayErrorBody(body, dialect) {
70
+ if (dialect === "anthropic" ? asAnthropicEnvelope(body) !== null : asOpenAIEnvelope(body) !== null) return null;
71
+ const reason = extractReason(body);
72
+ return reason === null ? null : emit(body, reason, dialect);
73
+ }
74
+ /** Wrap a fetch so failed responses reach the model in its own dialect. */
75
+ function wrapFetchWithGatewayErrorNormalization(baseFetch, dialect) {
76
+ const inner = baseFetch ?? ((...args) => globalThis.fetch(...args));
77
+ return async (input, init) => {
78
+ const response = await inner(input, init);
79
+ if (response.ok) return response;
80
+ if (!(response.headers.get("content-type") ?? "").includes("application/json")) return response;
81
+ let parsed;
82
+ try {
83
+ parsed = JSON.parse(await response.clone().text());
84
+ } catch {
85
+ return response;
86
+ }
87
+ const normalized = normalizeGatewayErrorBody(parsed, dialect);
88
+ if (normalized === null) return response;
89
+ const headers = new Headers(response.headers);
90
+ headers.delete("content-length");
91
+ headers.delete("content-encoding");
92
+ return new Response(JSON.stringify(normalized), {
93
+ status: response.status,
94
+ statusText: response.statusText,
95
+ headers
96
+ });
97
+ };
98
+ }
99
+ //#endregion
100
+ export { normalizeGatewayErrorBody, wrapFetchWithGatewayErrorNormalization };
101
+
102
+ //# sourceMappingURL=neon-gateway-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neon-gateway-error.js","names":[],"sources":["../../src/lib/neon-gateway-error.ts"],"sourcesContent":["import type { FetchFunction } from \"@ai-sdk/provider-utils\";\n\n/**\n * Give every route of this provider the error envelope its model knows how to\n * read, so a failed call surfaces the gateway's reason on `error.message`\n * instead of a bare HTTP status line.\n *\n * The gateway emits several error shapes, and which one you get depends on\n * which layer rejected the request rather than on which route you called:\n *\n * 1. The gateway's own rejection, in OpenAI shape:\n * { \"error\": { \"message\": \"unknown model \\\"nope\\\"\" } }\n *\n * 2. A Databricks rejection, flat and with its own code:\n * { \"error_code\": \"BAD_REQUEST\", \"message\": \"service_tier='flex' is …\" }\n *\n * 3. A Databricks rejection wrapping an upstream error as a JSON *string*:\n * { \"error_code\": \"BAD_REQUEST\", \"message\": \"{\\\"error\\\":{\\\"message\\\":…}}\" }\n *\n * Meanwhile each underlying model parses a different schema: the OpenAI and\n * OpenAI-compatible models want `{ error: { message } }`, the Anthropic model\n * wants `{ type: \"error\", error: { type, message } }`. Anything that does not\n * match is dropped and the AI SDK falls back to the status text, which is how\n * `AI_APICallError: Bad Request` reaches a caller with the real explanation\n * stranded on `responseBody`.\n *\n * So the shape a body needs is a property of the route, not of the gateway:\n * this reads any of the above into one reason and re-emits it in the dialect\n * that route's model expects. Cross-dialect conversion is covered too, since\n * nothing guarantees a given layer answers in the dialect of the route it was\n * reached through. A body already valid for the target dialect is left alone,\n * as is any successful response and anything unrecognised.\n *\n * The Anthropic direction is unverified against a live gateway: that endpoint\n * currently answers every request with a plain-text 404, so no JSON envelope\n * can be observed on it at all.\n *\n * None of the models expose an error hook (`OpenAIConfig` has none, and the\n * Anthropic one takes a fixed handler), so this rides on `fetch` — the same\n * lever `wrapFetchWithHarmonyNormalization` uses. The two are disjoint: that\n * one returns early on a failed response, this one only acts on failures.\n */\n\n/** The error envelope a route's model can parse. */\nexport type GatewayErrorDialect = \"openai\" | \"anthropic\";\n\ninterface Reason {\n\tmessage: string;\n\t/** Preserved verbatim when the source was already an OpenAI envelope. */\n\topenaiFields?: Record<string, unknown>;\n\t/** An Anthropic `error.type`, or a Databricks `error_code`. */\n\ttype?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction asOpenAIEnvelope(value: unknown): Record<string, unknown> | null {\n\tif (\n\t\tisRecord(value) &&\n\t\tisRecord(value.error) &&\n\t\ttypeof value.error.message === \"string\"\n\t) {\n\t\treturn value.error;\n\t}\n\treturn null;\n}\n\nfunction asAnthropicEnvelope(value: unknown): Record<string, unknown> | null {\n\tif (\n\t\tisRecord(value) &&\n\t\tvalue.type === \"error\" &&\n\t\tisRecord(value.error) &&\n\t\ttypeof value.error.message === \"string\" &&\n\t\ttypeof value.error.type === \"string\"\n\t) {\n\t\treturn value.error;\n\t}\n\treturn null;\n}\n\n/** Read any envelope the gateway emits into a single reason. */\nfunction extractReason(body: unknown): Reason | null {\n\tconst anthropic = asAnthropicEnvelope(body);\n\tif (anthropic) {\n\t\treturn {\n\t\t\tmessage: anthropic.message as string,\n\t\t\ttype: anthropic.type as string,\n\t\t};\n\t}\n\n\tconst openai = asOpenAIEnvelope(body);\n\tif (openai) {\n\t\treturn {\n\t\t\tmessage: openai.message as string,\n\t\t\topenaiFields: openai,\n\t\t\ttype: typeof openai.type === \"string\" ? openai.type : undefined,\n\t\t};\n\t}\n\n\tif (!isRecord(body) || typeof body.message !== \"string\") {\n\t\treturn null;\n\t}\n\tconst code =\n\t\ttypeof body.error_code === \"string\" ? body.error_code : undefined;\n\n\t// Shape 3: an upstream envelope encoded into `message`. Prefer it — it\n\t// names the offending parameter, which the outer wrapper does not.\n\tif (body.message.trimStart().startsWith(\"{\")) {\n\t\ttry {\n\t\t\tconst inner: unknown = JSON.parse(body.message);\n\t\t\tconst nested = extractReason(inner);\n\t\t\tif (nested) {\n\t\t\t\treturn { ...nested, type: nested.type ?? code };\n\t\t\t}\n\t\t} catch {\n\t\t\t// Not JSON after all; fall through to shape 2.\n\t\t}\n\t}\n\n\t// Shape 2: a flat Databricks rejection.\n\treturn { message: body.message, type: code };\n}\n\n/**\n * Overlay the target envelope on the original body rather than replacing it,\n * so whatever else the gateway sent — `error_code`, request ids, the encoded\n * original message — still reaches the caller on `APICallError.responseBody`.\n */\nfunction emit(\n\tbody: unknown,\n\treason: Reason,\n\tdialect: GatewayErrorDialect,\n): unknown {\n\tconst original = isRecord(body) ? body : {};\n\tif (dialect === \"anthropic\") {\n\t\treturn {\n\t\t\t...original,\n\t\t\ttype: \"error\",\n\t\t\terror: {\n\t\t\t\t...reason.openaiFields,\n\t\t\t\ttype: reason.type ?? \"api_error\",\n\t\t\t\tmessage: reason.message,\n\t\t\t},\n\t\t};\n\t}\n\treturn {\n\t\t...original,\n\t\terror: {\n\t\t\t...reason.openaiFields,\n\t\t\tmessage: reason.message,\n\t\t\tcode: reason.openaiFields?.code ?? reason.type ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a gateway error body into `dialect`, or return null when it already\n * parses there (or is not a shape we recognise).\n */\nexport function normalizeGatewayErrorBody(\n\tbody: unknown,\n\tdialect: GatewayErrorDialect,\n): unknown | null {\n\tconst alreadyValid =\n\t\tdialect === \"anthropic\"\n\t\t\t? asAnthropicEnvelope(body) !== null\n\t\t\t: asOpenAIEnvelope(body) !== null;\n\tif (alreadyValid) {\n\t\treturn null;\n\t}\n\n\tconst reason = extractReason(body);\n\treturn reason === null ? null : emit(body, reason, dialect);\n}\n\n/** Wrap a fetch so failed responses reach the model in its own dialect. */\nexport function wrapFetchWithGatewayErrorNormalization(\n\tbaseFetch: FetchFunction | undefined,\n\tdialect: GatewayErrorDialect,\n): FetchFunction {\n\tconst inner: FetchFunction =\n\t\tbaseFetch ?? ((...args) => globalThis.fetch(...args));\n\n\treturn async (input, init) => {\n\t\tconst response = await inner(input, init);\n\t\tif (response.ok) {\n\t\t\treturn response;\n\t\t}\n\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\tif (!contentType.includes(\"application/json\")) {\n\t\t\treturn response;\n\t\t}\n\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(await response.clone().text());\n\t\t} catch {\n\t\t\treturn response;\n\t\t}\n\n\t\tconst normalized = normalizeGatewayErrorBody(parsed, dialect);\n\t\tif (normalized === null) {\n\t\t\treturn response;\n\t\t}\n\n\t\t// Rewriting the body invalidates both of these.\n\t\tconst headers = new Headers(response.headers);\n\t\theaders.delete(\"content-length\");\n\t\theaders.delete(\"content-encoding\");\n\n\t\treturn new Response(JSON.stringify(normalized), {\n\t\t\tstatus: response.status,\n\t\t\tstatusText: response.statusText,\n\t\t\theaders,\n\t\t});\n\t};\n}\n"],"mappings":";AAsDA,SAAS,SAAS,OAAkD;CACnE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC3E;AAEA,SAAS,iBAAiB,OAAgD;CACzE,IACC,SAAS,KAAK,KACd,SAAS,MAAM,KAAK,KACpB,OAAO,MAAM,MAAM,YAAY,UAE/B,OAAO,MAAM;CAEd,OAAO;AACR;AAEA,SAAS,oBAAoB,OAAgD;CAC5E,IACC,SAAS,KAAK,KACd,MAAM,SAAS,WACf,SAAS,MAAM,KAAK,KACpB,OAAO,MAAM,MAAM,YAAY,YAC/B,OAAO,MAAM,MAAM,SAAS,UAE5B,OAAO,MAAM;CAEd,OAAO;AACR;;AAGA,SAAS,cAAc,MAA8B;CACpD,MAAM,YAAY,oBAAoB,IAAI;CAC1C,IAAI,WACH,OAAO;EACN,SAAS,UAAU;EACnB,MAAM,UAAU;CACjB;CAGD,MAAM,SAAS,iBAAiB,IAAI;CACpC,IAAI,QACH,OAAO;EACN,SAAS,OAAO;EAChB,cAAc;EACd,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO,KAAA;CACvD;CAGD,IAAI,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,YAAY,UAC9C,OAAO;CAER,MAAM,OACL,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa,KAAA;CAIzD,IAAI,KAAK,QAAQ,UAAU,CAAC,CAAC,WAAW,GAAG,GAC1C,IAAI;EAEH,MAAM,SAAS,cADQ,KAAK,MAAM,KAAK,OACN,CAAC;EAClC,IAAI,QACH,OAAO;GAAE,GAAG;GAAQ,MAAM,OAAO,QAAQ;EAAK;CAEhD,QAAQ,CAER;CAID,OAAO;EAAE,SAAS,KAAK;EAAS,MAAM;CAAK;AAC5C;;;;;;AAOA,SAAS,KACR,MACA,QACA,SACU;CACV,MAAM,WAAW,SAAS,IAAI,IAAI,OAAO,CAAC;CAC1C,IAAI,YAAY,aACf,OAAO;EACN,GAAG;EACH,MAAM;EACN,OAAO;GACN,GAAG,OAAO;GACV,MAAM,OAAO,QAAQ;GACrB,SAAS,OAAO;EACjB;CACD;CAED,OAAO;EACN,GAAG;EACH,OAAO;GACN,GAAG,OAAO;GACV,SAAS,OAAO;GAChB,MAAM,OAAO,cAAc,QAAQ,OAAO,QAAQ;EACnD;CACD;AACD;;;;;AAMA,SAAgB,0BACf,MACA,SACiB;CAKjB,IAHC,YAAY,cACT,oBAAoB,IAAI,MAAM,OAC9B,iBAAiB,IAAI,MAAM,MAE9B,OAAO;CAGR,MAAM,SAAS,cAAc,IAAI;CACjC,OAAO,WAAW,OAAO,OAAO,KAAK,MAAM,QAAQ,OAAO;AAC3D;;AAGA,SAAgB,uCACf,WACA,SACgB;CAChB,MAAM,QACL,eAAe,GAAG,SAAS,WAAW,MAAM,GAAG,IAAI;CAEpD,OAAO,OAAO,OAAO,SAAS;EAC7B,MAAM,WAAW,MAAM,MAAM,OAAO,IAAI;EACxC,IAAI,SAAS,IACZ,OAAO;EAGR,IAAI,EADgB,SAAS,QAAQ,IAAI,cAAc,KAAK,GAAA,CAC3C,SAAS,kBAAkB,GAC3C,OAAO;EAGR,IAAI;EACJ,IAAI;GACH,SAAS,KAAK,MAAM,MAAM,SAAS,MAAM,CAAC,CAAC,KAAK,CAAC;EAClD,QAAQ;GACP,OAAO;EACR;EAEA,MAAM,aAAa,0BAA0B,QAAQ,OAAO;EAC5D,IAAI,eAAe,MAClB,OAAO;EAIR,MAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;EAC5C,QAAQ,OAAO,gBAAgB;EAC/B,QAAQ,OAAO,kBAAkB;EAEjC,OAAO,IAAI,SAAS,KAAK,UAAU,UAAU,GAAG;GAC/C,QAAQ,SAAS;GACjB,YAAY,SAAS;GACrB;EACD,CAAC;CACF;AACD"}
@@ -25,6 +25,13 @@ interface NeonModelCapabilities {
25
25
  supportsSeed: boolean;
26
26
  supportsStopSequences: boolean;
27
27
  supportsReasoningEffort: boolean;
28
+ /**
29
+ * True when a `claude-*` id could not be parsed into a version and was
30
+ * assumed to be new. The sampling restriction is then a precaution rather
31
+ * than something measured, and the warning says so instead of asserting a
32
+ * capability nobody checked.
33
+ */
34
+ claudeSamplingUnrecognized?: boolean;
28
35
  }
29
36
  /**
30
37
  * Heuristic, prefix-based capability detection for MLflow-routed Neon models.
@@ -1 +1 @@
1
- {"version":3,"file":"neon-model-capabilities.d.ts","names":[],"sources":["../../src/lib/neon-model-capabilities.ts"],"mappings":";KAAY,eAAA;AAAZ;AAiBA;AAEA;AAiBA;AAkCA;;;;;;KArDY,cAAA;iBAEI,iBAAA,mBAAoC;UAiBnC,qBAAA;UACR;;;;;;;;;;;;;;;;;;;;;;iBAiCO,wBAAA,mBAEb"}
1
+ {"version":3,"file":"neon-model-capabilities.d.ts","names":[],"sources":["../../src/lib/neon-model-capabilities.ts"],"mappings":";KAAY,eAAA;AAAZ;AAiBA;AAEA;AAiBA;AA8DA;;;;;;KAjFY,cAAA;iBAEI,iBAAA,mBAAoC;UAiBnC,qBAAA;UACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6DO,wBAAA,mBAEb"}
@@ -15,6 +15,20 @@ const PERMISSIVE = {
15
15
  supportsReasoningEffort: true
16
16
  };
17
17
  /**
18
+ * Whether a Claude id predates the 4.7 cutoff where the gateway stopped
19
+ * accepting `temperature` and `top_p`. Ids look like `claude-<tier>-<major>` or
20
+ * `claude-<tier>-<major>-<minor>`, optionally behind the legacy `databricks-`
21
+ * prefix. An id we cannot parse is treated as new, which is the safe direction.
22
+ */
23
+ function claudeVersion(id) {
24
+ const match = /^(?:databricks-)?claude-[a-z]+-(\d+)(?:-(\d+))?$/.exec(id);
25
+ if (!match) return null;
26
+ return {
27
+ major: Number(match[1]),
28
+ minor: match[2] === void 0 ? 0 : Number(match[2])
29
+ };
30
+ }
31
+ /**
18
32
  * Heuristic, prefix-based capability detection for MLflow-routed Neon models.
19
33
  *
20
34
  * The unified endpoint proxies to heterogeneous upstream providers, each of
@@ -25,16 +39,21 @@ const PERMISSIVE = {
25
39
  */
26
40
  function getNeonModelCapabilities(modelId) {
27
41
  const id = modelId.toLowerCase();
28
- if (id.includes("claude")) return {
29
- family: "anthropic",
30
- supportsTemperature: true,
31
- supportsTopP: true,
32
- temperatureTopPMutuallyExclusive: true,
33
- supportsPenalties: false,
34
- supportsSeed: false,
35
- supportsStopSequences: true,
36
- supportsReasoningEffort: false
37
- };
42
+ if (id.includes("claude")) {
43
+ const version = claudeVersion(id);
44
+ const sampling = version !== null && (version.major < 4 || version.major === 4 && version.minor <= 6);
45
+ return {
46
+ family: "anthropic",
47
+ supportsTemperature: sampling,
48
+ supportsTopP: sampling,
49
+ temperatureTopPMutuallyExclusive: true,
50
+ supportsPenalties: false,
51
+ supportsSeed: false,
52
+ supportsStopSequences: true,
53
+ supportsReasoningEffort: false,
54
+ ...version === null ? { claudeSamplingUnrecognized: true } : {}
55
+ };
56
+ }
38
57
  if (id.includes("gemini")) return {
39
58
  family: "google",
40
59
  ...PERMISSIVE,
@@ -63,6 +82,24 @@ function getNeonModelCapabilities(modelId) {
63
82
  supportsStopSequences: true,
64
83
  supportsReasoningEffort: true
65
84
  };
85
+ if (id.includes("gpt-oss")) return {
86
+ family: "other",
87
+ ...PERMISSIVE,
88
+ supportsPenalties: false,
89
+ supportsSeed: false,
90
+ supportsStopSequences: false
91
+ };
92
+ if (id.includes("qwen") || id.includes("gemma")) return {
93
+ family: "other",
94
+ ...PERMISSIVE,
95
+ supportsPenalties: false,
96
+ supportsSeed: false
97
+ };
98
+ if (id.includes("glm") || id.includes("inkling")) return {
99
+ family: "other",
100
+ ...PERMISSIVE,
101
+ supportsPenalties: false
102
+ };
66
103
  return {
67
104
  family: "other",
68
105
  ...PERMISSIVE
@@ -1 +1 @@
1
- {"version":3,"file":"neon-model-capabilities.js","names":[],"sources":["../../src/lib/neon-model-capabilities.ts"],"sourcesContent":["export type NeonModelFamily =\n\t| \"anthropic\"\n\t| \"google\"\n\t| \"openai\"\n\t| \"meta\"\n\t| \"other\";\n\n/**\n * Which gateway endpoint a model should be routed to.\n *\n * - `anthropic`: native Messages API — streaming structured output + native reasoning.\n * - `openai`: native Responses API — models served only natively (e.g. Codex),\n * native reasoning, and the image-generation tool.\n * - `mlflow`: the unified, OpenAI-compatible endpoint (the fallback). Gemini is\n * routed here because the gateway's native Gemini endpoint does not support\n * streaming (`streamGenerateContent` is rejected).\n */\nexport type NeonModelRoute = \"anthropic\" | \"openai\" | \"mlflow\";\n\nexport function getNeonModelRoute(modelId: string): NeonModelRoute {\n\tconst id = modelId.toLowerCase();\n\tif (id.includes(\"claude\")) {\n\t\treturn \"anthropic\";\n\t}\n\t// OpenAI proprietary models (gpt-4/gpt-5 families, Codex) are served only via\n\t// the native Responses API. `gpt-oss` is open-weight and served on the\n\t// unified chat endpoint, so it is intentionally excluded here.\n\tif (\n\t\t(id.includes(\"gpt-\") && !id.includes(\"gpt-oss\")) ||\n\t\tid.includes(\"codex\")\n\t) {\n\t\treturn \"openai\";\n\t}\n\treturn \"mlflow\";\n}\n\nexport interface NeonModelCapabilities {\n\tfamily: NeonModelFamily;\n\tsupportsTemperature: boolean;\n\tsupportsTopP: boolean;\n\t/**\n\t * Whether `temperature` and `topP` may be sent together. Anthropic models\n\t * accept only one of the two.\n\t */\n\ttemperatureTopPMutuallyExclusive: boolean;\n\tsupportsPenalties: boolean;\n\tsupportsSeed: boolean;\n\tsupportsStopSequences: boolean;\n\tsupportsReasoningEffort: boolean;\n}\n\nconst PERMISSIVE: Omit<NeonModelCapabilities, \"family\"> = {\n\tsupportsTemperature: true,\n\tsupportsTopP: true,\n\ttemperatureTopPMutuallyExclusive: false,\n\tsupportsPenalties: true,\n\tsupportsSeed: true,\n\tsupportsStopSequences: true,\n\tsupportsReasoningEffort: true,\n};\n\n/**\n * Heuristic, prefix-based capability detection for MLflow-routed Neon models.\n *\n * The unified endpoint proxies to heterogeneous upstream providers, each of\n * which accepts a different subset of the OpenAI-style parameters the AI SDK\n * emits. Sending a parameter an upstream rejects results in a hard `400`, so we\n * strip the parameters a family is known to reject and surface a warning\n * instead. Unknown/untested models stay permissive (passed through unchanged).\n */\nexport function getNeonModelCapabilities(\n\tmodelId: string,\n): NeonModelCapabilities {\n\tconst id = modelId.toLowerCase();\n\n\t// Anthropic (Claude): rejects penalties and seed, accepts only one of\n\t// temperature/topP, and rejects the OpenAI `reasoning_effort` field.\n\tif (id.includes(\"claude\")) {\n\t\treturn {\n\t\t\tfamily: \"anthropic\",\n\t\t\tsupportsTemperature: true,\n\t\t\tsupportsTopP: true,\n\t\t\ttemperatureTopPMutuallyExclusive: true,\n\t\t\tsupportsPenalties: false,\n\t\t\tsupportsSeed: false,\n\t\t\tsupportsStopSequences: true,\n\t\t\tsupportsReasoningEffort: false,\n\t\t};\n\t}\n\n\t// Google (Gemini): accepts standard sampling params but rejects the OpenAI\n\t// `reasoning_effort` field (not part of Gemini's generation config).\n\tif (id.includes(\"gemini\")) {\n\t\treturn {\n\t\t\tfamily: \"google\",\n\t\t\t...PERMISSIVE,\n\t\t\tsupportsReasoningEffort: false,\n\t\t};\n\t}\n\n\t// OpenAI GPT-5 reasoning family (routed to the native Responses API). The\n\t// Responses model strips parameters the Responses API doesn't accept\n\t// (penalties, seed, stop), but its reasoning-model detection matches the bare\n\t// model id (`gpt-5`), which the gateway's optional `databricks-` prefix can\n\t// defeat. So we only handle the temperature/topP restriction here: the\n\t// original gpt-5 / gpt-5-mini / gpt-5-nano require the default temperature and\n\t// reject topP, while gpt-5.1+ (a minor version digit follows) accept them\n\t// again. The regex matches both prefixed and unprefixed ids.\n\tif (/gpt-5/.test(id)) {\n\t\tconst hasMinorVersion = /gpt-5[.-]\\d/.test(id);\n\t\treturn {\n\t\t\tfamily: \"openai\",\n\t\t\tsupportsTemperature: hasMinorVersion,\n\t\t\tsupportsTopP: hasMinorVersion,\n\t\t\ttemperatureTopPMutuallyExclusive: false,\n\t\t\tsupportsPenalties: true,\n\t\t\tsupportsSeed: true,\n\t\t\tsupportsStopSequences: true,\n\t\t\tsupportsReasoningEffort: true,\n\t\t};\n\t}\n\n\t// Meta (Llama): rejects penalties and seed; accepts sampling params and stop.\n\tif (id.includes(\"llama\")) {\n\t\treturn {\n\t\t\tfamily: \"meta\",\n\t\t\tsupportsTemperature: true,\n\t\t\tsupportsTopP: true,\n\t\t\ttemperatureTopPMutuallyExclusive: false,\n\t\t\tsupportsPenalties: false,\n\t\t\tsupportsSeed: false,\n\t\t\tsupportsStopSequences: true,\n\t\t\tsupportsReasoningEffort: true,\n\t\t};\n\t}\n\n\treturn { family: \"other\", ...PERMISSIVE };\n}\n"],"mappings":";AAmBA,SAAgB,kBAAkB,SAAiC;CAClE,MAAM,KAAK,QAAQ,YAAY;CAC/B,IAAI,GAAG,SAAS,QAAQ,GACvB,OAAO;CAKR,IACE,GAAG,SAAS,MAAM,KAAK,CAAC,GAAG,SAAS,SAAS,KAC9C,GAAG,SAAS,OAAO,GAEnB,OAAO;CAER,OAAO;AACR;AAiBA,MAAM,aAAoD;CACzD,qBAAqB;CACrB,cAAc;CACd,kCAAkC;CAClC,mBAAmB;CACnB,cAAc;CACd,uBAAuB;CACvB,yBAAyB;AAC1B;;;;;;;;;;AAWA,SAAgB,yBACf,SACwB;CACxB,MAAM,KAAK,QAAQ,YAAY;CAI/B,IAAI,GAAG,SAAS,QAAQ,GACvB,OAAO;EACN,QAAQ;EACR,qBAAqB;EACrB,cAAc;EACd,kCAAkC;EAClC,mBAAmB;EACnB,cAAc;EACd,uBAAuB;EACvB,yBAAyB;CAC1B;CAKD,IAAI,GAAG,SAAS,QAAQ,GACvB,OAAO;EACN,QAAQ;EACR,GAAG;EACH,yBAAyB;CAC1B;CAWD,IAAI,QAAQ,KAAK,EAAE,GAAG;EACrB,MAAM,kBAAkB,cAAc,KAAK,EAAE;EAC7C,OAAO;GACN,QAAQ;GACR,qBAAqB;GACrB,cAAc;GACd,kCAAkC;GAClC,mBAAmB;GACnB,cAAc;GACd,uBAAuB;GACvB,yBAAyB;EAC1B;CACD;CAGA,IAAI,GAAG,SAAS,OAAO,GACtB,OAAO;EACN,QAAQ;EACR,qBAAqB;EACrB,cAAc;EACd,kCAAkC;EAClC,mBAAmB;EACnB,cAAc;EACd,uBAAuB;EACvB,yBAAyB;CAC1B;CAGD,OAAO;EAAE,QAAQ;EAAS,GAAG;CAAW;AACzC"}
1
+ {"version":3,"file":"neon-model-capabilities.js","names":[],"sources":["../../src/lib/neon-model-capabilities.ts"],"sourcesContent":["export type NeonModelFamily =\n\t| \"anthropic\"\n\t| \"google\"\n\t| \"openai\"\n\t| \"meta\"\n\t| \"other\";\n\n/**\n * Which gateway endpoint a model should be routed to.\n *\n * - `anthropic`: native Messages API — streaming structured output + native reasoning.\n * - `openai`: native Responses API — models served only natively (e.g. Codex),\n * native reasoning, and the image-generation tool.\n * - `mlflow`: the unified, OpenAI-compatible endpoint (the fallback). Gemini is\n * routed here because the gateway's native Gemini endpoint does not support\n * streaming (`streamGenerateContent` is rejected).\n */\nexport type NeonModelRoute = \"anthropic\" | \"openai\" | \"mlflow\";\n\nexport function getNeonModelRoute(modelId: string): NeonModelRoute {\n\tconst id = modelId.toLowerCase();\n\tif (id.includes(\"claude\")) {\n\t\treturn \"anthropic\";\n\t}\n\t// OpenAI proprietary models (gpt-4/gpt-5 families, Codex) are served only via\n\t// the native Responses API. `gpt-oss` is open-weight and served on the\n\t// unified chat endpoint, so it is intentionally excluded here.\n\tif (\n\t\t(id.includes(\"gpt-\") && !id.includes(\"gpt-oss\")) ||\n\t\tid.includes(\"codex\")\n\t) {\n\t\treturn \"openai\";\n\t}\n\treturn \"mlflow\";\n}\n\nexport interface NeonModelCapabilities {\n\tfamily: NeonModelFamily;\n\tsupportsTemperature: boolean;\n\tsupportsTopP: boolean;\n\t/**\n\t * Whether `temperature` and `topP` may be sent together. Anthropic models\n\t * accept only one of the two.\n\t */\n\ttemperatureTopPMutuallyExclusive: boolean;\n\tsupportsPenalties: boolean;\n\tsupportsSeed: boolean;\n\tsupportsStopSequences: boolean;\n\tsupportsReasoningEffort: boolean;\n\t/**\n\t * True when a `claude-*` id could not be parsed into a version and was\n\t * assumed to be new. The sampling restriction is then a precaution rather\n\t * than something measured, and the warning says so instead of asserting a\n\t * capability nobody checked.\n\t */\n\tclaudeSamplingUnrecognized?: boolean;\n}\n\nconst PERMISSIVE: Omit<NeonModelCapabilities, \"family\"> = {\n\tsupportsTemperature: true,\n\tsupportsTopP: true,\n\ttemperatureTopPMutuallyExclusive: false,\n\tsupportsPenalties: true,\n\tsupportsSeed: true,\n\tsupportsStopSequences: true,\n\tsupportsReasoningEffort: true,\n};\n\n/**\n * Whether a Claude id predates the 4.7 cutoff where the gateway stopped\n * accepting `temperature` and `top_p`. Ids look like `claude-<tier>-<major>` or\n * `claude-<tier>-<major>-<minor>`, optionally behind the legacy `databricks-`\n * prefix. An id we cannot parse is treated as new, which is the safe direction.\n */\nfunction claudeVersion(id: string): { major: number; minor: number } | null {\n\t// Anchored: `claude-opus-4-beta` and `claude-opus-4.7` must not match their\n\t// leading digits and be read as old models. A dated id such as\n\t// `claude-3-5-sonnet-20241022` does not parse either, and is reported as\n\t// unrecognised rather than silently classified.\n\tconst match = /^(?:databricks-)?claude-[a-z]+-(\\d+)(?:-(\\d+))?$/.exec(id);\n\tif (!match) {\n\t\treturn null;\n\t}\n\treturn {\n\t\tmajor: Number(match[1]),\n\t\tminor: match[2] === undefined ? 0 : Number(match[2]),\n\t};\n}\n\n/**\n * Heuristic, prefix-based capability detection for MLflow-routed Neon models.\n *\n * The unified endpoint proxies to heterogeneous upstream providers, each of\n * which accepts a different subset of the OpenAI-style parameters the AI SDK\n * emits. Sending a parameter an upstream rejects results in a hard `400`, so we\n * strip the parameters a family is known to reject and surface a warning\n * instead. Unknown/untested models stay permissive (passed through unchanged).\n */\nexport function getNeonModelCapabilities(\n\tmodelId: string,\n): NeonModelCapabilities {\n\tconst id = modelId.toLowerCase();\n\n\t// Anthropic (Claude): rejects penalties and seed, accepts only one of\n\t// temperature/topP, and rejects the OpenAI `reasoning_effort` field.\n\t//\n\t// From Claude 4.7 onward the gateway also rejects non-default sampling\n\t// entirely — `does not support the temperature parameter` /\n\t// `does not support sampling parameters: top_p` — because those models are\n\t// steered with `output_config.effort` instead. Measured: 4.1/4.5/4.6 accept\n\t// both, 4.7/4.8/5 reject both, and every id accepts the default\n\t// `temperature: 1.0`. A version comparison rather than a list of ids, so a\n\t// future Claude inherits the restriction: dropping a parameter the model\n\t// would have accepted costs a warning, claiming one it rejects costs a 400.\n\tif (id.includes(\"claude\")) {\n\t\tconst version = claudeVersion(id);\n\t\tconst sampling =\n\t\t\tversion !== null &&\n\t\t\t(version.major < 4 || (version.major === 4 && version.minor <= 6));\n\t\treturn {\n\t\t\tfamily: \"anthropic\",\n\t\t\tsupportsTemperature: sampling,\n\t\t\tsupportsTopP: sampling,\n\t\t\ttemperatureTopPMutuallyExclusive: true,\n\t\t\tsupportsPenalties: false,\n\t\t\tsupportsSeed: false,\n\t\t\tsupportsStopSequences: true,\n\t\t\tsupportsReasoningEffort: false,\n\t\t\t...(version === null ? { claudeSamplingUnrecognized: true } : {}),\n\t\t};\n\t}\n\n\t// Google (Gemini): accepts standard sampling params but rejects the OpenAI\n\t// `reasoning_effort` field (not part of Gemini's generation config).\n\tif (id.includes(\"gemini\")) {\n\t\treturn {\n\t\t\tfamily: \"google\",\n\t\t\t...PERMISSIVE,\n\t\t\tsupportsReasoningEffort: false,\n\t\t};\n\t}\n\n\t// OpenAI GPT-5 reasoning family (routed to the native Responses API). The\n\t// Responses model strips parameters the Responses API doesn't accept\n\t// (penalties, seed, stop), but its reasoning-model detection matches the bare\n\t// model id (`gpt-5`), which the gateway's optional `databricks-` prefix can\n\t// defeat. So we only handle the temperature/topP restriction here: the\n\t// original gpt-5 / gpt-5-mini / gpt-5-nano require the default temperature and\n\t// reject topP, while gpt-5.1+ (a minor version digit follows) accept them\n\t// again. The regex matches both prefixed and unprefixed ids.\n\tif (/gpt-5/.test(id)) {\n\t\tconst hasMinorVersion = /gpt-5[.-]\\d/.test(id);\n\t\treturn {\n\t\t\tfamily: \"openai\",\n\t\t\tsupportsTemperature: hasMinorVersion,\n\t\t\tsupportsTopP: hasMinorVersion,\n\t\t\ttemperatureTopPMutuallyExclusive: false,\n\t\t\tsupportsPenalties: true,\n\t\t\tsupportsSeed: true,\n\t\t\tsupportsStopSequences: true,\n\t\t\tsupportsReasoningEffort: true,\n\t\t};\n\t}\n\n\t// Meta (Llama): rejects penalties and seed; accepts sampling params and stop.\n\tif (id.includes(\"llama\")) {\n\t\treturn {\n\t\t\tfamily: \"meta\",\n\t\t\tsupportsTemperature: true,\n\t\t\tsupportsTopP: true,\n\t\t\ttemperatureTopPMutuallyExclusive: false,\n\t\t\tsupportsPenalties: false,\n\t\t\tsupportsSeed: false,\n\t\t\tsupportsStopSequences: true,\n\t\t\tsupportsReasoningEffort: true,\n\t\t};\n\t}\n\n\t// Everything else on the unified endpoint rejects penalties with\n\t// `parameter \"frequency_penalty\" must be equal to 0`. Gemini, handled above,\n\t// is the only MLflow-routed family that accepts them. Seed and stop vary, so\n\t// each family carries what was measured rather than inheriting a guess.\n\tif (id.includes(\"gpt-oss\")) {\n\t\treturn {\n\t\t\tfamily: \"other\",\n\t\t\t...PERMISSIVE,\n\t\t\tsupportsPenalties: false,\n\t\t\tsupportsSeed: false,\n\t\t\tsupportsStopSequences: false,\n\t\t};\n\t}\n\n\tif (id.includes(\"qwen\") || id.includes(\"gemma\")) {\n\t\treturn {\n\t\t\tfamily: \"other\",\n\t\t\t...PERMISSIVE,\n\t\t\tsupportsPenalties: false,\n\t\t\tsupportsSeed: false,\n\t\t};\n\t}\n\n\tif (id.includes(\"glm\") || id.includes(\"inkling\")) {\n\t\treturn { family: \"other\", ...PERMISSIVE, supportsPenalties: false };\n\t}\n\n\treturn { family: \"other\", ...PERMISSIVE };\n}\n"],"mappings":";AAmBA,SAAgB,kBAAkB,SAAiC;CAClE,MAAM,KAAK,QAAQ,YAAY;CAC/B,IAAI,GAAG,SAAS,QAAQ,GACvB,OAAO;CAKR,IACE,GAAG,SAAS,MAAM,KAAK,CAAC,GAAG,SAAS,SAAS,KAC9C,GAAG,SAAS,OAAO,GAEnB,OAAO;CAER,OAAO;AACR;AAwBA,MAAM,aAAoD;CACzD,qBAAqB;CACrB,cAAc;CACd,kCAAkC;CAClC,mBAAmB;CACnB,cAAc;CACd,uBAAuB;CACvB,yBAAyB;AAC1B;;;;;;;AAQA,SAAS,cAAc,IAAqD;CAK3E,MAAM,QAAQ,mDAAmD,KAAK,EAAE;CACxE,IAAI,CAAC,OACJ,OAAO;CAER,OAAO;EACN,OAAO,OAAO,MAAM,EAAE;EACtB,OAAO,MAAM,OAAO,KAAA,IAAY,IAAI,OAAO,MAAM,EAAE;CACpD;AACD;;;;;;;;;;AAWA,SAAgB,yBACf,SACwB;CACxB,MAAM,KAAK,QAAQ,YAAY;CAa/B,IAAI,GAAG,SAAS,QAAQ,GAAG;EAC1B,MAAM,UAAU,cAAc,EAAE;EAChC,MAAM,WACL,YAAY,SACX,QAAQ,QAAQ,KAAM,QAAQ,UAAU,KAAK,QAAQ,SAAS;EAChE,OAAO;GACN,QAAQ;GACR,qBAAqB;GACrB,cAAc;GACd,kCAAkC;GAClC,mBAAmB;GACnB,cAAc;GACd,uBAAuB;GACvB,yBAAyB;GACzB,GAAI,YAAY,OAAO,EAAE,4BAA4B,KAAK,IAAI,CAAC;EAChE;CACD;CAIA,IAAI,GAAG,SAAS,QAAQ,GACvB,OAAO;EACN,QAAQ;EACR,GAAG;EACH,yBAAyB;CAC1B;CAWD,IAAI,QAAQ,KAAK,EAAE,GAAG;EACrB,MAAM,kBAAkB,cAAc,KAAK,EAAE;EAC7C,OAAO;GACN,QAAQ;GACR,qBAAqB;GACrB,cAAc;GACd,kCAAkC;GAClC,mBAAmB;GACnB,cAAc;GACd,uBAAuB;GACvB,yBAAyB;EAC1B;CACD;CAGA,IAAI,GAAG,SAAS,OAAO,GACtB,OAAO;EACN,QAAQ;EACR,qBAAqB;EACrB,cAAc;EACd,kCAAkC;EAClC,mBAAmB;EACnB,cAAc;EACd,uBAAuB;EACvB,yBAAyB;CAC1B;CAOD,IAAI,GAAG,SAAS,SAAS,GACxB,OAAO;EACN,QAAQ;EACR,GAAG;EACH,mBAAmB;EACnB,cAAc;EACd,uBAAuB;CACxB;CAGD,IAAI,GAAG,SAAS,MAAM,KAAK,GAAG,SAAS,OAAO,GAC7C,OAAO;EACN,QAAQ;EACR,GAAG;EACH,mBAAmB;EACnB,cAAc;CACf;CAGD,IAAI,GAAG,SAAS,KAAK,KAAK,GAAG,SAAS,SAAS,GAC9C,OAAO;EAAE,QAAQ;EAAS,GAAG;EAAY,mBAAmB;CAAM;CAGnE,OAAO;EAAE,QAAQ;EAAS,GAAG;CAAW;AACzC"}
@@ -5,20 +5,14 @@ import { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3GenerateRes
5
5
 
6
6
  /**
7
7
  * Language model for OpenAI models served via the Neon AI Gateway's native
8
- * Responses API (`/openai/v1/responses`). This route is required for
9
- * models that are only served natively (e.g. Codex) and unlocks native
10
- * reasoning and the image-generation tool.
8
+ * Responses API (`/openai/v1/responses`). This route is required for models
9
+ * that are only served natively (e.g. Codex) and unlocks native reasoning and
10
+ * the image-generation tool.
11
11
  *
12
- * The shared OpenAI Responses model shapes a request based on whether the model
13
- * is a reasoning model, but its detection is brittle here: the gateway also
14
- * accepts the legacy `databricks-` prefix, which defeats the upstream bare-id
15
- * (`gpt-5`) match. For the GPT-5 reasoning family we set the model's own
16
- * `forceReasoning` provider option so it applies the correct reasoning behavior
17
- * for both id forms. Users can still override it.
12
+ * The request shaping lives in `neon-responses-options.ts`, which documents
13
+ * what this route requires and why.
18
14
  */
19
15
  declare class NeonResponsesLanguageModel extends OpenAIResponsesLanguageModel implements LanguageModelV3 {
20
- private get isReasoningFamily();
21
- private withForcedReasoning;
22
16
  doGenerate(options: LanguageModelV3CallOptions): Promise<LanguageModelV3GenerateResult>;
23
17
  doStream(options: LanguageModelV3CallOptions): Promise<LanguageModelV3StreamResult>;
24
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"neon-responses-language-model.d.ts","names":[],"sources":["../../src/lib/neon-responses-language-model.ts"],"mappings":";;;;;;;AAqBA;AACC;AA4BU;AACC;AAAR;AAKO;AACC;AAAR;AAnCK;AACG;AAAe;cAFd,0BAAA,SACJ,4BAAA,YACG;;;sBA2BD,6BACP,QAAQ;oBAKD,6BACP,QAAQ"}
1
+ {"version":3,"file":"neon-responses-language-model.d.ts","names":[],"sources":["../../src/lib/neon-responses-language-model.ts"],"mappings":";;;;;;;AAkBA;AACC;AAIU;AACC;AAAR;AAOO;AACC;AAAR,cAdS,0BAAA,SACJ,4BAAA,YACG,eAYR,CAAA;EAbK,UAAA,CAAA,OAAA,EAIE,0BAJF,CAAA,EAKL,OALK,CAKG,6BALH,CAAA;EACG,QAAA,CAAA,OAAA,EAWD,0BAXC,CAAA,EAYR,OAZQ,CAYA,2BAZA,CAAA;AAAe"}
@@ -1,42 +1,21 @@
1
+ import { withResponsesGatewayDefaults } from "./neon-responses-options.js";
1
2
  import { OpenAIResponsesLanguageModel } from "@ai-sdk/openai/internal";
2
3
  //#region src/lib/neon-responses-language-model.ts
3
4
  /**
4
5
  * Language model for OpenAI models served via the Neon AI Gateway's native
5
- * Responses API (`/openai/v1/responses`). This route is required for
6
- * models that are only served natively (e.g. Codex) and unlocks native
7
- * reasoning and the image-generation tool.
6
+ * Responses API (`/openai/v1/responses`). This route is required for models
7
+ * that are only served natively (e.g. Codex) and unlocks native reasoning and
8
+ * the image-generation tool.
8
9
  *
9
- * The shared OpenAI Responses model shapes a request based on whether the model
10
- * is a reasoning model, but its detection is brittle here: the gateway also
11
- * accepts the legacy `databricks-` prefix, which defeats the upstream bare-id
12
- * (`gpt-5`) match. For the GPT-5 reasoning family we set the model's own
13
- * `forceReasoning` provider option so it applies the correct reasoning behavior
14
- * for both id forms. Users can still override it.
10
+ * The request shaping lives in `neon-responses-options.ts`, which documents
11
+ * what this route requires and why.
15
12
  */
16
13
  var NeonResponsesLanguageModel = class extends OpenAIResponsesLanguageModel {
17
- get isReasoningFamily() {
18
- return /gpt-5/.test(this.modelId.toLowerCase());
19
- }
20
- withForcedReasoning(options) {
21
- if (!this.isReasoningFamily) return options;
22
- const openai = options.providerOptions?.openai;
23
- if (openai != null && "forceReasoning" in openai) return options;
24
- return {
25
- ...options,
26
- providerOptions: {
27
- ...options.providerOptions,
28
- openai: {
29
- ...openai,
30
- forceReasoning: true
31
- }
32
- }
33
- };
34
- }
35
14
  doGenerate(options) {
36
- return super.doGenerate(this.withForcedReasoning(options));
15
+ return super.doGenerate(withResponsesGatewayDefaults(this.modelId, options));
37
16
  }
38
17
  doStream(options) {
39
- return super.doStream(this.withForcedReasoning(options));
18
+ return super.doStream(withResponsesGatewayDefaults(this.modelId, options));
40
19
  }
41
20
  };
42
21
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"neon-responses-language-model.js","names":[],"sources":["../../src/lib/neon-responses-language-model.ts"],"sourcesContent":["import { OpenAIResponsesLanguageModel } from \"@ai-sdk/openai/internal\";\nimport type {\n\tLanguageModelV3,\n\tLanguageModelV3CallOptions,\n\tLanguageModelV3GenerateResult,\n\tLanguageModelV3StreamResult,\n} from \"@ai-sdk/provider\";\n\n/**\n * Language model for OpenAI models served via the Neon AI Gateway's native\n * Responses API (`/openai/v1/responses`). This route is required for\n * models that are only served natively (e.g. Codex) and unlocks native\n * reasoning and the image-generation tool.\n *\n * The shared OpenAI Responses model shapes a request based on whether the model\n * is a reasoning model, but its detection is brittle here: the gateway also\n * accepts the legacy `databricks-` prefix, which defeats the upstream bare-id\n * (`gpt-5`) match. For the GPT-5 reasoning family we set the model's own\n * `forceReasoning` provider option so it applies the correct reasoning behavior\n * for both id forms. Users can still override it.\n */\nexport class NeonResponsesLanguageModel\n\textends OpenAIResponsesLanguageModel\n\timplements LanguageModelV3\n{\n\tprivate get isReasoningFamily(): boolean {\n\t\treturn /gpt-5/.test(this.modelId.toLowerCase());\n\t}\n\n\tprivate withForcedReasoning(\n\t\toptions: LanguageModelV3CallOptions,\n\t): LanguageModelV3CallOptions {\n\t\tif (!this.isReasoningFamily) {\n\t\t\treturn options;\n\t\t}\n\t\tconst openai = options.providerOptions?.openai;\n\t\t// Respect an explicit user setting.\n\t\tif (openai != null && \"forceReasoning\" in openai) {\n\t\t\treturn options;\n\t\t}\n\t\treturn {\n\t\t\t...options,\n\t\t\tproviderOptions: {\n\t\t\t\t...options.providerOptions,\n\t\t\t\topenai: { ...openai, forceReasoning: true },\n\t\t\t},\n\t\t};\n\t}\n\n\toverride doGenerate(\n\t\toptions: LanguageModelV3CallOptions,\n\t): Promise<LanguageModelV3GenerateResult> {\n\t\treturn super.doGenerate(this.withForcedReasoning(options));\n\t}\n\n\toverride doStream(\n\t\toptions: LanguageModelV3CallOptions,\n\t): Promise<LanguageModelV3StreamResult> {\n\t\treturn super.doStream(this.withForcedReasoning(options));\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,IAAa,6BAAb,cACS,6BAET;CACC,IAAY,oBAA6B;EACxC,OAAO,QAAQ,KAAK,KAAK,QAAQ,YAAY,CAAC;CAC/C;CAEA,oBACC,SAC6B;EAC7B,IAAI,CAAC,KAAK,mBACT,OAAO;EAER,MAAM,SAAS,QAAQ,iBAAiB;EAExC,IAAI,UAAU,QAAQ,oBAAoB,QACzC,OAAO;EAER,OAAO;GACN,GAAG;GACH,iBAAiB;IAChB,GAAG,QAAQ;IACX,QAAQ;KAAE,GAAG;KAAQ,gBAAgB;IAAK;GAC3C;EACD;CACD;CAEA,WACC,SACyC;EACzC,OAAO,MAAM,WAAW,KAAK,oBAAoB,OAAO,CAAC;CAC1D;CAEA,SACC,SACuC;EACvC,OAAO,MAAM,SAAS,KAAK,oBAAoB,OAAO,CAAC;CACxD;AACD"}
1
+ {"version":3,"file":"neon-responses-language-model.js","names":[],"sources":["../../src/lib/neon-responses-language-model.ts"],"sourcesContent":["import { OpenAIResponsesLanguageModel } from \"@ai-sdk/openai/internal\";\nimport type {\n\tLanguageModelV3,\n\tLanguageModelV3CallOptions,\n\tLanguageModelV3GenerateResult,\n\tLanguageModelV3StreamResult,\n} from \"@ai-sdk/provider\";\nimport { withResponsesGatewayDefaults } from \"./neon-responses-options.js\";\n\n/**\n * Language model for OpenAI models served via the Neon AI Gateway's native\n * Responses API (`/openai/v1/responses`). This route is required for models\n * that are only served natively (e.g. Codex) and unlocks native reasoning and\n * the image-generation tool.\n *\n * The request shaping lives in `neon-responses-options.ts`, which documents\n * what this route requires and why.\n */\nexport class NeonResponsesLanguageModel\n\textends OpenAIResponsesLanguageModel\n\timplements LanguageModelV3\n{\n\toverride doGenerate(\n\t\toptions: LanguageModelV3CallOptions,\n\t): Promise<LanguageModelV3GenerateResult> {\n\t\treturn super.doGenerate(\n\t\t\twithResponsesGatewayDefaults(this.modelId, options),\n\t\t);\n\t}\n\n\toverride doStream(\n\t\toptions: LanguageModelV3CallOptions,\n\t): Promise<LanguageModelV3StreamResult> {\n\t\treturn super.doStream(\n\t\t\twithResponsesGatewayDefaults(this.modelId, options),\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AAkBA,IAAa,6BAAb,cACS,6BAET;CACC,WACC,SACyC;EACzC,OAAO,MAAM,WACZ,6BAA6B,KAAK,SAAS,OAAO,CACnD;CACD;CAEA,SACC,SACuC;EACvC,OAAO,MAAM,SACZ,6BAA6B,KAAK,SAAS,OAAO,CACnD;CACD;AACD"}
@@ -0,0 +1,14 @@
1
+ import { LanguageModelV3CallOptions } from "@ai-sdk/provider";
2
+
3
+ //#region src/lib/neon-responses-options.d.ts
4
+ /** Models the gateway serves as reasoning models on this route. */
5
+ declare function isReasoningModelId(modelId: string): boolean;
6
+ /**
7
+ * Apply the route's defaults to one call, or throw when a caller asked for
8
+ * something the gateway cannot do. Returns the options unchanged when there is
9
+ * nothing to add.
10
+ */
11
+ declare function withResponsesGatewayDefaults(modelId: string, options: LanguageModelV3CallOptions): LanguageModelV3CallOptions;
12
+ //#endregion
13
+ export { isReasoningModelId, withResponsesGatewayDefaults };
14
+ //# sourceMappingURL=neon-responses-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neon-responses-options.d.ts","names":[],"sources":["../../src/lib/neon-responses-options.ts"],"mappings":";;;;iBAmCgB,kBAAA;AAAhB;AASA;AAA4C;AAElC;AACP;AAA0B,iBAHb,4BAAA,CAGa,OAAA,EAAA,MAAA,EAAA,OAAA,EADnB,0BACmB,CAAA,EAA1B,0BAA0B"}
@@ -0,0 +1,66 @@
1
+ import { UnsupportedFunctionalityError } from "@ai-sdk/provider";
2
+ //#region src/lib/neon-responses-options.ts
3
+ /**
4
+ * Request defaults for the Neon AI Gateway's Responses route.
5
+ *
6
+ * `store: false` is the one value this gateway accepts, not a preference. It
7
+ * serves the Responses API statelessly and keeps no items — a response comes
8
+ * back with `"store": false` even when the request never set it. Left alone the
9
+ * shared OpenAI model assumes stored-item semantics and replays earlier
10
+ * reasoning as `{ type: "item_reference", id }`, which nothing upstream can
11
+ * resolve; the gateway answers `502 INTERNAL_ERROR` on the second step of a
12
+ * tool loop, after the first has already succeeded. Sending `false` makes the
13
+ * model inline the encrypted reasoning instead.
14
+ *
15
+ * Since no other value can work, an explicit `store` that is not `false` is
16
+ * refused rather than sent: the request would come back `400
17
+ * INVALID_PARAMETER_VALUE` after a round trip, and quietly forcing it to
18
+ * `false` would be the wrong kind of quiet for a data-retention flag. A
19
+ * type-level constraint is not available — `providerOptions` is
20
+ * `Record<string, JSONObject>`, so a provider cannot narrow it at the call
21
+ * site.
22
+ *
23
+ * `forceReasoning: true` covers a gap in the shared model's own detection,
24
+ * which matches a bare `gpt-5` prefix. The gateway also accepts the legacy
25
+ * `databricks-` form, and `databricks-gpt-5-2` fails that match while still
26
+ * being a reasoning model.
27
+ */
28
+ const STATELESS_HELP = "See https://github.com/neondatabase/neon-pkgs/tree/main/packages/ai-sdk-provider#errors";
29
+ /** Models the gateway serves as reasoning models on this route. */
30
+ function isReasoningModelId(modelId) {
31
+ return /gpt-5/.test(modelId.toLowerCase());
32
+ }
33
+ /**
34
+ * Apply the route's defaults to one call, or throw when a caller asked for
35
+ * something the gateway cannot do. Returns the options unchanged when there is
36
+ * nothing to add.
37
+ */
38
+ function withResponsesGatewayDefaults(modelId, options) {
39
+ const openai = options.providerOptions?.openai;
40
+ const defaults = {};
41
+ if (openai?.store === void 0) defaults.store = false;
42
+ else if (openai.store === true || openai.store === null) throw new UnsupportedFunctionalityError({
43
+ functionality: "storing responses (providerOptions.openai.store)",
44
+ message: `\`store: ${String(openai.store)}\` is not available on the Neon AI Gateway, which serves the Responses API statelessly and never persists response items. Remove \`providerOptions.openai.store\`, or set it to \`false\`.` + (openai.store === null ? " `null` is not the same as omitting it — the AI SDK reads a null `store` as `true`." : "") + ` ${STATELESS_HELP}`
45
+ });
46
+ for (const option of ["previousResponseId", "conversation"]) if (openai?.[option] != null) throw new UnsupportedFunctionalityError({
47
+ functionality: `server-side conversation state (providerOptions.openai.${option})`,
48
+ message: `\`${option}\` is not available on the Neon AI Gateway, which serves the Responses API statelessly and never persists response items, so there is nothing for \`${option}\` to refer to. Send the full message history instead. ${STATELESS_HELP}`
49
+ });
50
+ if (isReasoningModelId(modelId) && openai?.forceReasoning === void 0) defaults.forceReasoning = true;
51
+ if (Object.keys(defaults).length === 0) return options;
52
+ return {
53
+ ...options,
54
+ providerOptions: {
55
+ ...options.providerOptions,
56
+ openai: {
57
+ ...openai,
58
+ ...defaults
59
+ }
60
+ }
61
+ };
62
+ }
63
+ //#endregion
64
+ export { isReasoningModelId, withResponsesGatewayDefaults };
65
+
66
+ //# sourceMappingURL=neon-responses-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neon-responses-options.js","names":[],"sources":["../../src/lib/neon-responses-options.ts"],"sourcesContent":["import {\n\ttype LanguageModelV3CallOptions,\n\tUnsupportedFunctionalityError,\n} from \"@ai-sdk/provider\";\n\n/**\n * Request defaults for the Neon AI Gateway's Responses route.\n *\n * `store: false` is the one value this gateway accepts, not a preference. It\n * serves the Responses API statelessly and keeps no items — a response comes\n * back with `\"store\": false` even when the request never set it. Left alone the\n * shared OpenAI model assumes stored-item semantics and replays earlier\n * reasoning as `{ type: \"item_reference\", id }`, which nothing upstream can\n * resolve; the gateway answers `502 INTERNAL_ERROR` on the second step of a\n * tool loop, after the first has already succeeded. Sending `false` makes the\n * model inline the encrypted reasoning instead.\n *\n * Since no other value can work, an explicit `store` that is not `false` is\n * refused rather than sent: the request would come back `400\n * INVALID_PARAMETER_VALUE` after a round trip, and quietly forcing it to\n * `false` would be the wrong kind of quiet for a data-retention flag. A\n * type-level constraint is not available — `providerOptions` is\n * `Record<string, JSONObject>`, so a provider cannot narrow it at the call\n * site.\n *\n * `forceReasoning: true` covers a gap in the shared model's own detection,\n * which matches a bare `gpt-5` prefix. The gateway also accepts the legacy\n * `databricks-` form, and `databricks-gpt-5-2` fails that match while still\n * being a reasoning model.\n */\n\nconst STATELESS_HELP =\n\t\"See https://github.com/neondatabase/neon-pkgs/tree/main/packages/ai-sdk-provider#errors\";\n\n/** Models the gateway serves as reasoning models on this route. */\nexport function isReasoningModelId(modelId: string): boolean {\n\treturn /gpt-5/.test(modelId.toLowerCase());\n}\n\n/**\n * Apply the route's defaults to one call, or throw when a caller asked for\n * something the gateway cannot do. Returns the options unchanged when there is\n * nothing to add.\n */\nexport function withResponsesGatewayDefaults(\n\tmodelId: string,\n\toptions: LanguageModelV3CallOptions,\n): LanguageModelV3CallOptions {\n\tconst openai = options.providerOptions?.openai;\n\tconst defaults: Record<string, boolean> = {};\n\n\t// `undefined` counts as unset rather than as a choice: it serializes away,\n\t// so `{ store: undefined }` and `{}` are the same request on the wire.\n\tif (openai?.store === undefined) {\n\t\tdefaults.store = false;\n\t} else if (openai.store === true || openai.store === null) {\n\t\t// `null` reads as \"omitted\" but is not: the shared model resolves it\n\t\t// with `store ?? true`, so it asks for storage just as `true` does.\n\t\tthrow new UnsupportedFunctionalityError({\n\t\t\tfunctionality: \"storing responses (providerOptions.openai.store)\",\n\t\t\tmessage:\n\t\t\t\t`\\`store: ${String(openai.store)}\\` is not available on the Neon AI ` +\n\t\t\t\t\"Gateway, which serves the Responses API statelessly and never \" +\n\t\t\t\t\"persists response items. Remove `providerOptions.openai.store`, or \" +\n\t\t\t\t\"set it to `false`.\" +\n\t\t\t\t(openai.store === null\n\t\t\t\t\t? \" `null` is not the same as omitting it — the AI SDK reads a null\" +\n\t\t\t\t\t\t\" `store` as `true`.\"\n\t\t\t\t\t: \"\") +\n\t\t\t\t` ${STATELESS_HELP}`,\n\t\t});\n\t}\n\n\t// Any other type is left for the shared model's own provider-option schema\n\t// (`store: z.boolean().nullish()`), which rejects it locally with a type\n\t// error. Only `true` and `null` pass that schema and are refused by the\n\t// gateway, so only they need saying something about here.\n\n\t// The same statelessness rules these out, and they belong to the same set:\n\t// refusing one and forwarding the others would read as an oversight.\n\tfor (const option of [\"previousResponseId\", \"conversation\"] as const) {\n\t\tif (openai?.[option] != null) {\n\t\t\tthrow new UnsupportedFunctionalityError({\n\t\t\t\tfunctionality: `server-side conversation state (providerOptions.openai.${option})`,\n\t\t\t\tmessage:\n\t\t\t\t\t`\\`${option}\\` is not available on the Neon AI Gateway, which ` +\n\t\t\t\t\t\"serves the Responses API statelessly and never persists response \" +\n\t\t\t\t\t`items, so there is nothing for \\`${option}\\` to refer to. Send ` +\n\t\t\t\t\t`the full message history instead. ${STATELESS_HELP}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tif (isReasoningModelId(modelId) && openai?.forceReasoning === undefined) {\n\t\tdefaults.forceReasoning = true;\n\t}\n\n\tif (Object.keys(defaults).length === 0) {\n\t\treturn options;\n\t}\n\treturn {\n\t\t...options,\n\t\tproviderOptions: {\n\t\t\t...options.providerOptions,\n\t\t\topenai: { ...openai, ...defaults },\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAM,iBACL;;AAGD,SAAgB,mBAAmB,SAA0B;CAC5D,OAAO,QAAQ,KAAK,QAAQ,YAAY,CAAC;AAC1C;;;;;;AAOA,SAAgB,6BACf,SACA,SAC6B;CAC7B,MAAM,SAAS,QAAQ,iBAAiB;CACxC,MAAM,WAAoC,CAAC;CAI3C,IAAI,QAAQ,UAAU,KAAA,GACrB,SAAS,QAAQ;MACX,IAAI,OAAO,UAAU,QAAQ,OAAO,UAAU,MAGpD,MAAM,IAAI,8BAA8B;EACvC,eAAe;EACf,SACC,YAAY,OAAO,OAAO,KAAK,EAAE,+LAIhC,OAAO,UAAU,OACf,wFAEA,MACH,IAAI;CACN,CAAC;CAUF,KAAK,MAAM,UAAU,CAAC,sBAAsB,cAAc,GACzD,IAAI,SAAS,WAAW,MACvB,MAAM,IAAI,8BAA8B;EACvC,eAAe,0DAA0D,OAAO;EAChF,SACC,KAAK,OAAO,sJAEwB,OAAO,yDACN;CACvC,CAAC;CAIH,IAAI,mBAAmB,OAAO,KAAK,QAAQ,mBAAmB,KAAA,GAC7D,SAAS,iBAAiB;CAG3B,IAAI,OAAO,KAAK,QAAQ,CAAC,CAAC,WAAW,GACpC,OAAO;CAER,OAAO;EACN,GAAG;EACH,iBAAiB;GAChB,GAAG,QAAQ;GACX,QAAQ;IAAE,GAAG;IAAQ,GAAG;GAAS;EAClC;CACD;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/lib/provider.ts"],"mappings":";;;;;;;cAoCM,iBAAe,CAAA,CAAA;;IAAf,OAAA,aAOJ;IAAA,IAAA,eAAA,cAAA,YAAA,CAAA,CAAA;;;;GAAA,CAAA,CAAA,IAAA,CAAA;KAEU,aAAA,GAAgB,CAAA,CAAE,aAAa;UA4C1B,oBAAA;;;;;;;EA9Cf;AAPmB;AAAA;AASrB;EAAyB,MAAA,CAAA,EAAA,MAAA;EAAkB;EAAf,OAAE,CAAA,EA2DnB,MA3DmB,CAAA,MAAA,EAAA,MAAA,CAAA;EAAK;EA4ClB,KAAA,CAAA,EAkBR,aAlBQ;AAAoB;AAe1B,UAMM,YAAA,SAAqB,UAN3B,CAAA;EAGF;EAAa,CAAA,OAAA,EAKX,eALW,CAAA,EAKO,eALP;EAGL;EAAa,aAAA,CAAA,OAAA,EAKN,eALM,CAAA,EAKY,eALZ;EAEnB;EAAkB,IAAA,CAAA,OAAA,EAMd,eANc,CAAA,EAMI,eANJ;EAGL;EAAkB,KAAA,EAAA,OAM3B,eAN2B;EAG3B;EAAkB,kBAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,KAAA;AAGlB;AAXuB,iBAiBtB,UAAA,CAjBsB,OAAA,CAAA,EAiBF,oBAjBE,CAAA,EAiB0B,YAjB1B;AAAU;AAiBhC,cA+FH,IA/Fa,EA+FT,YA/FS"}
1
+ {"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/lib/provider.ts"],"mappings":";;;;;;;cAqCM,iBAAe,CAAA,CAAA;;IAAf,OAAA,aAOJ;IAAA,IAAA,eAAA,cAAA,YAAA,CAAA,CAAA;;;;GAAA,CAAA,CAAA,IAAA,CAAA;KAEU,aAAA,GAAgB,CAAA,CAAE,aAAa;UA4C1B,oBAAA;;;;;;;EA9Cf;AAPmB;AAAA;AASrB;EAAyB,MAAA,CAAA,EAAA,MAAA;EAAkB;EAAf,OAAE,CAAA,EA2DnB,MA3DmB,CAAA,MAAA,EAAA,MAAA,CAAA;EAAK;EA4ClB,KAAA,CAAA,EAkBR,aAlBQ;AAAoB;AAe1B,UAMM,YAAA,SAAqB,UAN3B,CAAA;EAGF;EAAa,CAAA,OAAA,EAKX,eALW,CAAA,EAKO,eALP;EAGL;EAAa,aAAA,CAAA,OAAA,EAKN,eALM,CAAA,EAKY,eALZ;EAEnB;EAAkB,IAAA,CAAA,OAAA,EAMd,eANc,CAAA,EAMI,eANJ;EAGL;EAAkB,KAAA,EAAA,OAM3B,eAN2B;EAG3B;EAAkB,kBAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,KAAA;AAGlB;AAXuB,iBAiBtB,UAAA,CAjBsB,OAAA,CAAA,EAiBF,oBAjBE,CAAA,EAiB0B,YAjB1B;AAAU;AAiBhC,cAiHH,IAjHa,EAiHT,YAjHS"}
@@ -1,7 +1,8 @@
1
- import { NeonAnthropicLanguageModel } from "./neon-anthropic-language-model.js";
2
1
  import { getNeonModelRoute } from "./neon-model-capabilities.js";
2
+ import { NeonAnthropicLanguageModel } from "./neon-anthropic-language-model.js";
3
3
  import { NeonChatLanguageModel } from "./neon-chat-language-model.js";
4
4
  import { NeonResponsesLanguageModel } from "./neon-responses-language-model.js";
5
+ import { wrapFetchWithGatewayErrorNormalization } from "./neon-gateway-error.js";
5
6
  import { wrapFetchWithHarmonyNormalization } from "./neon-harmony-normalize.js";
6
7
  import { neonOpenAITools } from "./neon-openai-tools.js";
7
8
  import { VERSION } from "./version.js";
@@ -61,21 +62,21 @@ function createNeon(options = {}) {
61
62
  provider: "neon.anthropic",
62
63
  baseURL: `${getHost()}/anthropic/v1`,
63
64
  headers: () => getHeaders({ "anthropic-version": "2023-06-01" }),
64
- fetch: options.fetch,
65
+ fetch: wrapFetchWithGatewayErrorNormalization(options.fetch, "anthropic"),
65
66
  generateId
66
67
  });
67
68
  const createOpenAIModel = (modelId) => new NeonResponsesLanguageModel(modelId, {
68
69
  provider: "neon.openai.responses",
69
70
  url: ({ path }) => `${getHost()}/openai/v1${path}`,
70
71
  headers: getHeaders,
71
- fetch: options.fetch,
72
+ fetch: wrapFetchWithGatewayErrorNormalization(options.fetch, "openai"),
72
73
  fileIdPrefixes: ["file-"]
73
74
  });
74
75
  const createChatModel = (modelId) => new NeonChatLanguageModel(modelId, {
75
76
  provider: "neon.chat",
76
77
  url: ({ path }) => `${getHost()}/v1${path}`,
77
78
  headers: getHeaders,
78
- fetch: wrapFetchWithHarmonyNormalization(options.fetch),
79
+ fetch: wrapFetchWithHarmonyNormalization(wrapFetchWithGatewayErrorNormalization(options.fetch, "openai")),
79
80
  errorStructure: neonErrorStructure,
80
81
  transformRequestBody: transformNeonRequestBody,
81
82
  supportsStructuredOutputs: true