@neon/ai-sdk-provider 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
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
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.
6
6
 
@@ -9,10 +9,10 @@ Model ids use the canonical Neon (unprefixed) form — `claude-sonnet-4-6`, `gpt
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
 
@@ -100,4 +100,4 @@ cp .env.example .env # fill NEON_AI_GATEWAY_BASE_URL + NEON_AI_GATEWAY_TOKEN f
100
100
  pnpm test:e2e
101
101
  ```
102
102
 
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.
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`. 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"}
@@ -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"}
@@ -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":";;;;;;;AAqBA;AACC;AA4BU;AACC;AAAR;AAKO;AACC;AAAR;AAnCK;AACG;AAAe;cAFd,0BAAA,SACJ,4BAAA,YACG;;;sBA2BD,6BACP,QAAQ;oBAKD,6BACP,QAAQ"}
@@ -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":";;;;;;;cAoCM,iBAAe,CAAA,CAAA;;IAAf,OAAA,aAOJ;IAAA,IAAA,eAAA,cAAA,YAAA,CAAA,CAAA;;;;GAAA,CAAA,CAAA,IAAA,CAAA;KAEU,aAAA,GAAgB,CAAA,CAAE,aAAa;UA4C1B,oBAAA;;;;;;;EA9Cf;AAPmB;AAAA;AASrB;EAAyB,MAAA,CAAA,EAAA,MAAA;EAAkB;EAAf,OAAE,CAAA,EA2DnB,MA3DmB,CAAA,MAAA,EAAA,MAAA,CAAA;EAAK;EA4ClB,KAAA,CAAA,EAkBR,aAlBQ;AAAoB;AAe1B,UAMM,YAAA,SAAqB,UAN3B,CAAA;EAGF;EAAa,CAAA,OAAA,EAKX,eALW,CAAA,EAKO,eALP;EAGL;EAAa,aAAA,CAAA,OAAA,EAKN,eALM,CAAA,EAKY,eALZ;EAEnB;EAAkB,IAAA,CAAA,OAAA,EAMd,eANc,CAAA,EAMI,eANJ;EAGL;EAAkB,KAAA,EAAA,OAM3B,eAN2B;EAG3B;EAAkB,kBAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,KAAA;AAGlB;AAXuB,iBAiBtB,UAAA,CAjBsB,OAAA,CAAA,EAiBF,oBAjBE,CAAA,EAiB0B,YAjB1B;AAAU;AAiBhC,cA+FH,IA/Fa,EA+FT,YA/FS"}
@@ -3,24 +3,12 @@ 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
5
  import { wrapFetchWithHarmonyNormalization } from "./neon-harmony-normalize.js";
6
+ import { neonOpenAITools } from "./neon-openai-tools.js";
6
7
  import { VERSION } from "./version.js";
7
- import { openai } from "@ai-sdk/openai";
8
8
  import { NoSuchModelError } from "@ai-sdk/provider";
9
9
  import { generateId, loadApiKey, loadSetting, withUserAgentSuffix, withoutTrailingSlash } from "@ai-sdk/provider-utils";
10
10
  import { z } from "zod/v4";
11
11
  //#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
12
  const neonErrorStructure = {
25
13
  errorSchema: z.object({ error: z.object({
26
14
  message: z.string(),
@@ -103,7 +91,7 @@ function createNeon(options = {}) {
103
91
  provider.specificationVersion = "v3";
104
92
  provider.languageModel = createLanguageModel;
105
93
  provider.chat = createLanguageModel;
106
- provider.tools = openai.tools;
94
+ provider.tools = neonOpenAITools;
107
95
  provider.embeddingModel = (modelId) => {
108
96
  throw new NoSuchModelError({
109
97
  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 { 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: 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 = 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":";;;;;;;;;;;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,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.2";
3
3
  //#endregion
4
4
  export { version };
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neon/ai-sdk-provider",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
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": {