@neondatabase/ai-sdk-provider 0.7.1 → 0.7.3

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.
@@ -1,6 +1,5 @@
1
- # Apache License 2.0
2
1
 
3
- Apache License
2
+ Apache License
4
3
  Version 2.0, January 2004
5
4
  http://www.apache.org/licenses/
6
5
 
@@ -175,4 +174,29 @@
175
174
  incurred by, or claims asserted against, such Contributor by reason
176
175
  of your accepting any such warranty or additional liability.
177
176
 
178
- END OF TERMS AND CONDITIONS
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # @neon/ai-sdk-provider
2
2
 
3
- Community [Vercel AI SDK](https://ai-sdk.dev) provider for the [Neon](https://neon.com) AI Gateway. Requires **AI SDK v6** (`ai@^6`).
3
+ Community [Vercel AI SDK](https://ai-sdk.dev) provider for the [Neon](https://neon.com) AI Gateway. Supports **AI SDK v6 and v7** (`ai@^6` or `ai@^7`).
4
4
 
5
- The Neon AI Gateway is **branch-scoped**: each Neon project branch gets its own gateway host, and a platform token authorizes requests for that branch. This provider routes each model to the best gateway endpoint (Anthropic → native Messages, OpenAI → native Responses incl. **Codex**, everything else → unified OpenAI-compatible MLflow endpoint), so a single `neon('claude-...')` call reaches the whole catalog.
5
+ The Neon AI Gateway is **branch-scoped**: each Neon project branch gets its own gateway host, and a platform token authorizes requests for that branch. Use the same `neon(modelId)` API across the branch's model catalog; the provider selects Anthropic Messages, OpenAI Responses, or Chat Completions for each model.
6
6
 
7
- Model ids use the canonical Neon (unprefixed) form — `claude-sonnet-4-6`, `gpt-5`, `gemini-2-5-flash` — matching the [`neon` provider on models.dev](https://models.dev). The typed catalog mirrors that provider exactly (kept in sync by a scheduled drift check), plus a few extra gateway-served ids that models.dev doesn't list yet (e.g. Codex, Llama, Qwen). Any other id — including the legacy `databricks-` prefixed form (`databricks-claude-sonnet-4-6`) — is still accepted as a plain string, so existing code keeps working.
7
+ Use canonical model ids such as `gpt-5-mini`, `llama-4-maverick`, and `gemini-3-flash`, matching the [`neon` provider on models.dev](https://models.dev). The typed catalog includes the known model ids, and arbitrary strings are accepted so newly available models work before the types update.
8
8
 
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- npm install @neon/ai-sdk-provider ai@^6
12
+ npm install @neon/ai-sdk-provider ai
13
13
  ```
14
14
 
15
- > **Requirements:** Node.js >= 20.19.
15
+ > **Requirements:** Node.js >= 20.19 with AI SDK 6. AI SDK 7 itself requires Node.js >= 22.
16
16
 
17
17
  ## Configuration
18
18
 
@@ -31,7 +31,7 @@ import { generateText } from "ai";
31
31
 
32
32
  // Reads NEON_AI_GATEWAY_BASE_URL + NEON_AI_GATEWAY_TOKEN from the environment.
33
33
  const { text } = await generateText({
34
- model: neon("claude-haiku-4-5"), // or 'gpt-5-3-codex', etc.
34
+ model: neon("gpt-5-mini"), // or "llama-4-maverick", "gemini-3-flash", …
35
35
  prompt: "Summarize Postgres for me.",
36
36
  });
37
37
  ```
@@ -51,17 +51,19 @@ const neon = createNeon({
51
51
 
52
52
  | Model family | Endpoint | Why |
53
53
  | --- | --- | --- |
54
- | Anthropic (`claude-*`) | native Messages API | streaming structured output + native reasoning |
55
- | OpenAI (`gpt-*`, `*-codex`) | native Responses API | Codex (native-only), native reasoning, image-gen tool |
56
- | Everything else (Gemini, Llama, Qwen, gpt-oss, ...) | unified MLflow endpoint | broad coverage; Gemini is here because its native endpoint does not support streaming |
54
+ | Anthropic (`claude-*`) | Messages API | Anthropic tools, structured output, and reasoning |
55
+ | OpenAI (`gpt-*`, `*-codex`) | Responses API | Codex, reasoning, and built-in tools such as image generation |
56
+ | Everything else (Gemini, Llama, Qwen, gpt-oss, ...) | Chat Completions API | one streaming interface across the remaining model families |
57
57
 
58
- Routing matches on the model id, so both the canonical (`gpt-5`) and the legacy `databricks-`-prefixed (`databricks-gpt-5`) forms route identically.
58
+ Routing matches on the model id.
59
59
 
60
60
  ## Capabilities
61
61
 
62
- Verified across Anthropic, OpenAI (incl. Codex), Google, and Meta models: `generateText` / `streamText` (text, system prompts, multi-turn), tool calling (single and multi-step, generate and stream), `generateObject` / `streamObject`, and image (vision) input.
62
+ `generateText` and `streamText` work with any model available to your branch. `generateObject`, `streamObject`, and single- or multi-step tool calls work with OpenAI (including Codex), Meta, and Alibaba models. Gemini currently supports `generateText` and `streamText`; structured output and multi-step tools are not supported. Vision input works on models that accept images.
63
63
 
64
- For MLflow-routed models, the provider detects the model family and drops parameters a backend rejects (e.g. penalties/`seed` for Llama, `reasoningEffort` for Gemini) with an AI SDK warning (`result.warnings`) instead of failing the request.
64
+ Claude models use the Messages API when `claude-*` ids are available. For models using Chat Completions, the provider removes unsupported options such as penalties or `seed` for Llama and `reasoningEffort` for Gemini, then reports them in `result.warnings` instead of failing the request. Unsupported Responses API storage options throw before a request is sent — see [Errors](#errors).
65
+
66
+ Which ids your branch serves is account-specific during the beta; `GET $NEON_AI_GATEWAY_BASE_URL/v1/models` is the authoritative list.
65
67
 
66
68
  ## Image generation
67
69
 
@@ -85,11 +87,90 @@ for await (const part of result.fullStream) {
85
87
  }
86
88
  ```
87
89
 
90
+ ### Edit a generated image
91
+
92
+ The gateway does not support stored Responses API items (`store: true`, `previousResponseId`, or `conversation`), which the Responses API uses to reuse tool results across turns. For image edits, pass the returned bytes back as image input; otherwise the model does not receive the original image and generates a new one.
93
+
94
+ ```ts
95
+ const first = await generateText({
96
+ model: neon("gpt-5-mini"),
97
+ prompt: "Generate an image of a plain red square.",
98
+ tools: { image_generation: neon.tools.imageGeneration({ outputFormat: "jpeg" }) },
99
+ });
100
+
101
+ const generated = first.steps
102
+ .flatMap((step) => step.toolResults)
103
+ .find((result) => result.toolName === "image_generation");
104
+ const output = generated?.output;
105
+ if (
106
+ !output ||
107
+ typeof output !== "object" ||
108
+ !("result" in output) ||
109
+ typeof output.result !== "string"
110
+ ) {
111
+ throw new Error("Image generation did not return image bytes.");
112
+ }
113
+
114
+ // Pass it back as image content, not as conversation history.
115
+ await generateText({
116
+ model: neon("gpt-5-mini"),
117
+ tools: { image_generation: neon.tools.imageGeneration({ outputFormat: "jpeg" }) },
118
+ messages: [
119
+ {
120
+ role: "user",
121
+ content: [
122
+ { type: "image", image: output.result, mediaType: "image/jpeg" },
123
+ { type: "text", text: "Make that same square blue instead." },
124
+ ],
125
+ },
126
+ ],
127
+ });
128
+ ```
129
+
130
+ The same stateless limitation applies to other built-in Responses tools such as web search and code interpreter.
131
+
132
+ ## Errors
133
+
134
+ A failed call rejects with the AI SDK's `APICallError`, and `error.message` carries the gateway's own reason rather than the bare HTTP status line:
135
+
136
+ ```ts
137
+ import { APICallError, generateText } from "ai";
138
+
139
+ try {
140
+ await generateText({ model: neon("gpt-5-mini"), maxOutputTokens: 1, prompt });
141
+ } catch (error) {
142
+ if (APICallError.isInstance(error)) {
143
+ error.message; // "Invalid 'max_output_tokens': integer below minimum
144
+ // value. Expected a value >= 16, but got 1 instead."
145
+ error.responseBody; // the gateway's original body, including its error_code
146
+ }
147
+ }
148
+ ```
149
+
150
+ The provider normalizes JSON error responses so the gateway's reason lands on `error.message`. Errors delivered inside an open stream and non-JSON responses keep the HTTP status line; inspect `error.responseBody` for their payload.
151
+
152
+ Requests that cannot work are refused before they leave, with `UnsupportedFunctionalityError`:
153
+
154
+ | `providerOptions.openai` | Why |
155
+ | --- | --- |
156
+ | `store: true` or `store: null` | The Responses route is stateless and never persists items. `null` is not "omitted" — the AI SDK reads it as `true`. |
157
+ | `previousResponseId`, `conversation` | Nothing is stored for them to refer to. Send the full message history instead. |
158
+
159
+ Provider options are namespaced per route, which decides where the SDK reads them from:
160
+
161
+ | Route | Models | Namespace |
162
+ | --- | --- | --- |
163
+ | Responses | `gpt-*` (incl. Codex) | `openai` |
164
+ | Chat Completions | Gemini, Llama, Qwen, gpt-oss, … | `neon` |
165
+ | Anthropic Messages | `claude-*` | `anthropic` |
166
+
167
+ So `store` is only refused on the Responses route; the same option is ignored elsewhere.
168
+
88
169
  ## Limitations
89
170
 
90
- - `generateImage()` and embeddings (`embed` / `embedMany`) are not offered by the gateway and throw `NoSuchModelError`.
91
- - `gpt-oss-*` models return a non-standard ("harmony") response shape on the unified endpoint (`message.content` as an array of reasoning/text parts instead of a string). The provider normalizes this to the OpenAI Chat Completions contract (string `content` + `reasoning_content`) so `generateText`/`streamText` work and reasoning is surfaced. See neondatabase/neon-pkgs#308.
92
- - OpenAI Responses multi-turn tool flows (`generateText` + `stepCountIs`) can return 502 from the gateway; tool calling is covered on Anthropic/Google/Meta in e2e.
171
+ - The gateway does not offer image or embedding model endpoints, so `generateImage()`, `embed()`, and `embedMany()` throw `NoSuchModelError`. Image generation is available through the Responses API's built-in `image_generation` tool with `neon.tools.imageGeneration()`.
172
+ - Results from provider-executed tools (`neon.tools.imageGeneration`, and the other Responses built-ins) are not replayed to the gateway on a later step — see [Edit a generated image](#edit-a-generated-image).
173
+ - The Responses route is stateless, so the provider sends `store: false` and refuses `store: true`, `store: null`, `previousResponseId`, or `conversation` — see [Errors](#errors).
93
174
 
94
175
  ## End-to-end tests
95
176
 
@@ -100,4 +181,4 @@ cp .env.example .env # fill NEON_AI_GATEWAY_BASE_URL + NEON_AI_GATEWAY_TOKEN f
100
181
  pnpm test:e2e
101
182
  ```
102
183
 
103
- The matrix covers one models.dev `neon` model per family (Anthropic, OpenAI, Codex, Gemini, Meta) across `generateText`, `streamText`, `generateObject`, tool calling, and `neon.tools.imageGeneration`. Skipped when gateway env vars are absent.
184
+ The matrix covers one models.dev `neon` model per family (Anthropic, OpenAI, Codex, Gemini, Meta, Alibaba) across `generateText`, `streamText`, `generateObject`, tool calling, and `neon.tools.imageGeneration`. `generateObject` and tool calling run on the subset of families where they are verified (see [Capabilities](#capabilities)); a family whose representative id the branch does not serve is skipped rather than failed. It also fetches the live `/v1/models` catalog and calls every currently enabled model with both AI SDK 6 and AI SDK 7. Tests are skipped when gateway env vars are absent.
@@ -1 +1 @@
1
- {"version":3,"file":"neon-anthropic-language-model.d.ts","names":[],"sources":["../../src/lib/neon-anthropic-language-model.ts"],"mappings":";;;;;;;AAkBA;;;;;;;;AACS,cADI,0BAAA,SACJ,8BAAA,YACG,eADH,CAAA;UACG,iBAAA;EAAe,UAAA,CAAA,OAAA,EAoBhB,0BApBgB,CAAA,EAqBvB,OArBuB,CAqBf,6BArBe,CAAA;oBA0BhB,6BACP,QAAQ"}
1
+ {"version":3,"file":"neon-anthropic-language-model.d.ts","names":[],"sources":["../../src/lib/neon-anthropic-language-model.ts"],"mappings":";;;;;;;AAkBA;AACC;AAqBU;AACC;AAAR;AAKO;AACC;AAAR;AA5BK,cADI,0BAAA,SACJ,8BAAA,YACG,eADH,CAAA;EACG,QAAA,iBAAA;EAAe,UAAA,CAAA,OAAA,EAoBhB,0BApBgB,CAAA,EAqBvB,OArBuB,CAqBf,6BArBe,CAAA;oBA0BhB,6BACP,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"neon-capabilities.d.ts","names":[],"sources":["../../src/lib/neon-capabilities.ts"],"mappings":";;;;;;AAaA;;;AAGc,iBAHE,qBAAA,CAGF,OAAA,EAAA,MAAA,EAAA,OAAA,EADJ,0BACI,CAAA,EAAA;SAAsC,EAAtC,0BAAsC;EAAe,QAAA,EAAf,eAAe,EAAA;AA4FnE,CAAA;;;;AAAiE,iBAAjD,wBAAA,CAAiD,KAAA,EAAjB,eAAiB,EAAA,CAAA,EAAA,eAAA,CAAA,yBAAA,EAAA,yBAAA,CAAA"}
1
+ {"version":3,"file":"neon-capabilities.d.ts","names":[],"sources":["../../src/lib/neon-capabilities.ts"],"mappings":";;;;;;AAaA;AAAqC;AAE3B;AACI,iBAHE,qBAAA,CAGF,OAAA,EAAA,MAAA,EAAA,OAAA,EADJ,0BACI,CAAA,EAAA;EAAsC,OAAA,EAAtC,0BAAsC;EAAe,QAAA,EAAf,eAAe,EAAA;AA4FnE,CAAA;AAAwC;AAAQ;AAAiB;AAAA,iBAAjD,wBAAA,CAAiD,KAAA,EAAjB,eAAiB,EAAA,CAAA,EAAA,eAAA,CAAA,yBAAA,EAAA,yBAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"neon-chat-language-model.d.ts","names":[],"sources":["../../src/lib/neon-chat-language-model.ts"],"mappings":";;;;;;;AAqBA;;;;;;;AAmBI,cAnBS,qBAAA,SACJ,iCAAA,YACG,eAiBR,CAAA;YAlBK,CAAA,OAAA,EAIE,0BAJF,CAAA,EAKL,OALK,CAKG,6BALH,CAAA;UACG,CAAA,OAAA,EAgBD,0BAhBC,CAAA,EAiBR,OAjBQ,CAiBA,2BAjBA,CAAA;AAAe"}
1
+ {"version":3,"file":"neon-chat-language-model.d.ts","names":[],"sources":["../../src/lib/neon-chat-language-model.ts"],"mappings":";;;;;;;AAqBA;AACC;AAIU;AACC;AAAR;AAYO;AACC;AAAR,cAnBS,qBAAA,SACJ,iCAAA,YACG,eAiBR,CAAA;EAlBK,UAAA,CAAA,OAAA,EAIE,0BAJF,CAAA,EAKL,OALK,CAKG,6BALH,CAAA;EACG,QAAA,CAAA,OAAA,EAgBD,0BAhBC,CAAA,EAiBR,OAjBQ,CAiBA,2BAjBA,CAAA;AAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"neon-chat-options.d.ts","names":[],"sources":["../../src/lib/neon-chat-options.ts"],"mappings":";;AAiBa,cAAA,mBA8BH,EAAA,SAAA,CAAA,iBAAA,EAAA,iBAAA,EAAA,iBAAA,EAAA,iBAAA,EAAA,mBAAA,EAAA,mBAAA,EAAA,iBAAA,EAAA,kBAAA,EAAA,OAAA,EAAA,YAAA,EAAA,YAAA,EAAA,SAAA,EAAA,SAAA,EAAA,SAAA,EAAA,cAAA,EAAA,cAAA,EAAA,SAAA,EAAA,cAAA,EAAA,aAAA,EAAA,cAAA,EAAA,gBAAA,EAAA,gBAAA,EAAA,uBAAA,EAAA,gBAAA,EAAA,kBAAA,CAAA;AAQV;AAoBA;;;;AAE+B;AAOnB,cA7BC,oBA6BiB,EAAA,SAAgB,CAAA,iBAAA,EAAA,eAAA,EAAA,eAAA,EAAA,aAAA,EAAA,kBAAA,EAAA,aAAA,EAAA,kBAAA,EAAA,6BAAA,EAAA,4BAAA,EAAA,6BAAA,EAAA,kBAAA,CAAA;;KATlC,gBAAA,WACD,uCACA;;;;;;KAOC,eAAA,GAAkB"}
1
+ {"version":3,"file":"neon-chat-options.d.ts","names":[],"sources":["../../src/lib/neon-chat-options.ts"],"mappings":";;AAiBa,cAAA,mBA8BH,EAAA,SAAA,CAAA,iBAAA,EAAA,iBAAA,EAAA,iBAAA,EAAA,iBAAA,EAAA,mBAAA,EAAA,mBAAA,EAAA,iBAAA,EAAA,kBAAA,EAAA,OAAA,EAAA,YAAA,EAAA,YAAA,EAAA,SAAA,EAAA,SAAA,EAAA,SAAA,EAAA,cAAA,EAAA,cAAA,EAAA,SAAA,EAAA,cAAA,EAAA,aAAA,EAAA,cAAA,EAAA,gBAAA,EAAA,gBAAA,EAAA,uBAAA,EAAA,gBAAA,EAAA,kBAAA,CAAA;AAQV;AAoBA;AAA4B;AACjB;AACA;AAAoB;AAOnB,cA7BC,oBA6BiB,EAAA,SAAgB,CAAA,iBAAA,EAAA,eAAA,EAAA,eAAA,EAAA,aAAA,EAAA,kBAAA,EAAA,aAAA,EAAA,kBAAA,EAAA,6BAAA,EAAA,4BAAA,EAAA,6BAAA,EAAA,kBAAA,CAAA;;KATlC,gBAAA,WACD,uCACA;;;;;;KAOC,eAAA,GAAkB"}
@@ -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"}
@@ -1 +1 @@
1
- {"version":3,"file":"neon-harmony-normalize.d.ts","names":[],"sources":["../../src/lib/neon-harmony-normalize.ts"],"mappings":";;;UAwCU,gBAAA;;EAAA,SAAA,EAAA,MAAA;AAAgB;AAgB1B;AAkDA,UA5DU,eAAA,CA4DM;EAkCA,IAAA,EAAA,OAAA;EAuGA,OAAA,EAAA,OAAA;;;;AAEA;;;iBA7LA,qBAAA,oBAEb;;;;;iBAgDa,uBAAA,iBAAwC;;;;;iBAkCxC,kBAAA,iBAAmC;;;;;;;;;;iBAuGnC,iCAAA,aACH,gBACV"}
1
+ {"version":3,"file":"neon-harmony-normalize.d.ts","names":[],"sources":["../../src/lib/neon-harmony-normalize.ts"],"mappings":";;;UAwCU,gBAAA;;EAAA,SAAA,EAAA,MAAA;AAAgB;AAgB1B;AAkDA,UA5DU,eAAA,CA4DM;EAkCA,IAAA,EAAA,OAAA;EAuGA,OAAA,EAAA,OAAA;AAAiC;AACpC;AACV;AAAa;;;iBA7LA,qBAAA,oBAEb;;;;;iBAgDa,uBAAA,iBAAwC;;;;;iBAkCxC,kBAAA,iBAAmC;;;;;;;;;;iBAuGnC,iCAAA,aACH,gBACV"}
@@ -1 +1 @@
1
- {"version":3,"file":"neon-harmony-normalize.js","names":[],"sources":["../../src/lib/neon-harmony-normalize.ts"],"sourcesContent":["import type { FetchFunction } from \"@ai-sdk/provider-utils\";\n\n// TODO(#308): Temporary workaround. Remove this whole module (and its wiring in\n// provider.ts) once the Neon AI Gateway returns Chat Completions-compliant\n// gpt-oss responses (string `content` + `reasoning_content`). The wrapper is a\n// transparent pass-through for any already-compliant response, so it is safe to\n// leave in place until the gateway fix ships — but it should be deleted then.\n\n/**\n * Normalize the Neon AI Gateway's non-OpenAI-compliant `gpt-oss` (\"harmony\")\n * response shape into the OpenAI Chat Completions contract before it reaches\n * the AI SDK's schema validation.\n *\n * For `gpt-oss-*` the gateway returns `message.content` (and streaming\n * `delta.content`) as an array of Responses-style parts:\n *\n * \"content\": [\n * { \"type\": \"reasoning\", \"summary\": [{ \"type\": \"summary_text\", \"text\": \"…\" }] },\n * { \"type\": \"text\", \"text\": \"…\" }\n * ]\n *\n * The Chat Completions spec requires `content` to be a string, so any strict\n * OpenAI-compatible client (incl. the Vercel AI SDK) rejects it. We flatten the\n * `text` parts into the string `content` and hoist reasoning into\n * `reasoning_content` (which `@ai-sdk/openai-compatible` already surfaces as an\n * AI SDK reasoning part).\n *\n * The transform only rewrites when `content` is an array, and the fetch wrapper\n * returns the response untouched when nothing was rewritten. So it is a true\n * no-op / pass-through for every spec-compliant model (Llama, Gemini, Qwen, …)\n * and self-retires the moment the gateway starts returning a compliant shape —\n * it will not double-transform or otherwise interfere with the downstream fix.\n *\n * See neondatabase/neon-pkgs#308.\n */\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\ninterface ExtractedHarmony {\n\ttext: string;\n\treasoning: string;\n}\n\n/** The result of a normalization pass, flagging whether anything was rewritten. */\ninterface NormalizeResult {\n\tbody: unknown;\n\tchanged: boolean;\n}\n\n/**\n * Pull the text and reasoning out of a harmony `content` array. Returns `null`\n * when `content` is not an array (i.e. an already-compliant string response),\n * signalling that no rewrite is needed.\n */\nexport function extractHarmonyContent(\n\tcontent: unknown,\n): ExtractedHarmony | null {\n\tif (!Array.isArray(content)) {\n\t\treturn null;\n\t}\n\n\tconst texts: string[] = [];\n\tconst reasonings: string[] = [];\n\n\tfor (const part of content) {\n\t\tif (!isRecord(part)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (part.type === \"text\") {\n\t\t\tif (typeof part.text === \"string\") {\n\t\t\t\ttexts.push(part.text);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (part.type === \"reasoning\") {\n\t\t\t// Reasoning items can carry the raw CoT in `text`, a `summary[]` of\n\t\t\t// `summary_text` parts, and/or a `content[]` of `reasoning_text` parts.\n\t\t\tif (typeof part.text === \"string\") {\n\t\t\t\treasonings.push(part.text);\n\t\t\t}\n\t\t\tif (Array.isArray(part.summary)) {\n\t\t\t\tfor (const entry of part.summary) {\n\t\t\t\t\tif (isRecord(entry) && typeof entry.text === \"string\") {\n\t\t\t\t\t\treasonings.push(entry.text);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (Array.isArray(part.content)) {\n\t\t\t\tfor (const entry of part.content) {\n\t\t\t\t\tif (isRecord(entry) && typeof entry.text === \"string\") {\n\t\t\t\t\t\treasonings.push(entry.text);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { text: texts.join(\"\"), reasoning: reasonings.join(\"\\n\") };\n}\n\n/**\n * Rewrite a non-streaming `chat.completion` body in place. `changed` is false\n * when the body was already compliant (no harmony array found).\n */\nexport function normalizeCompletionBody(body: unknown): NormalizeResult {\n\tif (!isRecord(body) || !Array.isArray(body.choices)) {\n\t\treturn { body, changed: false };\n\t}\n\tlet changed = false;\n\tfor (const choice of body.choices) {\n\t\tif (!isRecord(choice)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst message = choice.message;\n\t\tif (!isRecord(message)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst extracted = extractHarmonyContent(message.content);\n\t\tif (extracted === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tmessage.content = extracted.text;\n\t\tchanged = true;\n\t\tif (\n\t\t\textracted.reasoning.length > 0 &&\n\t\t\tmessage.reasoning_content == null &&\n\t\t\tmessage.reasoning == null\n\t\t) {\n\t\t\tmessage.reasoning_content = extracted.reasoning;\n\t\t}\n\t}\n\treturn { body, changed };\n}\n\n/**\n * Rewrite a streaming `chat.completion.chunk` body in place. `changed` is false\n * when the chunk was already compliant (no harmony array found).\n */\nexport function normalizeChunkBody(body: unknown): NormalizeResult {\n\tif (!isRecord(body) || !Array.isArray(body.choices)) {\n\t\treturn { body, changed: false };\n\t}\n\tlet changed = false;\n\tfor (const choice of body.choices) {\n\t\tif (!isRecord(choice)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst delta = choice.delta;\n\t\tif (!isRecord(delta)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst extracted = extractHarmonyContent(delta.content);\n\t\tif (extracted === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tchanged = true;\n\t\t// Drop empty text so reasoning-only chunks don't emit spurious text deltas.\n\t\tif (extracted.text.length > 0) {\n\t\t\tdelta.content = extracted.text;\n\t\t} else {\n\t\t\tdelta.content = undefined;\n\t\t}\n\t\tif (\n\t\t\textracted.reasoning.length > 0 &&\n\t\t\tdelta.reasoning_content == null &&\n\t\t\tdelta.reasoning == null\n\t\t) {\n\t\t\tdelta.reasoning_content = extracted.reasoning;\n\t\t}\n\t}\n\treturn { body, changed };\n}\n\n/**\n * Rewrite each `data:` frame of an SSE stream. Compliant frames pass through\n * verbatim; only harmony frames are re-serialized.\n */\nfunction normalizeEventStream(\n\tbody: ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n\tconst decoder = new TextDecoder();\n\tconst encoder = new TextEncoder();\n\tlet buffer = \"\";\n\n\tconst rewriteLine = (line: string): string => {\n\t\tif (!line.startsWith(\"data:\")) {\n\t\t\treturn line;\n\t\t}\n\t\tconst payload = line.slice(\"data:\".length).trim();\n\t\tif (payload === \"\" || payload === \"[DONE]\") {\n\t\t\treturn line;\n\t\t}\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(payload);\n\t\t} catch {\n\t\t\treturn line;\n\t\t}\n\t\tconst { body: normalized, changed } = normalizeChunkBody(parsed);\n\t\t// Pass compliant frames through unchanged so we don't reshape a fixed gateway.\n\t\treturn changed ? `data: ${JSON.stringify(normalized)}` : line;\n\t};\n\n\treturn body.pipeThrough(\n\t\tnew TransformStream<Uint8Array, Uint8Array>({\n\t\t\ttransform(chunk, controller) {\n\t\t\t\tbuffer += decoder.decode(chunk, { stream: true });\n\t\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\t\tbuffer = lines.pop() ?? \"\";\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\tcontroller.enqueue(\n\t\t\t\t\t\tencoder.encode(`${rewriteLine(line)}\\n`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t},\n\t\t\tflush(controller) {\n\t\t\t\tif (buffer.length > 0) {\n\t\t\t\t\tcontroller.enqueue(encoder.encode(rewriteLine(buffer)));\n\t\t\t\t}\n\t\t\t},\n\t\t}),\n\t);\n}\n\nfunction headersWithout(source: Headers, ...names: string[]): Headers {\n\tconst copy = new Headers(source);\n\tfor (const name of names) {\n\t\tcopy.delete(name);\n\t}\n\treturn copy;\n}\n\n/**\n * Wrap a fetch implementation so gpt-oss harmony responses are normalized to\n * the OpenAI Chat Completions shape before the caller parses them. Composes\n * with a user-supplied `fetch` (falls back to the global `fetch`).\n *\n * When the response is already compliant, the original response is returned\n * untouched — a transparent pass-through that will not interfere once the\n * gateway is fixed.\n */\nexport function wrapFetchWithHarmonyNormalization(\n\tbaseFetch?: FetchFunction,\n): FetchFunction {\n\tconst doFetch: FetchFunction =\n\t\tbaseFetch ?? ((input, init) => globalThis.fetch(input, init));\n\n\treturn async (input, init) => {\n\t\tconst response = await doFetch(input, init);\n\t\tif (!response.ok || response.body === null) {\n\t\t\treturn response;\n\t\t}\n\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\n\t\tif (contentType.includes(\"text/event-stream\")) {\n\t\t\t// Streaming is inspected frame-by-frame; compliant frames pass through\n\t\t\t// verbatim (see normalizeEventStream).\n\t\t\treturn new Response(normalizeEventStream(response.body), {\n\t\t\t\tstatus: response.status,\n\t\t\t\tstatusText: response.statusText,\n\t\t\t\t// content-length/encoding no longer describe the (possibly) rewritten body.\n\t\t\t\theaders: headersWithout(\n\t\t\t\t\tresponse.headers,\n\t\t\t\t\t\"content-length\",\n\t\t\t\t\t\"content-encoding\",\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\n\t\tif (contentType.includes(\"application/json\")) {\n\t\t\t// Inspect a clone so the original body stays intact for the pass-through case.\n\t\t\tlet text: string;\n\t\t\ttry {\n\t\t\t\ttext = await response.clone().text();\n\t\t\t} catch {\n\t\t\t\treturn response;\n\t\t\t}\n\t\t\tlet parsed: unknown;\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(text);\n\t\t\t} catch {\n\t\t\t\treturn response;\n\t\t\t}\n\t\t\tconst { body: normalized, changed } =\n\t\t\t\tnormalizeCompletionBody(parsed);\n\t\t\tif (!changed) {\n\t\t\t\t// Already compliant: return the original response untouched.\n\t\t\t\treturn response;\n\t\t\t}\n\t\t\treturn new Response(JSON.stringify(normalized), {\n\t\t\t\tstatus: response.status,\n\t\t\t\tstatusText: response.statusText,\n\t\t\t\theaders: headersWithout(\n\t\t\t\t\tresponse.headers,\n\t\t\t\t\t\"content-length\",\n\t\t\t\t\t\"content-encoding\",\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\n\t\treturn response;\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"neon-harmony-normalize.js","names":[],"sources":["../../src/lib/neon-harmony-normalize.ts"],"sourcesContent":["import type { FetchFunction } from \"@ai-sdk/provider-utils\";\n\n// TODO(#308): Temporary workaround. Remove this whole module (and its wiring in\n// provider.ts) once the Neon AI Gateway returns Chat Completions-compliant\n// gpt-oss responses (string `content` + `reasoning_content`). The wrapper is a\n// transparent pass-through for any already-compliant response, so it is safe to\n// leave in place until the gateway fix ships — but it should be deleted then.\n\n/**\n * Normalize the Neon AI Gateway's non-OpenAI-compliant `gpt-oss` (\"harmony\")\n * response shape into the OpenAI Chat Completions contract before it reaches\n * the AI SDK's schema validation.\n *\n * For `gpt-oss-*` the gateway returns `message.content` (and streaming\n * `delta.content`) as an array of Responses-style parts:\n *\n * \"content\": [\n * { \"type\": \"reasoning\", \"summary\": [{ \"type\": \"summary_text\", \"text\": \"…\" }] },\n * { \"type\": \"text\", \"text\": \"…\" }\n * ]\n *\n * The Chat Completions spec requires `content` to be a string, so any strict\n * OpenAI-compatible client (incl. the Vercel AI SDK) rejects it. We flatten the\n * `text` parts into the string `content` and hoist reasoning into\n * `reasoning_content` (which `@ai-sdk/openai-compatible` already surfaces as an\n * AI SDK reasoning part).\n *\n * The transform only rewrites when `content` is an array, and the fetch wrapper\n * returns the response untouched when nothing was rewritten. So it is a true\n * no-op / pass-through for every spec-compliant model (Llama, Gemini, Qwen, …)\n * and self-retires the moment the gateway starts returning a compliant shape —\n * it will not double-transform or otherwise interfere with the downstream fix.\n *\n * See neondatabase/neon-pkgs#308.\n */\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\ninterface ExtractedHarmony {\n\ttext: string;\n\treasoning: string;\n}\n\n/** The result of a normalization pass, flagging whether anything was rewritten. */\ninterface NormalizeResult {\n\tbody: unknown;\n\tchanged: boolean;\n}\n\n/**\n * Pull the text and reasoning out of a harmony `content` array. Returns `null`\n * when `content` is not an array (i.e. an already-compliant string response),\n * signalling that no rewrite is needed.\n */\nexport function extractHarmonyContent(\n\tcontent: unknown,\n): ExtractedHarmony | null {\n\tif (!Array.isArray(content)) {\n\t\treturn null;\n\t}\n\n\tconst texts: string[] = [];\n\tconst reasonings: string[] = [];\n\n\tfor (const part of content) {\n\t\tif (!isRecord(part)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (part.type === \"text\") {\n\t\t\tif (typeof part.text === \"string\") {\n\t\t\t\ttexts.push(part.text);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (part.type === \"reasoning\") {\n\t\t\t// Reasoning items can carry the raw CoT in `text`, a `summary[]` of\n\t\t\t// `summary_text` parts, and/or a `content[]` of `reasoning_text` parts.\n\t\t\tif (typeof part.text === \"string\") {\n\t\t\t\treasonings.push(part.text);\n\t\t\t}\n\t\t\tif (Array.isArray(part.summary)) {\n\t\t\t\tfor (const entry of part.summary) {\n\t\t\t\t\tif (isRecord(entry) && typeof entry.text === \"string\") {\n\t\t\t\t\t\treasonings.push(entry.text);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (Array.isArray(part.content)) {\n\t\t\t\tfor (const entry of part.content) {\n\t\t\t\t\tif (isRecord(entry) && typeof entry.text === \"string\") {\n\t\t\t\t\t\treasonings.push(entry.text);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { text: texts.join(\"\"), reasoning: reasonings.join(\"\\n\") };\n}\n\n/**\n * Rewrite a non-streaming `chat.completion` body in place. `changed` is false\n * when the body was already compliant (no harmony array found).\n */\nexport function normalizeCompletionBody(body: unknown): NormalizeResult {\n\tif (!isRecord(body) || !Array.isArray(body.choices)) {\n\t\treturn { body, changed: false };\n\t}\n\tlet changed = false;\n\tfor (const choice of body.choices) {\n\t\tif (!isRecord(choice)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst message = choice.message;\n\t\tif (!isRecord(message)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst extracted = extractHarmonyContent(message.content);\n\t\tif (extracted === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tmessage.content = extracted.text;\n\t\tchanged = true;\n\t\tif (\n\t\t\textracted.reasoning.length > 0 &&\n\t\t\tmessage.reasoning_content == null &&\n\t\t\tmessage.reasoning == null\n\t\t) {\n\t\t\tmessage.reasoning_content = extracted.reasoning;\n\t\t}\n\t}\n\treturn { body, changed };\n}\n\n/**\n * Rewrite a streaming `chat.completion.chunk` body in place. `changed` is false\n * when the chunk was already compliant (no harmony array found).\n */\nexport function normalizeChunkBody(body: unknown): NormalizeResult {\n\tif (!isRecord(body) || !Array.isArray(body.choices)) {\n\t\treturn { body, changed: false };\n\t}\n\tlet changed = false;\n\tfor (const choice of body.choices) {\n\t\tif (!isRecord(choice)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst delta = choice.delta;\n\t\tif (!isRecord(delta)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst extracted = extractHarmonyContent(delta.content);\n\t\tif (extracted === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tchanged = true;\n\t\t// Drop empty text so reasoning-only chunks don't emit spurious text deltas.\n\t\tif (extracted.text.length > 0) {\n\t\t\tdelta.content = extracted.text;\n\t\t} else {\n\t\t\tdelta.content = undefined;\n\t\t}\n\t\tif (\n\t\t\textracted.reasoning.length > 0 &&\n\t\t\tdelta.reasoning_content == null &&\n\t\t\tdelta.reasoning == null\n\t\t) {\n\t\t\tdelta.reasoning_content = extracted.reasoning;\n\t\t}\n\t}\n\treturn { body, changed };\n}\n\n/**\n * Rewrite each `data:` frame of an SSE stream. Compliant frames pass through\n * verbatim; only harmony frames are re-serialized.\n */\nfunction normalizeEventStream(\n\tbody: ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n\tconst decoder = new TextDecoder();\n\tconst encoder = new TextEncoder();\n\tlet buffer = \"\";\n\n\tconst rewriteLine = (line: string): string => {\n\t\tif (!line.startsWith(\"data:\")) {\n\t\t\treturn line;\n\t\t}\n\t\tconst payload = line.slice(\"data:\".length).trim();\n\t\tif (payload === \"\" || payload === \"[DONE]\") {\n\t\t\treturn line;\n\t\t}\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(payload);\n\t\t} catch {\n\t\t\treturn line;\n\t\t}\n\t\tconst { body: normalized, changed } = normalizeChunkBody(parsed);\n\t\t// Pass compliant frames through unchanged so we don't reshape a fixed gateway.\n\t\treturn changed ? `data: ${JSON.stringify(normalized)}` : line;\n\t};\n\n\treturn body.pipeThrough(\n\t\tnew TransformStream<Uint8Array, Uint8Array>({\n\t\t\ttransform(chunk, controller) {\n\t\t\t\tbuffer += decoder.decode(chunk, { stream: true });\n\t\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\t\tbuffer = lines.pop() ?? \"\";\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\tcontroller.enqueue(\n\t\t\t\t\t\tencoder.encode(`${rewriteLine(line)}\\n`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t},\n\t\t\tflush(controller) {\n\t\t\t\tif (buffer.length > 0) {\n\t\t\t\t\tcontroller.enqueue(encoder.encode(rewriteLine(buffer)));\n\t\t\t\t}\n\t\t\t},\n\t\t}),\n\t);\n}\n\nfunction headersWithout(source: Headers, ...names: string[]): Headers {\n\tconst copy = new Headers(source);\n\tfor (const name of names) {\n\t\tcopy.delete(name);\n\t}\n\treturn copy;\n}\n\n/**\n * Wrap a fetch implementation so gpt-oss harmony responses are normalized to\n * the OpenAI Chat Completions shape before the caller parses them. Composes\n * with a user-supplied `fetch` (falls back to the global `fetch`).\n *\n * When the response is already compliant, the original response is returned\n * untouched — a transparent pass-through that will not interfere once the\n * gateway is fixed.\n */\nexport function wrapFetchWithHarmonyNormalization(\n\tbaseFetch?: FetchFunction,\n): FetchFunction {\n\tconst doFetch: FetchFunction =\n\t\tbaseFetch ?? ((input, init) => globalThis.fetch(input, init));\n\n\treturn async (input, init) => {\n\t\tconst response = await doFetch(input, init);\n\t\tif (!response.ok || response.body === null) {\n\t\t\treturn response;\n\t\t}\n\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\n\t\tif (contentType.includes(\"text/event-stream\")) {\n\t\t\t// Streaming is inspected frame-by-frame; compliant frames pass through\n\t\t\t// verbatim (see normalizeEventStream).\n\t\t\treturn new Response(normalizeEventStream(response.body), {\n\t\t\t\tstatus: response.status,\n\t\t\t\tstatusText: response.statusText,\n\t\t\t\t// content-length/encoding no longer describe the (possibly) rewritten body.\n\t\t\t\theaders: headersWithout(\n\t\t\t\t\tresponse.headers,\n\t\t\t\t\t\"content-length\",\n\t\t\t\t\t\"content-encoding\",\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\n\t\tif (contentType.includes(\"application/json\")) {\n\t\t\t// Inspect a clone so the original body stays intact for the pass-through case.\n\t\t\tlet text: string;\n\t\t\ttry {\n\t\t\t\ttext = await response.clone().text();\n\t\t\t} catch {\n\t\t\t\treturn response;\n\t\t\t}\n\t\t\tlet parsed: unknown;\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(text);\n\t\t\t} catch {\n\t\t\t\treturn response;\n\t\t\t}\n\t\t\tconst { body: normalized, changed } =\n\t\t\t\tnormalizeCompletionBody(parsed);\n\t\t\tif (!changed) {\n\t\t\t\t// Already compliant: return the original response untouched.\n\t\t\t\treturn response;\n\t\t\t}\n\t\t\treturn new Response(JSON.stringify(normalized), {\n\t\t\t\tstatus: response.status,\n\t\t\t\tstatusText: response.statusText,\n\t\t\t\theaders: headersWithout(\n\t\t\t\t\tresponse.headers,\n\t\t\t\t\t\"content-length\",\n\t\t\t\t\t\"content-encoding\",\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\n\t\treturn response;\n\t};\n}\n"],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import { z } from "zod/v4";
2
+ import { openai } from "@ai-sdk/openai";
3
+
4
+ //#region src/lib/neon-openai-tools.d.ts
5
+ type NeonImageGenerationArgs = {
6
+ background?: "auto" | "opaque" | "transparent";
7
+ inputFidelity?: "low" | "high";
8
+ inputImageMask?: {
9
+ fileId?: string;
10
+ imageUrl?: string;
11
+ };
12
+ model?: string;
13
+ moderation?: "auto";
14
+ outputCompression?: number;
15
+ outputFormat?: "png" | "jpeg" | "webp";
16
+ partialImages?: number;
17
+ quality?: "auto" | "low" | "medium" | "high";
18
+ size?: "auto" | "1024x1024" | "1024x1536" | "1536x1024";
19
+ };
20
+ /**
21
+ * OpenAI image generation tool with a schema shape shared by AI SDK 6 and 7.
22
+ *
23
+ * AI SDK 6's official factory wraps schemas in its version-specific `Schema`
24
+ * type. AI SDK 7 can execute the resulting object, but rejects that type at
25
+ * compile time. Raw Zod 4 schemas implement the shared Standard Schema
26
+ * interface accepted by both SDK versions.
27
+ */
28
+ declare function imageGeneration(args?: NeonImageGenerationArgs): {
29
+ type: "provider";
30
+ id: "openai.image_generation";
31
+ args: {
32
+ background?: "auto" | "opaque" | "transparent";
33
+ inputFidelity?: "low" | "high";
34
+ inputImageMask?: {
35
+ fileId?: string;
36
+ imageUrl?: string;
37
+ };
38
+ model?: string;
39
+ moderation?: "auto";
40
+ outputCompression?: number;
41
+ outputFormat?: "png" | "jpeg" | "webp";
42
+ partialImages?: number;
43
+ quality?: "auto" | "low" | "medium" | "high";
44
+ size?: "auto" | "1024x1024" | "1024x1536" | "1536x1024";
45
+ };
46
+ inputSchema: z.ZodObject<{}, z.core.$strip>;
47
+ outputSchema: z.ZodObject<{
48
+ result: z.ZodString;
49
+ }, z.core.$strip>;
50
+ isProviderExecuted: true;
51
+ };
52
+ type NeonOpenAITools = Omit<typeof openai.tools, "imageGeneration"> & {
53
+ imageGeneration: typeof imageGeneration;
54
+ };
55
+ declare const neonOpenAITools: NeonOpenAITools;
56
+ //#endregion
57
+ export { NeonImageGenerationArgs, NeonOpenAITools, imageGeneration, neonOpenAITools };
58
+ //# sourceMappingURL=neon-openai-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neon-openai-tools.d.ts","names":[],"sources":["../../src/lib/neon-openai-tools.ts"],"mappings":";;;;KAGY,uBAAA;;EAAA,aAAA,CAAA,EAAA,KAAA,GAAA,MAAuB;EA2BnB,cAAA,CAAA,EAAA;IAAe,MAAA,CAAA,EAAA,MAAA;IAAO,QAAA,CAAA,EAAA,MAAA;;;;;;;EAe1B,OAAA,CAAA,EAAA,MAAA,GAAe,KAAA,GAAA,QAAA,GAAA,MAAA;EAAA,IAAA,CAAA,EAAA,MAAA,GAAA,WAAA,GAAA,WAAA,GAAA,WAAA;AACnB,CAAA;AADsB;AAIL;AAAe;AAGxC;;;;;iBAtBgB,eAAA,QAAsB;;;;;;;;;;;;;;;;;;;;;;;;KAe1B,eAAA,GAAkB,YACtB,MAAA,CAAe;0BAGE;;cAGZ,iBAAiB"}
@@ -0,0 +1,34 @@
1
+ import { z } from "zod/v4";
2
+ import { openai } from "@ai-sdk/openai";
3
+ //#region src/lib/neon-openai-tools.ts
4
+ const imageGenerationInputSchema = z.object({});
5
+ const imageGenerationOutputSchema = z.object({ result: z.string() });
6
+ /**
7
+ * OpenAI image generation tool with a schema shape shared by AI SDK 6 and 7.
8
+ *
9
+ * AI SDK 6's official factory wraps schemas in its version-specific `Schema`
10
+ * type. AI SDK 7 can execute the resulting object, but rejects that type at
11
+ * compile time. Raw Zod 4 schemas implement the shared Standard Schema
12
+ * interface accepted by both SDK versions.
13
+ */
14
+ function imageGeneration(args = {}) {
15
+ const type = "provider";
16
+ const id = "openai.image_generation";
17
+ const isProviderExecuted = true;
18
+ return {
19
+ type,
20
+ id,
21
+ args: { ...args },
22
+ inputSchema: imageGenerationInputSchema,
23
+ outputSchema: imageGenerationOutputSchema,
24
+ isProviderExecuted
25
+ };
26
+ }
27
+ const neonOpenAITools = {
28
+ ...openai.tools,
29
+ imageGeneration
30
+ };
31
+ //#endregion
32
+ export { imageGeneration, neonOpenAITools };
33
+
34
+ //# sourceMappingURL=neon-openai-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neon-openai-tools.js","names":["openaiProvider"],"sources":["../../src/lib/neon-openai-tools.ts"],"sourcesContent":["import { openai as openaiProvider } from \"@ai-sdk/openai\";\nimport { z } from \"zod/v4\";\n\nexport type NeonImageGenerationArgs = {\n\tbackground?: \"auto\" | \"opaque\" | \"transparent\";\n\tinputFidelity?: \"low\" | \"high\";\n\tinputImageMask?: {\n\t\tfileId?: string;\n\t\timageUrl?: string;\n\t};\n\tmodel?: string;\n\tmoderation?: \"auto\";\n\toutputCompression?: number;\n\toutputFormat?: \"png\" | \"jpeg\" | \"webp\";\n\tpartialImages?: number;\n\tquality?: \"auto\" | \"low\" | \"medium\" | \"high\";\n\tsize?: \"auto\" | \"1024x1024\" | \"1024x1536\" | \"1536x1024\";\n};\n\nconst imageGenerationInputSchema = z.object({});\nconst imageGenerationOutputSchema = z.object({ result: z.string() });\n\n/**\n * OpenAI image generation tool with a schema shape shared by AI SDK 6 and 7.\n *\n * AI SDK 6's official factory wraps schemas in its version-specific `Schema`\n * type. AI SDK 7 can execute the resulting object, but rejects that type at\n * compile time. Raw Zod 4 schemas implement the shared Standard Schema\n * interface accepted by both SDK versions.\n */\nexport function imageGeneration(args: NeonImageGenerationArgs = {}) {\n\tconst type: \"provider\" = \"provider\";\n\tconst id: \"openai.image_generation\" = \"openai.image_generation\";\n\tconst isProviderExecuted: true = true;\n\n\treturn {\n\t\ttype,\n\t\tid,\n\t\targs: { ...args },\n\t\tinputSchema: imageGenerationInputSchema,\n\t\toutputSchema: imageGenerationOutputSchema,\n\t\tisProviderExecuted,\n\t};\n}\n\nexport type NeonOpenAITools = Omit<\n\ttypeof openaiProvider.tools,\n\t\"imageGeneration\"\n> & {\n\timageGeneration: typeof imageGeneration;\n};\n\nexport const neonOpenAITools: NeonOpenAITools = {\n\t...openaiProvider.tools,\n\timageGeneration,\n};\n"],"mappings":";;;AAmBA,MAAM,6BAA6B,EAAE,OAAO,CAAC,CAAC;AAC9C,MAAM,8BAA8B,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;;;;;;;;;AAUnE,SAAgB,gBAAgB,OAAgC,CAAC,GAAG;CACnE,MAAM,OAAmB;CACzB,MAAM,KAAgC;CACtC,MAAM,qBAA2B;CAEjC,OAAO;EACN;EACA;EACA,MAAM,EAAE,GAAG,KAAK;EAChB,aAAa;EACb,cAAc;EACd;CACD;AACD;AASA,MAAa,kBAAmC;CAC/C,GAAGA,OAAe;CAClB;AACD"}
@@ -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;;;;;;;;;;AAE2B;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,11 +1,10 @@
1
1
  import { NeonChatModelId } from "./neon-chat-options.js";
2
- import { openai } from "@ai-sdk/openai";
2
+ import { neonOpenAITools } from "./neon-openai-tools.js";
3
3
  import { LanguageModelV3, ProviderV3 } from "@ai-sdk/provider";
4
4
  import { FetchFunction } from "@ai-sdk/provider-utils";
5
5
  import { z } from "zod/v4";
6
6
 
7
7
  //#region src/lib/provider.d.ts
8
-
9
8
  declare const neonErrorSchema: z.ZodObject<{
10
9
  error: z.ZodObject<{
11
10
  message: z.ZodString;
@@ -40,7 +39,7 @@ interface NeonProvider extends ProviderV3 {
40
39
  /** Creates a Neon AI Gateway chat model for text generation. */
41
40
  chat(modelId: NeonChatModelId): LanguageModelV3;
42
41
  /** OpenAI Responses tools (e.g. `imageGeneration`) for OpenAI-routed models. */
43
- tools: typeof openai.tools;
42
+ tools: typeof neonOpenAITools;
44
43
  /** @deprecated Use `embeddingModel` instead. */
45
44
  textEmbeddingModel(modelId: string): never;
46
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/lib/provider.ts"],"mappings":";;;;;;;;cAoCM,iBAAe,CAAA,CAAA;;;;;IAAA,IAAA,eAAA,cAAA,aAAA,CAAA,CAAA;EAAA,CAAA,eAAA,CAAA;AASrB,CAAA,eAAY,CAAA;AAAa,KAAb,aAAA,GAAgB,CAAA,CAAE,KAAL,CAAA,OAAkB,eAAlB,CAAA;AAAkB,UA4C1B,oBAAA,CA5C0B;;AAAR;AA4CnC;;;SAkBS,CAAA,EAAA,MAAA;EAAa;AAGtB;;;QAE6B,CAAA,EAAA,MAAA;;SAGa,CAAA,EAX/B,MAW+B,CAAA,MAAA,EAAA,MAAA,CAAA;;OAGT,CAAA,EAXxB,aAWwB;;AARK,UAArB,YAAA,SAAqB,UAAA,CAAA;EAAU;EAiBhC,CAAA,OAAA,EAfL,eAee,CAAA,EAfG,eAeH;EAAA;eAAU,CAAA,OAAA,EAZZ,eAYY,CAAA,EAZM,eAYN;;EAAwC,IAAA,CAAA,OAAA,EAT7D,eAS6D,CAAA,EAT3C,eAS2C;EA+F/D;gBArGE,MAAA,CAAe;;;;iBAMd,UAAA,WAAoB,uBAA4B;;cA+FnD,MAAI"}
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"}
@@ -2,25 +2,14 @@ import { NeonAnthropicLanguageModel } from "./neon-anthropic-language-model.js";
2
2
  import { getNeonModelRoute } from "./neon-model-capabilities.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";
7
+ import { neonOpenAITools } from "./neon-openai-tools.js";
6
8
  import { VERSION } from "./version.js";
7
- import { openai } from "@ai-sdk/openai";
8
9
  import { NoSuchModelError } from "@ai-sdk/provider";
9
10
  import { generateId, loadApiKey, loadSetting, withUserAgentSuffix, withoutTrailingSlash } from "@ai-sdk/provider-utils";
10
11
  import { z } from "zod/v4";
11
12
  //#region src/lib/provider.ts
12
- /**
13
- * Community Vercel AI SDK provider for the Neon AI Gateway.
14
- *
15
- * `createNeon()` returns a provider that routes each model to the best gateway
16
- * endpoint based on its id (Anthropic → native Messages, OpenAI → native
17
- * Responses incl. Codex, everything else → unified Chat Completions), so a single
18
- * `neon('claude-...')` call (same base URL + token) reaches the whole catalog.
19
- * Ids use the canonical Neon (unprefixed) form; the legacy `databricks-` prefix
20
- * is also accepted. Configure with the branch-scoped `NEON_AI_GATEWAY_BASE_URL` +
21
- * `NEON_AI_GATEWAY_TOKEN` emitted by `neonctl env pull` / `neon dev`, or pass
22
- * `baseURL` / `apiKey` explicitly.
23
- */
24
13
  const neonErrorStructure = {
25
14
  errorSchema: z.object({ error: z.object({
26
15
  message: z.string(),
@@ -73,21 +62,21 @@ function createNeon(options = {}) {
73
62
  provider: "neon.anthropic",
74
63
  baseURL: `${getHost()}/anthropic/v1`,
75
64
  headers: () => getHeaders({ "anthropic-version": "2023-06-01" }),
76
- fetch: options.fetch,
65
+ fetch: wrapFetchWithGatewayErrorNormalization(options.fetch, "anthropic"),
77
66
  generateId
78
67
  });
79
68
  const createOpenAIModel = (modelId) => new NeonResponsesLanguageModel(modelId, {
80
69
  provider: "neon.openai.responses",
81
70
  url: ({ path }) => `${getHost()}/openai/v1${path}`,
82
71
  headers: getHeaders,
83
- fetch: options.fetch,
72
+ fetch: wrapFetchWithGatewayErrorNormalization(options.fetch, "openai"),
84
73
  fileIdPrefixes: ["file-"]
85
74
  });
86
75
  const createChatModel = (modelId) => new NeonChatLanguageModel(modelId, {
87
76
  provider: "neon.chat",
88
77
  url: ({ path }) => `${getHost()}/v1${path}`,
89
78
  headers: getHeaders,
90
- fetch: wrapFetchWithHarmonyNormalization(options.fetch),
79
+ fetch: wrapFetchWithHarmonyNormalization(wrapFetchWithGatewayErrorNormalization(options.fetch, "openai")),
91
80
  errorStructure: neonErrorStructure,
92
81
  transformRequestBody: transformNeonRequestBody,
93
82
  supportsStructuredOutputs: true
@@ -103,7 +92,7 @@ function createNeon(options = {}) {
103
92
  provider.specificationVersion = "v3";
104
93
  provider.languageModel = createLanguageModel;
105
94
  provider.chat = createLanguageModel;
106
- provider.tools = openai.tools;
95
+ provider.tools = neonOpenAITools;
107
96
  provider.embeddingModel = (modelId) => {
108
97
  throw new NoSuchModelError({
109
98
  modelId,
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","names":["openaiProvider"],"sources":["../../src/lib/provider.ts"],"sourcesContent":["/**\n * Community Vercel AI SDK provider for the Neon AI Gateway.\n *\n * `createNeon()` returns a provider that routes each model to the best gateway\n * endpoint based on its id (Anthropic → native Messages, OpenAI → native\n * Responses incl. Codex, everything else → unified Chat Completions), so a single\n * `neon('claude-...')` call (same base URL + token) reaches the whole catalog.\n * Ids use the canonical Neon (unprefixed) form; the legacy `databricks-` prefix\n * is also accepted. Configure with the branch-scoped `NEON_AI_GATEWAY_BASE_URL` +\n * `NEON_AI_GATEWAY_TOKEN` emitted by `neonctl env pull` / `neon dev`, or pass\n * `baseURL` / `apiKey` explicitly.\n */\nimport { openai as openaiProvider } from \"@ai-sdk/openai\";\nimport type { ProviderErrorStructure } from \"@ai-sdk/openai-compatible\";\nimport {\n\ttype LanguageModelV3,\n\tNoSuchModelError,\n\ttype ProviderV3,\n} from \"@ai-sdk/provider\";\nimport {\n\ttype FetchFunction,\n\tgenerateId,\n\tloadApiKey,\n\tloadSetting,\n\twithoutTrailingSlash,\n\twithUserAgentSuffix,\n} from \"@ai-sdk/provider-utils\";\nimport { z } from \"zod/v4\";\nimport { NeonAnthropicLanguageModel } from \"./neon-anthropic-language-model.js\";\nimport { NeonChatLanguageModel } from \"./neon-chat-language-model.js\";\nimport type { NeonChatModelId } from \"./neon-chat-options.js\";\nimport { wrapFetchWithHarmonyNormalization } from \"./neon-harmony-normalize.js\";\nimport { getNeonModelRoute } from \"./neon-model-capabilities.js\";\nimport { NeonResponsesLanguageModel } from \"./neon-responses-language-model.js\";\nimport { VERSION } from \"./version.js\";\n\nconst neonErrorSchema = z.object({\n\terror: z.object({\n\t\tmessage: z.string(),\n\t\ttype: z.string().nullish(),\n\t\tparam: z.unknown().nullish(),\n\t\tcode: z.unknown().nullish(),\n\t}),\n});\n\nexport type NeonErrorData = z.infer<typeof neonErrorSchema>;\n\nconst neonErrorStructure: ProviderErrorStructure<NeonErrorData> = {\n\terrorSchema: neonErrorSchema,\n\terrorToMessage: (data) => data.error.message,\n};\n\n/**\n * Recursively remove the JSON Schema `$schema` marker, which some gateway\n * backends (notably Gemini) reject in tool/structured-output schemas. Other\n * backends ignore its absence, so stripping it everywhere is safe.\n */\nfunction stripJsonSchemaMarker(value: unknown): unknown {\n\tif (Array.isArray(value)) {\n\t\treturn value.map(stripJsonSchemaMarker);\n\t}\n\tif (value !== null && typeof value === \"object\") {\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const [key, entry] of Object.entries(value)) {\n\t\t\tif (key === \"$schema\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tresult[key] = stripJsonSchemaMarker(entry);\n\t\t}\n\t\treturn result;\n\t}\n\treturn value;\n}\n\nfunction transformNeonRequestBody(\n\targs: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst transformed = { ...args };\n\tif (transformed.tools != null) {\n\t\ttransformed.tools = stripJsonSchemaMarker(transformed.tools);\n\t}\n\tif (transformed.response_format != null) {\n\t\ttransformed.response_format = stripJsonSchemaMarker(\n\t\t\ttransformed.response_format,\n\t\t);\n\t}\n\treturn transformed;\n}\n\nexport interface NeonProviderSettings {\n\t/**\n\t * Neon AI Gateway base URL — the branch-scoped host root, e.g.\n\t * `https://<branch-id>-api.ai.<region>.aws.neon.tech`. The gateway paths are\n\t * appended internally. Falls back to the `NEON_AI_GATEWAY_BASE_URL` env var.\n\t */\n\tbaseURL?: string;\n\n\t/**\n\t * Neon AI Gateway platform token (the `nt_live_...` value). Falls back to the\n\t * `NEON_AI_GATEWAY_TOKEN` env var.\n\t */\n\tapiKey?: string;\n\n\t/** Custom headers to include in the requests. */\n\theaders?: Record<string, string>;\n\n\t/** Custom fetch implementation (e.g. for testing or middleware). */\n\tfetch?: FetchFunction;\n}\n\nexport interface NeonProvider extends ProviderV3 {\n\t/** Creates a Neon AI Gateway model for text generation. */\n\t(modelId: NeonChatModelId): LanguageModelV3;\n\n\t/** Creates a Neon AI Gateway model for text generation. */\n\tlanguageModel(modelId: NeonChatModelId): LanguageModelV3;\n\n\t/** Creates a Neon AI Gateway chat model for text generation. */\n\tchat(modelId: NeonChatModelId): LanguageModelV3;\n\n\t/** OpenAI Responses tools (e.g. `imageGeneration`) for OpenAI-routed models. */\n\ttools: typeof openaiProvider.tools;\n\n\t/** @deprecated Use `embeddingModel` instead. */\n\ttextEmbeddingModel(modelId: string): never;\n}\n\nexport function createNeon(options: NeonProviderSettings = {}): NeonProvider {\n\t// Resolved lazily so that `createNeon()` and the default `neon` instance do\n\t// not throw at import time when configuration comes from the environment.\n\tconst getHost = () =>\n\t\twithoutTrailingSlash(\n\t\t\tloadSetting({\n\t\t\t\tsettingValue: options.baseURL,\n\t\t\t\tenvironmentVariableName: \"NEON_AI_GATEWAY_BASE_URL\",\n\t\t\t\tsettingName: \"baseURL\",\n\t\t\t\tdescription: \"Neon AI Gateway base URL\",\n\t\t\t}),\n\t\t);\n\n\tconst getHeaders = (extra?: Record<string, string>) =>\n\t\twithUserAgentSuffix(\n\t\t\t{\n\t\t\t\tAuthorization: `Bearer ${loadApiKey({\n\t\t\t\t\tapiKey: options.apiKey,\n\t\t\t\t\tenvironmentVariableName: \"NEON_AI_GATEWAY_TOKEN\",\n\t\t\t\t\tdescription: \"Neon AI Gateway token\",\n\t\t\t\t})}`,\n\t\t\t\t...extra,\n\t\t\t\t...options.headers,\n\t\t\t},\n\t\t\t`neondatabase/ai-sdk-provider/${VERSION}`,\n\t\t);\n\n\t// Anthropic models -> native Messages API.\n\tconst createAnthropicModel = (modelId: NeonChatModelId) =>\n\t\tnew NeonAnthropicLanguageModel(modelId, {\n\t\t\tprovider: \"neon.anthropic\",\n\t\t\tbaseURL: `${getHost()}/anthropic/v1`,\n\t\t\theaders: () => getHeaders({ \"anthropic-version\": \"2023-06-01\" }),\n\t\t\tfetch: options.fetch,\n\t\t\tgenerateId,\n\t\t});\n\n\t// OpenAI models (incl. Codex, only served natively) -> Responses API.\n\tconst createOpenAIModel = (modelId: NeonChatModelId) =>\n\t\tnew NeonResponsesLanguageModel(modelId, {\n\t\t\tprovider: \"neon.openai.responses\",\n\t\t\turl: ({ path }) => `${getHost()}/openai/v1${path}`,\n\t\t\theaders: getHeaders,\n\t\t\tfetch: options.fetch,\n\t\t\tfileIdPrefixes: [\"file-\"],\n\t\t});\n\n\t// Everything else (Gemini, Llama, Qwen, gpt-oss, ...) -> unified Chat\n\t// Completions endpoint. Gemini is here because its native endpoint can't stream.\n\tconst createChatModel = (modelId: NeonChatModelId) =>\n\t\tnew NeonChatLanguageModel(modelId, {\n\t\t\tprovider: \"neon.chat\",\n\t\t\turl: ({ path }) => `${getHost()}/v1${path}`,\n\t\t\theaders: getHeaders,\n\t\t\t// TODO(#308): Temporary workaround for gpt-oss. The gateway returns a\n\t\t\t// non-compliant \"harmony\" content-array shape; this wrapper normalizes it\n\t\t\t// to the Chat Completions contract and is a transparent pass-through for\n\t\t\t// every compliant response. Remove it (and neon-harmony-normalize.ts) once\n\t\t\t// the gateway returns spec-compliant gpt-oss responses.\n\t\t\tfetch: wrapFetchWithHarmonyNormalization(options.fetch),\n\t\t\terrorStructure: neonErrorStructure,\n\t\t\ttransformRequestBody: transformNeonRequestBody,\n\t\t\tsupportsStructuredOutputs: true,\n\t\t});\n\n\tconst createLanguageModel = (modelId: NeonChatModelId): LanguageModelV3 => {\n\t\tswitch (getNeonModelRoute(modelId)) {\n\t\t\tcase \"anthropic\":\n\t\t\t\treturn createAnthropicModel(modelId);\n\t\t\tcase \"openai\":\n\t\t\t\treturn createOpenAIModel(modelId);\n\t\t\tdefault:\n\t\t\t\treturn createChatModel(modelId);\n\t\t}\n\t};\n\n\tconst provider = (modelId: NeonChatModelId) => createLanguageModel(modelId);\n\n\tprovider.specificationVersion = \"v3\" as const;\n\tprovider.languageModel = createLanguageModel;\n\tprovider.chat = createLanguageModel;\n\tprovider.tools = openaiProvider.tools;\n\n\tprovider.embeddingModel = (modelId: string) => {\n\t\tthrow new NoSuchModelError({ modelId, modelType: \"embeddingModel\" });\n\t};\n\tprovider.textEmbeddingModel = provider.embeddingModel;\n\tprovider.imageModel = (modelId: string) => {\n\t\tthrow new NoSuchModelError({ modelId, modelType: \"imageModel\" });\n\t};\n\n\treturn provider;\n}\n\n/** Default Neon provider instance (reads `NEON_AI_GATEWAY_*` env vars). */\nexport const neon = createNeon();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+CA,MAAM,qBAA4D;CACjE,aAZuB,EAAE,OAAO,EAChC,OAAO,EAAE,OAAO;EACf,SAAS,EAAE,OAAO;EAClB,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ;EACzB,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ;EAC3B,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ;CAC3B,CAAC,EACF,CAK4B;CAC3B,iBAAiB,SAAS,KAAK,MAAM;AACtC;;;;;;AAOA,SAAS,sBAAsB,OAAyB;CACvD,IAAI,MAAM,QAAQ,KAAK,GACtB,OAAO,MAAM,IAAI,qBAAqB;CAEvC,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;EAChD,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;GACjD,IAAI,QAAQ,WACX;GAED,OAAO,OAAO,sBAAsB,KAAK;EAC1C;EACA,OAAO;CACR;CACA,OAAO;AACR;AAEA,SAAS,yBACR,MAC0B;CAC1B,MAAM,cAAc,EAAE,GAAG,KAAK;CAC9B,IAAI,YAAY,SAAS,MACxB,YAAY,QAAQ,sBAAsB,YAAY,KAAK;CAE5D,IAAI,YAAY,mBAAmB,MAClC,YAAY,kBAAkB,sBAC7B,YAAY,eACb;CAED,OAAO;AACR;AAwCA,SAAgB,WAAW,UAAgC,CAAC,GAAiB;CAG5E,MAAM,gBACL,qBACC,YAAY;EACX,cAAc,QAAQ;EACtB,yBAAyB;EACzB,aAAa;EACb,aAAa;CACd,CAAC,CACF;CAED,MAAM,cAAc,UACnB,oBACC;EACC,eAAe,UAAU,WAAW;GACnC,QAAQ,QAAQ;GAChB,yBAAyB;GACzB,aAAa;EACd,CAAC;EACD,GAAG;EACH,GAAG,QAAQ;CACZ,GACA,gCAAgC,SACjC;CAGD,MAAM,wBAAwB,YAC7B,IAAI,2BAA2B,SAAS;EACvC,UAAU;EACV,SAAS,GAAG,QAAQ,EAAE;EACtB,eAAe,WAAW,EAAE,qBAAqB,aAAa,CAAC;EAC/D,OAAO,QAAQ;EACf;CACD,CAAC;CAGF,MAAM,qBAAqB,YAC1B,IAAI,2BAA2B,SAAS;EACvC,UAAU;EACV,MAAM,EAAE,WAAW,GAAG,QAAQ,EAAE,YAAY;EAC5C,SAAS;EACT,OAAO,QAAQ;EACf,gBAAgB,CAAC,OAAO;CACzB,CAAC;CAIF,MAAM,mBAAmB,YACxB,IAAI,sBAAsB,SAAS;EAClC,UAAU;EACV,MAAM,EAAE,WAAW,GAAG,QAAQ,EAAE,KAAK;EACrC,SAAS;EAMT,OAAO,kCAAkC,QAAQ,KAAK;EACtD,gBAAgB;EAChB,sBAAsB;EACtB,2BAA2B;CAC5B,CAAC;CAEF,MAAM,uBAAuB,YAA8C;EAC1E,QAAQ,kBAAkB,OAAO,GAAjC;GACC,KAAK,aACJ,OAAO,qBAAqB,OAAO;GACpC,KAAK,UACJ,OAAO,kBAAkB,OAAO;GACjC,SACC,OAAO,gBAAgB,OAAO;EAChC;CACD;CAEA,MAAM,YAAY,YAA6B,oBAAoB,OAAO;CAE1E,SAAS,uBAAuB;CAChC,SAAS,gBAAgB;CACzB,SAAS,OAAO;CAChB,SAAS,QAAQA,OAAe;CAEhC,SAAS,kBAAkB,YAAoB;EAC9C,MAAM,IAAI,iBAAiB;GAAE;GAAS,WAAW;EAAiB,CAAC;CACpE;CACA,SAAS,qBAAqB,SAAS;CACvC,SAAS,cAAc,YAAoB;EAC1C,MAAM,IAAI,iBAAiB;GAAE;GAAS,WAAW;EAAa,CAAC;CAChE;CAEA,OAAO;AACR;;AAGA,MAAa,OAAO,WAAW"}
1
+ {"version":3,"file":"provider.js","names":[],"sources":["../../src/lib/provider.ts"],"sourcesContent":["/**\n * Community Vercel AI SDK provider for the Neon AI Gateway.\n *\n * `createNeon()` returns a provider that routes each model to the best gateway\n * endpoint based on its id (Anthropic → native Messages, OpenAI → native\n * Responses incl. Codex, everything else → unified Chat Completions), so a single\n * `neon('claude-...')` call (same base URL + token) reaches the whole catalog.\n * Ids use the canonical Neon (unprefixed) form; the legacy `databricks-` prefix\n * is also accepted. Configure with the branch-scoped `NEON_AI_GATEWAY_BASE_URL` +\n * `NEON_AI_GATEWAY_TOKEN` emitted by `neonctl env pull` / `neon dev`, or pass\n * `baseURL` / `apiKey` explicitly.\n */\nimport type { ProviderErrorStructure } from \"@ai-sdk/openai-compatible\";\nimport {\n\ttype LanguageModelV3,\n\tNoSuchModelError,\n\ttype ProviderV3,\n} from \"@ai-sdk/provider\";\nimport {\n\ttype FetchFunction,\n\tgenerateId,\n\tloadApiKey,\n\tloadSetting,\n\twithoutTrailingSlash,\n\twithUserAgentSuffix,\n} from \"@ai-sdk/provider-utils\";\nimport { z } from \"zod/v4\";\nimport { NeonAnthropicLanguageModel } from \"./neon-anthropic-language-model.js\";\nimport { NeonChatLanguageModel } from \"./neon-chat-language-model.js\";\nimport type { NeonChatModelId } from \"./neon-chat-options.js\";\nimport { wrapFetchWithGatewayErrorNormalization } from \"./neon-gateway-error.js\";\nimport { wrapFetchWithHarmonyNormalization } from \"./neon-harmony-normalize.js\";\nimport { getNeonModelRoute } from \"./neon-model-capabilities.js\";\nimport { neonOpenAITools } from \"./neon-openai-tools.js\";\nimport { NeonResponsesLanguageModel } from \"./neon-responses-language-model.js\";\nimport { VERSION } from \"./version.js\";\n\nconst neonErrorSchema = z.object({\n\terror: z.object({\n\t\tmessage: z.string(),\n\t\ttype: z.string().nullish(),\n\t\tparam: z.unknown().nullish(),\n\t\tcode: z.unknown().nullish(),\n\t}),\n});\n\nexport type NeonErrorData = z.infer<typeof neonErrorSchema>;\n\nconst neonErrorStructure: ProviderErrorStructure<NeonErrorData> = {\n\terrorSchema: neonErrorSchema,\n\terrorToMessage: (data) => data.error.message,\n};\n\n/**\n * Recursively remove the JSON Schema `$schema` marker, which some gateway\n * backends (notably Gemini) reject in tool/structured-output schemas. Other\n * backends ignore its absence, so stripping it everywhere is safe.\n */\nfunction stripJsonSchemaMarker(value: unknown): unknown {\n\tif (Array.isArray(value)) {\n\t\treturn value.map(stripJsonSchemaMarker);\n\t}\n\tif (value !== null && typeof value === \"object\") {\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const [key, entry] of Object.entries(value)) {\n\t\t\tif (key === \"$schema\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tresult[key] = stripJsonSchemaMarker(entry);\n\t\t}\n\t\treturn result;\n\t}\n\treturn value;\n}\n\nfunction transformNeonRequestBody(\n\targs: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst transformed = { ...args };\n\tif (transformed.tools != null) {\n\t\ttransformed.tools = stripJsonSchemaMarker(transformed.tools);\n\t}\n\tif (transformed.response_format != null) {\n\t\ttransformed.response_format = stripJsonSchemaMarker(\n\t\t\ttransformed.response_format,\n\t\t);\n\t}\n\treturn transformed;\n}\n\nexport interface NeonProviderSettings {\n\t/**\n\t * Neon AI Gateway base URL — the branch-scoped host root, e.g.\n\t * `https://<branch-id>-api.ai.<region>.aws.neon.tech`. The gateway paths are\n\t * appended internally. Falls back to the `NEON_AI_GATEWAY_BASE_URL` env var.\n\t */\n\tbaseURL?: string;\n\n\t/**\n\t * Neon AI Gateway platform token (the `nt_live_...` value). Falls back to the\n\t * `NEON_AI_GATEWAY_TOKEN` env var.\n\t */\n\tapiKey?: string;\n\n\t/** Custom headers to include in the requests. */\n\theaders?: Record<string, string>;\n\n\t/** Custom fetch implementation (e.g. for testing or middleware). */\n\tfetch?: FetchFunction;\n}\n\nexport interface NeonProvider extends ProviderV3 {\n\t/** Creates a Neon AI Gateway model for text generation. */\n\t(modelId: NeonChatModelId): LanguageModelV3;\n\n\t/** Creates a Neon AI Gateway model for text generation. */\n\tlanguageModel(modelId: NeonChatModelId): LanguageModelV3;\n\n\t/** Creates a Neon AI Gateway chat model for text generation. */\n\tchat(modelId: NeonChatModelId): LanguageModelV3;\n\n\t/** OpenAI Responses tools (e.g. `imageGeneration`) for OpenAI-routed models. */\n\ttools: typeof neonOpenAITools;\n\n\t/** @deprecated Use `embeddingModel` instead. */\n\ttextEmbeddingModel(modelId: string): never;\n}\n\nexport function createNeon(options: NeonProviderSettings = {}): NeonProvider {\n\t// Resolved lazily so that `createNeon()` and the default `neon` instance do\n\t// not throw at import time when configuration comes from the environment.\n\tconst getHost = () =>\n\t\twithoutTrailingSlash(\n\t\t\tloadSetting({\n\t\t\t\tsettingValue: options.baseURL,\n\t\t\t\tenvironmentVariableName: \"NEON_AI_GATEWAY_BASE_URL\",\n\t\t\t\tsettingName: \"baseURL\",\n\t\t\t\tdescription: \"Neon AI Gateway base URL\",\n\t\t\t}),\n\t\t);\n\n\tconst getHeaders = (extra?: Record<string, string>) =>\n\t\twithUserAgentSuffix(\n\t\t\t{\n\t\t\t\tAuthorization: `Bearer ${loadApiKey({\n\t\t\t\t\tapiKey: options.apiKey,\n\t\t\t\t\tenvironmentVariableName: \"NEON_AI_GATEWAY_TOKEN\",\n\t\t\t\t\tdescription: \"Neon AI Gateway token\",\n\t\t\t\t})}`,\n\t\t\t\t...extra,\n\t\t\t\t...options.headers,\n\t\t\t},\n\t\t\t`neondatabase/ai-sdk-provider/${VERSION}`,\n\t\t);\n\n\t// Anthropic models -> native Messages API.\n\tconst createAnthropicModel = (modelId: NeonChatModelId) =>\n\t\tnew NeonAnthropicLanguageModel(modelId, {\n\t\t\tprovider: \"neon.anthropic\",\n\t\t\tbaseURL: `${getHost()}/anthropic/v1`,\n\t\t\theaders: () => getHeaders({ \"anthropic-version\": \"2023-06-01\" }),\n\t\t\tfetch: wrapFetchWithGatewayErrorNormalization(\n\t\t\t\toptions.fetch,\n\t\t\t\t\"anthropic\",\n\t\t\t),\n\t\t\tgenerateId,\n\t\t});\n\n\t// OpenAI models (incl. Codex, only served natively) -> Responses API.\n\tconst createOpenAIModel = (modelId: NeonChatModelId) =>\n\t\tnew NeonResponsesLanguageModel(modelId, {\n\t\t\tprovider: \"neon.openai.responses\",\n\t\t\turl: ({ path }) => `${getHost()}/openai/v1${path}`,\n\t\t\theaders: getHeaders,\n\t\t\t// This route returns Databricks error envelopes the OpenAI schema\n\t\t\t// cannot read, which reach the caller as a bare \"Bad Request\".\n\t\t\t// `OpenAIConfig` has no error hook, so the rewrite rides on fetch.\n\t\t\tfetch: wrapFetchWithGatewayErrorNormalization(\n\t\t\t\toptions.fetch,\n\t\t\t\t\"openai\",\n\t\t\t),\n\t\t\tfileIdPrefixes: [\"file-\"],\n\t\t});\n\n\t// Everything else (Gemini, Llama, Qwen, gpt-oss, ...) -> unified Chat\n\t// Completions endpoint. Gemini is here because its native endpoint can't stream.\n\tconst createChatModel = (modelId: NeonChatModelId) =>\n\t\tnew NeonChatLanguageModel(modelId, {\n\t\t\tprovider: \"neon.chat\",\n\t\t\turl: ({ path }) => `${getHost()}/v1${path}`,\n\t\t\theaders: getHeaders,\n\t\t\t// Two wrappers, on disjoint halves of the response: the harmony one\n\t\t\t// rewrites successful gpt-oss bodies and returns early on any error,\n\t\t\t// the error one only touches non-2xx bodies. This route emits the\n\t\t\t// same flat `{ error_code, message }` Databricks envelope as the\n\t\t\t// Responses route (an out-of-range `temperature`, say), which\n\t\t\t// `errorStructure` below cannot read on its own.\n\t\t\t//\n\t\t\t// TODO(#308): Temporary workaround for gpt-oss. The gateway returns a\n\t\t\t// non-compliant \"harmony\" content-array shape; this wrapper normalizes it\n\t\t\t// to the Chat Completions contract and is a transparent pass-through for\n\t\t\t// every compliant response. Remove it (and neon-harmony-normalize.ts) once\n\t\t\t// the gateway returns spec-compliant gpt-oss responses.\n\t\t\tfetch: wrapFetchWithHarmonyNormalization(\n\t\t\t\twrapFetchWithGatewayErrorNormalization(options.fetch, \"openai\"),\n\t\t\t),\n\t\t\terrorStructure: neonErrorStructure,\n\t\t\ttransformRequestBody: transformNeonRequestBody,\n\t\t\tsupportsStructuredOutputs: true,\n\t\t});\n\n\tconst createLanguageModel = (modelId: NeonChatModelId): LanguageModelV3 => {\n\t\tswitch (getNeonModelRoute(modelId)) {\n\t\t\tcase \"anthropic\":\n\t\t\t\treturn createAnthropicModel(modelId);\n\t\t\tcase \"openai\":\n\t\t\t\treturn createOpenAIModel(modelId);\n\t\t\tdefault:\n\t\t\t\treturn createChatModel(modelId);\n\t\t}\n\t};\n\n\tconst provider = (modelId: NeonChatModelId) => createLanguageModel(modelId);\n\n\tprovider.specificationVersion = \"v3\" as const;\n\tprovider.languageModel = createLanguageModel;\n\tprovider.chat = createLanguageModel;\n\tprovider.tools = neonOpenAITools;\n\n\tprovider.embeddingModel = (modelId: string) => {\n\t\tthrow new NoSuchModelError({ modelId, modelType: \"embeddingModel\" });\n\t};\n\tprovider.textEmbeddingModel = provider.embeddingModel;\n\tprovider.imageModel = (modelId: string) => {\n\t\tthrow new NoSuchModelError({ modelId, modelType: \"imageModel\" });\n\t};\n\n\treturn provider;\n}\n\n/** Default Neon provider instance (reads `NEON_AI_GATEWAY_*` env vars). */\nexport const neon = createNeon();\n"],"mappings":";;;;;;;;;;;;AAgDA,MAAM,qBAA4D;CACjE,aAZuB,EAAE,OAAO,EAChC,OAAO,EAAE,OAAO;EACf,SAAS,EAAE,OAAO;EAClB,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ;EACzB,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ;EAC3B,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ;CAC3B,CAAC,EACF,CAK4B;CAC3B,iBAAiB,SAAS,KAAK,MAAM;AACtC;;;;;;AAOA,SAAS,sBAAsB,OAAyB;CACvD,IAAI,MAAM,QAAQ,KAAK,GACtB,OAAO,MAAM,IAAI,qBAAqB;CAEvC,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;EAChD,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;GACjD,IAAI,QAAQ,WACX;GAED,OAAO,OAAO,sBAAsB,KAAK;EAC1C;EACA,OAAO;CACR;CACA,OAAO;AACR;AAEA,SAAS,yBACR,MAC0B;CAC1B,MAAM,cAAc,EAAE,GAAG,KAAK;CAC9B,IAAI,YAAY,SAAS,MACxB,YAAY,QAAQ,sBAAsB,YAAY,KAAK;CAE5D,IAAI,YAAY,mBAAmB,MAClC,YAAY,kBAAkB,sBAC7B,YAAY,eACb;CAED,OAAO;AACR;AAwCA,SAAgB,WAAW,UAAgC,CAAC,GAAiB;CAG5E,MAAM,gBACL,qBACC,YAAY;EACX,cAAc,QAAQ;EACtB,yBAAyB;EACzB,aAAa;EACb,aAAa;CACd,CAAC,CACF;CAED,MAAM,cAAc,UACnB,oBACC;EACC,eAAe,UAAU,WAAW;GACnC,QAAQ,QAAQ;GAChB,yBAAyB;GACzB,aAAa;EACd,CAAC;EACD,GAAG;EACH,GAAG,QAAQ;CACZ,GACA,gCAAgC,SACjC;CAGD,MAAM,wBAAwB,YAC7B,IAAI,2BAA2B,SAAS;EACvC,UAAU;EACV,SAAS,GAAG,QAAQ,EAAE;EACtB,eAAe,WAAW,EAAE,qBAAqB,aAAa,CAAC;EAC/D,OAAO,uCACN,QAAQ,OACR,WACD;EACA;CACD,CAAC;CAGF,MAAM,qBAAqB,YAC1B,IAAI,2BAA2B,SAAS;EACvC,UAAU;EACV,MAAM,EAAE,WAAW,GAAG,QAAQ,EAAE,YAAY;EAC5C,SAAS;EAIT,OAAO,uCACN,QAAQ,OACR,QACD;EACA,gBAAgB,CAAC,OAAO;CACzB,CAAC;CAIF,MAAM,mBAAmB,YACxB,IAAI,sBAAsB,SAAS;EAClC,UAAU;EACV,MAAM,EAAE,WAAW,GAAG,QAAQ,EAAE,KAAK;EACrC,SAAS;EAaT,OAAO,kCACN,uCAAuC,QAAQ,OAAO,QAAQ,CAC/D;EACA,gBAAgB;EAChB,sBAAsB;EACtB,2BAA2B;CAC5B,CAAC;CAEF,MAAM,uBAAuB,YAA8C;EAC1E,QAAQ,kBAAkB,OAAO,GAAjC;GACC,KAAK,aACJ,OAAO,qBAAqB,OAAO;GACpC,KAAK,UACJ,OAAO,kBAAkB,OAAO;GACjC,SACC,OAAO,gBAAgB,OAAO;EAChC;CACD;CAEA,MAAM,YAAY,YAA6B,oBAAoB,OAAO;CAE1E,SAAS,uBAAuB;CAChC,SAAS,gBAAgB;CACzB,SAAS,OAAO;CAChB,SAAS,QAAQ;CAEjB,SAAS,kBAAkB,YAAoB;EAC9C,MAAM,IAAI,iBAAiB;GAAE;GAAS,WAAW;EAAiB,CAAC;CACpE;CACA,SAAS,qBAAqB,SAAS;CACvC,SAAS,cAAc,YAAoB;EAC1C,MAAM,IAAI,iBAAiB;GAAE;GAAS,WAAW;EAAa,CAAC;CAChE;CAEA,OAAO;AACR;;AAGA,MAAa,OAAO,WAAW"}
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.7.1";
2
+ var version = "0.7.3";
3
3
  //#endregion
4
4
  export { version };
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neondatabase/ai-sdk-provider",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Community Vercel AI SDK provider for the Neon AI Gateway.",
5
5
  "keywords": [
6
6
  "neon",
@@ -39,7 +39,8 @@
39
39
  "devDependencies": {
40
40
  "@types/node": "^20.19.0",
41
41
  "@vitest/coverage-v8": "^3.0.9",
42
- "ai": "6.0.132",
42
+ "ai": "6.0.228",
43
+ "ai-v7": "npm:ai@7.0.0-beta.187",
43
44
  "console-fail-test": "0.5.0",
44
45
  "tsdown": "^0.14.1",
45
46
  "typescript": "^5.9.0",
@@ -60,7 +61,7 @@
60
61
  "@ai-sdk/provider-utils": "^4.0.27"
61
62
  },
62
63
  "peerDependencies": {
63
- "ai": "^6.0.0",
64
+ "ai": "^6.0.0 || ^7.0.0-0",
64
65
  "zod": "^3.25.76 || ^4.1.8"
65
66
  },
66
67
  "scripts": {