@tanstack/ai-vercel-gateway 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/esm/adapters/embedding.d.ts +21 -0
  4. package/dist/esm/adapters/embedding.js +73 -0
  5. package/dist/esm/adapters/embedding.js.map +1 -0
  6. package/dist/esm/adapters/factory.d.ts +16 -0
  7. package/dist/esm/adapters/factory.js +28 -0
  8. package/dist/esm/adapters/factory.js.map +1 -0
  9. package/dist/esm/adapters/image.d.ts +22 -0
  10. package/dist/esm/adapters/image.js +87 -0
  11. package/dist/esm/adapters/image.js.map +1 -0
  12. package/dist/esm/adapters/responses-text.d.ts +26 -0
  13. package/dist/esm/adapters/responses-text.js +38 -0
  14. package/dist/esm/adapters/responses-text.js.map +1 -0
  15. package/dist/esm/adapters/summarize.d.ts +9 -0
  16. package/dist/esm/adapters/summarize.js +17 -0
  17. package/dist/esm/adapters/summarize.js.map +1 -0
  18. package/dist/esm/adapters/text.d.ts +60 -0
  19. package/dist/esm/adapters/text.js +29 -0
  20. package/dist/esm/adapters/text.js.map +1 -0
  21. package/dist/esm/embedding/embedding-provider-options.d.ts +5 -0
  22. package/dist/esm/image/image-provider-options.d.ts +7 -0
  23. package/dist/esm/index.d.ts +17 -0
  24. package/dist/esm/index.js +9 -0
  25. package/dist/esm/message-types.d.ts +44 -0
  26. package/dist/esm/model-meta.d.ts +493 -0
  27. package/dist/esm/model-meta.js +351 -0
  28. package/dist/esm/model-meta.js.map +1 -0
  29. package/dist/esm/text/responses-provider-options.d.ts +3 -0
  30. package/dist/esm/text/text-provider-options.d.ts +40 -0
  31. package/dist/esm/tools/function-tool.d.ts +16 -0
  32. package/dist/esm/tools/function-tool.js +30 -0
  33. package/dist/esm/tools/function-tool.js.map +1 -0
  34. package/dist/esm/tools/index.d.ts +2 -0
  35. package/dist/esm/tools/index.js +3 -0
  36. package/dist/esm/tools/tool-converter.d.ts +7 -0
  37. package/dist/esm/tools/tool-converter.js +15 -0
  38. package/dist/esm/tools/tool-converter.js.map +1 -0
  39. package/dist/esm/utils/client.d.ts +8 -0
  40. package/dist/esm/utils/client.js +29 -0
  41. package/dist/esm/utils/client.js.map +1 -0
  42. package/dist/esm/utils/map-gateway-options.d.ts +4 -0
  43. package/dist/esm/utils/map-gateway-options.js +14 -0
  44. package/dist/esm/utils/map-gateway-options.js.map +1 -0
  45. package/package.json +73 -0
  46. package/src/adapters/embedding.ts +122 -0
  47. package/src/adapters/factory.ts +81 -0
  48. package/src/adapters/image.ts +156 -0
  49. package/src/adapters/responses-text.ts +99 -0
  50. package/src/adapters/summarize.ts +44 -0
  51. package/src/adapters/text.ts +68 -0
  52. package/src/embedding/embedding-provider-options.ts +6 -0
  53. package/src/image/image-provider-options.ts +13 -0
  54. package/src/index.ts +78 -0
  55. package/src/message-types.ts +45 -0
  56. package/src/model-meta.ts +2512 -0
  57. package/src/text/responses-provider-options.ts +10 -0
  58. package/src/text/text-provider-options.ts +47 -0
  59. package/src/tools/function-tool.ts +46 -0
  60. package/src/tools/index.ts +5 -0
  61. package/src/tools/tool-converter.ts +15 -0
  62. package/src/utils/client.ts +40 -0
  63. package/src/utils/map-gateway-options.ts +15 -0
@@ -0,0 +1,16 @@
1
+ import { Tool } from '@tanstack/ai';
2
+ export interface FunctionTool {
3
+ type: 'function';
4
+ function: {
5
+ name: string;
6
+ description?: string;
7
+ parameters: Record<string, unknown>;
8
+ strict?: boolean;
9
+ };
10
+ }
11
+ /**
12
+ * Converts a standard Tool to Vercel AI Gateway Chat Completions tool format.
13
+ *
14
+ * Tool schemas are already converted to JSON Schema in the ai layer.
15
+ */
16
+ export declare function convertFunctionToolToAdapterFormat(tool: Tool): FunctionTool;
@@ -0,0 +1,30 @@
1
+ import { makeStructuredOutputCompatible } from "@tanstack/openai-base";
2
+ //#region src/tools/function-tool.ts
3
+ /**
4
+ * Converts a standard Tool to Vercel AI Gateway Chat Completions tool format.
5
+ *
6
+ * Tool schemas are already converted to JSON Schema in the ai layer.
7
+ */
8
+ function convertFunctionToolToAdapterFormat(tool) {
9
+ const inputSchema = tool.inputSchema ?? {
10
+ type: "object",
11
+ properties: {},
12
+ required: []
13
+ };
14
+ if (inputSchema.type === "object" && !inputSchema.properties) inputSchema.properties = {};
15
+ const jsonSchema = makeStructuredOutputCompatible(inputSchema, inputSchema.required || []);
16
+ jsonSchema.additionalProperties = false;
17
+ return {
18
+ type: "function",
19
+ function: {
20
+ name: tool.name,
21
+ description: tool.description,
22
+ parameters: jsonSchema,
23
+ strict: true
24
+ }
25
+ };
26
+ }
27
+ //#endregion
28
+ export { convertFunctionToolToAdapterFormat };
29
+
30
+ //# sourceMappingURL=function-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"function-tool.js","names":[],"sources":["../../../src/tools/function-tool.ts"],"sourcesContent":["import { makeStructuredOutputCompatible } from '@tanstack/openai-base'\nimport type { JSONSchema, Tool } from '@tanstack/ai'\n\nexport interface FunctionTool {\n type: 'function'\n function: {\n name: string\n description?: string\n parameters: Record<string, unknown>\n strict?: boolean\n }\n}\n\n/**\n * Converts a standard Tool to Vercel AI Gateway Chat Completions tool format.\n *\n * Tool schemas are already converted to JSON Schema in the ai layer.\n */\nexport function convertFunctionToolToAdapterFormat(tool: Tool): FunctionTool {\n const inputSchema = (tool.inputSchema ?? {\n type: 'object',\n properties: {},\n required: [],\n }) as JSONSchema\n\n if (inputSchema.type === 'object' && !inputSchema.properties) {\n inputSchema.properties = {}\n }\n\n const jsonSchema = makeStructuredOutputCompatible(\n inputSchema,\n inputSchema.required || [],\n )\n\n jsonSchema.additionalProperties = false\n\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: jsonSchema,\n strict: true,\n },\n }\n}\n"],"mappings":";;;;;;;AAkBA,SAAgB,mCAAmC,MAA0B;CAC3E,MAAM,cAAe,KAAK,eAAe;EACvC,MAAM;EACN,YAAY,CAAC;EACb,UAAU,CAAC;CACb;CAEA,IAAI,YAAY,SAAS,YAAY,CAAC,YAAY,YAChD,YAAY,aAAa,CAAC;CAG5B,MAAM,aAAa,+BACjB,aACA,YAAY,YAAY,CAAC,CAC3B;CAEA,WAAW,uBAAuB;CAElC,OAAO;EACL,MAAM;EACN,UAAU;GACR,MAAM,KAAK;GACX,aAAa,KAAK;GAClB,YAAY;GACZ,QAAQ;EACV;CACF;AACF"}
@@ -0,0 +1,2 @@
1
+ export { convertFunctionToolToAdapterFormat, type FunctionTool, } from './function-tool.js';
2
+ export { convertToolsToProviderFormat } from './tool-converter.js';
@@ -0,0 +1,3 @@
1
+ import { convertFunctionToolToAdapterFormat } from "./function-tool.js";
2
+ import { convertToolsToProviderFormat } from "./tool-converter.js";
3
+ export { convertFunctionToolToAdapterFormat, convertToolsToProviderFormat };
@@ -0,0 +1,7 @@
1
+ import { FunctionTool } from './function-tool.js';
2
+ import { Tool } from '@tanstack/ai';
3
+ /**
4
+ * Converts an array of standard Tools to Vercel AI Gateway format.
5
+ * The Gateway Chat Completions API is OpenAI-compatible, so we support function tools.
6
+ */
7
+ export declare function convertToolsToProviderFormat(tools: Array<Tool>): Array<FunctionTool>;
@@ -0,0 +1,15 @@
1
+ import { convertFunctionToolToAdapterFormat } from "./function-tool.js";
2
+ //#region src/tools/tool-converter.ts
3
+ /**
4
+ * Converts an array of standard Tools to Vercel AI Gateway format.
5
+ * The Gateway Chat Completions API is OpenAI-compatible, so we support function tools.
6
+ */
7
+ function convertToolsToProviderFormat(tools) {
8
+ return tools.map((tool) => {
9
+ return convertFunctionToolToAdapterFormat(tool);
10
+ });
11
+ }
12
+ //#endregion
13
+ export { convertToolsToProviderFormat };
14
+
15
+ //# sourceMappingURL=tool-converter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-converter.js","names":[],"sources":["../../../src/tools/tool-converter.ts"],"sourcesContent":["import { convertFunctionToolToAdapterFormat } from './function-tool'\nimport type { FunctionTool } from './function-tool'\nimport type { Tool } from '@tanstack/ai'\n\n/**\n * Converts an array of standard Tools to Vercel AI Gateway format.\n * The Gateway Chat Completions API is OpenAI-compatible, so we support function tools.\n */\nexport function convertToolsToProviderFormat(\n tools: Array<Tool>,\n): Array<FunctionTool> {\n return tools.map((tool) => {\n return convertFunctionToolToAdapterFormat(tool)\n })\n}\n"],"mappings":";;;;;;AAQA,SAAgB,6BACd,OACqB;CACrB,OAAO,MAAM,KAAK,SAAS;EACzB,OAAO,mCAAmC,IAAI;CAChD,CAAC;AACH"}
@@ -0,0 +1,8 @@
1
+ import { ClientOptions } from 'openai';
2
+ export interface VercelGatewayClientConfig extends Omit<ClientOptions, 'apiKey'> {
3
+ apiKey: string;
4
+ httpReferer?: string;
5
+ xTitle?: string;
6
+ }
7
+ export declare function getVercelGatewayApiKeyFromEnv(): string;
8
+ export declare function withVercelGatewayDefaults(config: VercelGatewayClientConfig): ClientOptions;
@@ -0,0 +1,29 @@
1
+ import { getApiKeyFromEnv } from "@tanstack/ai-utils";
2
+ //#region src/utils/client.ts
3
+ function getVercelGatewayApiKeyFromEnv() {
4
+ try {
5
+ return getApiKeyFromEnv("AI_GATEWAY_API_KEY");
6
+ } catch {
7
+ try {
8
+ return getApiKeyFromEnv("VERCEL_OIDC_TOKEN");
9
+ } catch {
10
+ throw new Error("AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN is required. Set one in the environment or pass apiKey to the factory.");
11
+ }
12
+ }
13
+ }
14
+ function withVercelGatewayDefaults(config) {
15
+ const { httpReferer, xTitle, defaultHeaders, ...rest } = config;
16
+ return {
17
+ ...rest,
18
+ baseURL: config.baseURL || "https://ai-gateway.vercel.sh/v1",
19
+ defaultHeaders: {
20
+ ...httpReferer ? { "http-referer": httpReferer } : {},
21
+ ...xTitle ? { "x-title": xTitle } : {},
22
+ ...defaultHeaders
23
+ }
24
+ };
25
+ }
26
+ //#endregion
27
+ export { getVercelGatewayApiKeyFromEnv, withVercelGatewayDefaults };
28
+
29
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/utils/client.ts"],"sourcesContent":["import { getApiKeyFromEnv } from '@tanstack/ai-utils'\nimport type { ClientOptions } from 'openai'\n\nexport interface VercelGatewayClientConfig extends Omit<\n ClientOptions,\n 'apiKey'\n> {\n apiKey: string\n httpReferer?: string\n xTitle?: string\n}\n\nexport function getVercelGatewayApiKeyFromEnv(): string {\n try {\n return getApiKeyFromEnv('AI_GATEWAY_API_KEY')\n } catch {\n try {\n return getApiKeyFromEnv('VERCEL_OIDC_TOKEN')\n } catch {\n throw new Error(\n 'AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN is required. Set one in the environment or pass apiKey to the factory.',\n )\n }\n }\n}\n\nexport function withVercelGatewayDefaults(\n config: VercelGatewayClientConfig,\n): ClientOptions {\n const { httpReferer, xTitle, defaultHeaders, ...rest } = config\n return {\n ...rest,\n baseURL: config.baseURL || 'https://ai-gateway.vercel.sh/v1',\n defaultHeaders: {\n ...(httpReferer ? { 'http-referer': httpReferer } : {}),\n ...(xTitle ? { 'x-title': xTitle } : {}),\n ...defaultHeaders,\n },\n }\n}\n"],"mappings":";;AAYA,SAAgB,gCAAwC;CACtD,IAAI;EACF,OAAO,iBAAiB,oBAAoB;CAC9C,QAAQ;EACN,IAAI;GACF,OAAO,iBAAiB,mBAAmB;EAC7C,QAAQ;GACN,MAAM,IAAI,MACR,gHACF;EACF;CACF;AACF;AAEA,SAAgB,0BACd,QACe;CACf,MAAM,EAAE,aAAa,QAAQ,gBAAgB,GAAG,SAAS;CACzD,OAAO;EACL,GAAG;EACH,SAAS,OAAO,WAAW;EAC3B,gBAAgB;GACd,GAAI,cAAc,EAAE,gBAAgB,YAAY,IAAI,CAAC;GACrD,GAAI,SAAS,EAAE,WAAW,OAAO,IAAI,CAAC;GACtC,GAAG;EACL;CACF;AACF"}
@@ -0,0 +1,4 @@
1
+ import { VercelGatewayRoutingOptions } from '../text/text-provider-options.js';
2
+ export declare function mapGatewayModelOptions(modelOptions: (Record<string, unknown> & {
3
+ gateway?: VercelGatewayRoutingOptions;
4
+ }) | undefined): Record<string, unknown>;
@@ -0,0 +1,14 @@
1
+ //#region src/utils/map-gateway-options.ts
2
+ function mapGatewayModelOptions(modelOptions) {
3
+ if (!modelOptions) return {};
4
+ const { gateway, ...rest } = modelOptions;
5
+ if (gateway === void 0) return rest;
6
+ return {
7
+ ...rest,
8
+ providerOptions: { gateway }
9
+ };
10
+ }
11
+ //#endregion
12
+ export { mapGatewayModelOptions };
13
+
14
+ //# sourceMappingURL=map-gateway-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map-gateway-options.js","names":[],"sources":["../../../src/utils/map-gateway-options.ts"],"sourcesContent":["import type { VercelGatewayRoutingOptions } from '../text/text-provider-options'\n\nexport function mapGatewayModelOptions(\n modelOptions:\n | (Record<string, unknown> & { gateway?: VercelGatewayRoutingOptions })\n | undefined,\n): Record<string, unknown> {\n if (!modelOptions) return {}\n const { gateway, ...rest } = modelOptions\n if (gateway === undefined) return rest\n return {\n ...rest,\n providerOptions: { gateway },\n }\n}\n"],"mappings":";AAEA,SAAgB,uBACd,cAGyB;CACzB,IAAI,CAAC,cAAc,OAAO,CAAC;CAC3B,MAAM,EAAE,SAAS,GAAG,SAAS;CAC7B,IAAI,YAAY,KAAA,GAAW,OAAO;CAClC,OAAO;EACL,GAAG;EACH,iBAAiB,EAAE,QAAQ;CAC7B;AACF"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@tanstack/ai-vercel-gateway",
3
+ "version": "0.0.1",
4
+ "description": "Vercel AI Gateway adapter for TanStack AI chat, embeddings, and image generation.",
5
+ "author": "Tanner Linsley",
6
+ "license": "MIT",
7
+ "homepage": "https://tanstack.com/ai",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/TanStack/ai.git",
11
+ "directory": "packages/ai-vercel-gateway"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/TanStack/ai/issues"
15
+ },
16
+ "funding": {
17
+ "type": "github",
18
+ "url": "https://github.com/sponsors/tannerlinsley"
19
+ },
20
+ "type": "module",
21
+ "module": "./dist/esm/index.js",
22
+ "types": "./dist/esm/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/esm/index.d.ts",
26
+ "import": "./dist/esm/index.js"
27
+ },
28
+ "./tools": {
29
+ "types": "./dist/esm/tools/index.d.ts",
30
+ "import": "./dist/esm/tools/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "src"
36
+ ],
37
+ "keywords": [
38
+ "ai",
39
+ "ai-sdk",
40
+ "typescript",
41
+ "tanstack",
42
+ "vercel",
43
+ "ai-gateway",
44
+ "adapter",
45
+ "llm",
46
+ "chat",
47
+ "embeddings",
48
+ "image-generation"
49
+ ],
50
+ "devDependencies": {
51
+ "@vitest/coverage-v8": "4.0.14",
52
+ "vite": "^8.1.4"
53
+ },
54
+ "peerDependencies": {
55
+ "zod": "^4.0.0",
56
+ "@tanstack/ai": "^0.44.0"
57
+ },
58
+ "dependencies": {
59
+ "openai": "^6.41.0",
60
+ "@tanstack/openai-base": "^0.9.12",
61
+ "@tanstack/ai-utils": "^0.4.0"
62
+ },
63
+ "scripts": {
64
+ "build": "vite build",
65
+ "clean": "premove ./build ./dist",
66
+ "lint:fix": "oxlint src --type-aware --fix",
67
+ "test:build": "publint --strict",
68
+ "test:oxlint": "oxlint src --type-aware",
69
+ "test:lib": "vitest run",
70
+ "test:lib:dev": "pnpm test:lib --watch",
71
+ "test:types": "tsc"
72
+ }
73
+ }
@@ -0,0 +1,122 @@
1
+ import OpenAI from 'openai'
2
+ import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters'
3
+ import { toRunErrorPayload } from '@tanstack/ai/adapter-internals'
4
+ import { generateId } from '@tanstack/ai-utils'
5
+ import { requireTextOnlyEmbeddingInput } from '@tanstack/ai'
6
+ import {
7
+ getVercelGatewayApiKeyFromEnv,
8
+ withVercelGatewayDefaults,
9
+ } from '../utils/client'
10
+ import { mapGatewayModelOptions } from '../utils/map-gateway-options'
11
+ import type {
12
+ EmbeddingOptions,
13
+ EmbeddingResult,
14
+ TokenUsage,
15
+ } from '@tanstack/ai'
16
+ import type OpenAI_SDK from 'openai'
17
+ import type {
18
+ VercelGatewayEmbeddingModel,
19
+ VercelGatewayEmbeddingModelInputModalitiesByName,
20
+ VercelGatewayEmbeddingModelProviderOptionsByName,
21
+ } from '../model-meta'
22
+ import type { VercelGatewayEmbeddingProviderOptions } from '../embedding/embedding-provider-options'
23
+ import type { VercelGatewayClientConfig } from '../utils/client'
24
+
25
+ export interface VercelGatewayEmbeddingConfig extends VercelGatewayClientConfig {}
26
+
27
+ /**
28
+ * Vercel AI Gateway embedding adapter.
29
+ *
30
+ * Talks to `POST /v1/embeddings` on the public OpenAI-compatible Gateway API.
31
+ */
32
+ export class VercelGatewayEmbeddingAdapter<
33
+ TModel extends VercelGatewayEmbeddingModel,
34
+ > extends BaseEmbeddingAdapter<
35
+ TModel,
36
+ VercelGatewayEmbeddingProviderOptions,
37
+ VercelGatewayEmbeddingModelProviderOptionsByName,
38
+ VercelGatewayEmbeddingModelInputModalitiesByName
39
+ > {
40
+ readonly name = 'vercel-gateway' as const
41
+
42
+ protected client: OpenAI
43
+
44
+ constructor(config: VercelGatewayEmbeddingConfig, model: TModel) {
45
+ super(model, {})
46
+ this.client = new OpenAI(withVercelGatewayDefaults(config))
47
+ }
48
+
49
+ async createEmbeddings(
50
+ options: EmbeddingOptions<VercelGatewayEmbeddingProviderOptions>,
51
+ ): Promise<EmbeddingResult> {
52
+ const { model, logger } = options
53
+ const texts = requireTextOnlyEmbeddingInput(options.input, this.name, model)
54
+ const mapped = mapGatewayModelOptions(
55
+ options.modelOptions as Record<string, unknown> | undefined,
56
+ )
57
+
58
+ try {
59
+ const request: OpenAI_SDK.EmbeddingCreateParams = {
60
+ ...mapped,
61
+ model,
62
+ input: texts,
63
+ encoding_format: 'float',
64
+ }
65
+ if (options.dimensions !== undefined) {
66
+ request.dimensions = options.dimensions
67
+ }
68
+
69
+ logger.request(
70
+ `activity=embed provider=${this.name} model=${model} inputs=${texts.length}`,
71
+ { provider: this.name, model },
72
+ )
73
+
74
+ const response = await this.client.embeddings.create(request)
75
+
76
+ const usage: TokenUsage = {
77
+ promptTokens: response.usage.prompt_tokens,
78
+ completionTokens: 0,
79
+ totalTokens: response.usage.total_tokens,
80
+ }
81
+
82
+ return {
83
+ id: generateId(this.name),
84
+ model,
85
+ embeddings: response.data.map((item) => ({
86
+ vector: item.embedding,
87
+ index: item.index,
88
+ })),
89
+ usage,
90
+ }
91
+ } catch (error: unknown) {
92
+ logger.errors(`${this.name}.createEmbeddings fatal`, {
93
+ error: toRunErrorPayload(error, `${this.name}.createEmbeddings failed`),
94
+ source: `${this.name}.createEmbeddings`,
95
+ })
96
+ throw error
97
+ }
98
+ }
99
+ }
100
+
101
+ export function createVercelGatewayEmbedding<
102
+ TModel extends VercelGatewayEmbeddingModel,
103
+ >(
104
+ model: TModel,
105
+ apiKey: string,
106
+ config?: Omit<VercelGatewayEmbeddingConfig, 'apiKey'>,
107
+ ): VercelGatewayEmbeddingAdapter<TModel> {
108
+ return new VercelGatewayEmbeddingAdapter({ apiKey, ...config }, model)
109
+ }
110
+
111
+ export function vercelGatewayEmbedding<
112
+ TModel extends VercelGatewayEmbeddingModel,
113
+ >(
114
+ model: TModel,
115
+ config?: Omit<VercelGatewayEmbeddingConfig, 'apiKey'>,
116
+ ): VercelGatewayEmbeddingAdapter<TModel> {
117
+ return createVercelGatewayEmbedding(
118
+ model,
119
+ getVercelGatewayApiKeyFromEnv(),
120
+ config,
121
+ )
122
+ }
@@ -0,0 +1,81 @@
1
+ import { getVercelGatewayApiKeyFromEnv } from '../utils/client'
2
+ import { VercelGatewayTextAdapter } from './text'
3
+ import { VercelGatewayResponsesTextAdapter } from './responses-text'
4
+ import type { VercelGatewayClientConfig } from '../utils/client'
5
+ import type { VercelGatewayTextConfig } from './text'
6
+ import type { VercelGatewayResponsesTextConfig } from './responses-text'
7
+ import type { VercelGatewayChatModel } from '../model-meta'
8
+
9
+ export type VercelGatewayTextApi = 'responses' | 'chat' | 'chat-completions'
10
+
11
+ /** Config for the branching factory's Responses mode (default, or api: 'responses'). */
12
+ export type VercelGatewayResponsesApiConfig = Omit<
13
+ VercelGatewayResponsesTextConfig,
14
+ 'apiKey'
15
+ > & {
16
+ api?: 'responses'
17
+ }
18
+
19
+ /** Config for the branching factory's Chat Completions mode (api required). */
20
+ export type VercelGatewayChatApiConfig = Omit<
21
+ VercelGatewayTextConfig,
22
+ 'apiKey'
23
+ > & {
24
+ api: 'chat' | 'chat-completions'
25
+ }
26
+
27
+ type AnyVercelGatewayTextAdapter<TModel extends VercelGatewayChatModel> =
28
+ | VercelGatewayResponsesTextAdapter<TModel>
29
+ | VercelGatewayTextAdapter<TModel>
30
+
31
+ function stripApi<T extends { api?: unknown }>(config: T): Omit<T, 'api'> {
32
+ const { api, ...rest } = config
33
+ void api
34
+ return rest
35
+ }
36
+
37
+ function build<TModel extends VercelGatewayChatModel>(
38
+ model: TModel,
39
+ config: VercelGatewayClientConfig & { api?: VercelGatewayTextApi },
40
+ ): AnyVercelGatewayTextAdapter<TModel> {
41
+ if (config.api === 'chat' || config.api === 'chat-completions') {
42
+ return new VercelGatewayTextAdapter(stripApi(config), model)
43
+ }
44
+ return new VercelGatewayResponsesTextAdapter(stripApi(config), model)
45
+ }
46
+
47
+ export function createVercelGatewayText<TModel extends VercelGatewayChatModel>(
48
+ model: TModel,
49
+ apiKey: string,
50
+ config?: VercelGatewayResponsesApiConfig,
51
+ ): VercelGatewayResponsesTextAdapter<TModel>
52
+ export function createVercelGatewayText<TModel extends VercelGatewayChatModel>(
53
+ model: TModel,
54
+ apiKey: string,
55
+ config: VercelGatewayChatApiConfig,
56
+ ): VercelGatewayTextAdapter<TModel>
57
+ export function createVercelGatewayText<TModel extends VercelGatewayChatModel>(
58
+ model: TModel,
59
+ apiKey: string,
60
+ config?: VercelGatewayResponsesApiConfig | VercelGatewayChatApiConfig,
61
+ ): AnyVercelGatewayTextAdapter<TModel> {
62
+ return build(model, { ...config, apiKey })
63
+ }
64
+
65
+ export function vercelGatewayText<TModel extends VercelGatewayChatModel>(
66
+ model: TModel,
67
+ config?: VercelGatewayResponsesApiConfig,
68
+ ): VercelGatewayResponsesTextAdapter<TModel>
69
+ export function vercelGatewayText<TModel extends VercelGatewayChatModel>(
70
+ model: TModel,
71
+ config: VercelGatewayChatApiConfig,
72
+ ): VercelGatewayTextAdapter<TModel>
73
+ export function vercelGatewayText<TModel extends VercelGatewayChatModel>(
74
+ model: TModel,
75
+ config?: VercelGatewayResponsesApiConfig | VercelGatewayChatApiConfig,
76
+ ): AnyVercelGatewayTextAdapter<TModel> {
77
+ return build(model, {
78
+ ...config,
79
+ apiKey: getVercelGatewayApiKeyFromEnv(),
80
+ })
81
+ }
@@ -0,0 +1,156 @@
1
+ import OpenAI from 'openai'
2
+ import { resolveMediaPrompt } from '@tanstack/ai'
3
+ import { BaseImageAdapter } from '@tanstack/ai/adapters'
4
+ import { toRunErrorPayload } from '@tanstack/ai/adapter-internals'
5
+ import { buildImagesUsage } from '@tanstack/openai-base'
6
+ import { generateId } from '@tanstack/ai-utils'
7
+ import {
8
+ getVercelGatewayApiKeyFromEnv,
9
+ withVercelGatewayDefaults,
10
+ } from '../utils/client'
11
+ import { mapGatewayModelOptions } from '../utils/map-gateway-options'
12
+ import type {
13
+ GeneratedImage,
14
+ ImageGenerationOptions,
15
+ ImageGenerationResult,
16
+ } from '@tanstack/ai'
17
+ import type OpenAI_SDK from 'openai'
18
+ import type {
19
+ VercelGatewayImageModel,
20
+ VercelGatewayImageModelInputModalitiesByName,
21
+ VercelGatewayImageModelProviderOptionsByName,
22
+ VercelGatewayImageModelSizeByName,
23
+ } from '../model-meta'
24
+ import type { VercelGatewayImageProviderOptions } from '../image/image-provider-options'
25
+ import type { VercelGatewayClientConfig } from '../utils/client'
26
+
27
+ export interface VercelGatewayImageConfig extends VercelGatewayClientConfig {}
28
+
29
+ /**
30
+ * Vercel AI Gateway image adapter.
31
+ *
32
+ * Text-to-image only via `POST /v1/images/generations`.
33
+ */
34
+ export class VercelGatewayImageAdapter<
35
+ TModel extends VercelGatewayImageModel,
36
+ > extends BaseImageAdapter<
37
+ TModel,
38
+ VercelGatewayImageProviderOptions,
39
+ VercelGatewayImageModelProviderOptionsByName,
40
+ VercelGatewayImageModelSizeByName,
41
+ VercelGatewayImageModelInputModalitiesByName
42
+ > {
43
+ override readonly kind = 'image' as const
44
+ readonly name = 'vercel-gateway' as const
45
+
46
+ protected client: OpenAI
47
+
48
+ constructor(config: VercelGatewayImageConfig, model: TModel) {
49
+ super(model, {})
50
+ this.client = new OpenAI(withVercelGatewayDefaults(config))
51
+ }
52
+
53
+ async generateImages(
54
+ options: ImageGenerationOptions<VercelGatewayImageProviderOptions>,
55
+ ): Promise<ImageGenerationResult> {
56
+ const { model, numberOfImages, size, logger } = options
57
+ const resolved = resolveMediaPrompt(options.prompt)
58
+ const prompt = resolved.text
59
+
60
+ if (resolved.videos.length > 0) {
61
+ throw new Error(
62
+ `${this.name}.generateImages does not support video prompt parts (model: ${model}).`,
63
+ )
64
+ }
65
+ if (resolved.audios.length > 0) {
66
+ throw new Error(
67
+ `${this.name}.generateImages does not support audio prompt parts (model: ${model}).`,
68
+ )
69
+ }
70
+ if (resolved.images.length > 0) {
71
+ throw new Error(
72
+ `${this.name}.generateImages does not support image edits in v1 (model: ${model}).`,
73
+ )
74
+ }
75
+
76
+ const mapped = mapGatewayModelOptions(
77
+ options.modelOptions as Record<string, unknown> | undefined,
78
+ )
79
+ const request: OpenAI_SDK.Images.ImageGenerateParams = {
80
+ ...mapped,
81
+ model,
82
+ prompt,
83
+ n: numberOfImages ?? 1,
84
+ }
85
+ if (size !== undefined) {
86
+ request.size = size
87
+ }
88
+
89
+ try {
90
+ logger.request(
91
+ `activity=image provider=${this.name} model=${model} n=${request.n ?? 1} size=${request.size ?? 'default'}`,
92
+ { provider: this.name, model },
93
+ )
94
+ const response = await this.client.images.generate({
95
+ ...request,
96
+ stream: false,
97
+ })
98
+
99
+ const images: Array<GeneratedImage> = (response.data ?? []).flatMap(
100
+ (item): Array<GeneratedImage> => {
101
+ const revisedPromptField =
102
+ item.revised_prompt !== undefined
103
+ ? { revisedPrompt: item.revised_prompt }
104
+ : {}
105
+ if (item.b64_json) {
106
+ return [{ b64Json: item.b64_json, ...revisedPromptField }]
107
+ }
108
+ if (item.url) {
109
+ return [{ url: item.url, ...revisedPromptField }]
110
+ }
111
+ return []
112
+ },
113
+ )
114
+
115
+ if (images.length === 0) {
116
+ throw new Error(`${this.name}: image response contained no images`)
117
+ }
118
+
119
+ const usage = buildImagesUsage(response.usage)
120
+
121
+ return {
122
+ id: generateId(this.name),
123
+ model,
124
+ images,
125
+ ...(usage ? { usage } : {}),
126
+ }
127
+ } catch (error: unknown) {
128
+ logger.errors(`${this.name}.generateImages fatal`, {
129
+ error: toRunErrorPayload(error, `${this.name}.generateImages failed`),
130
+ source: `${this.name}.generateImages`,
131
+ })
132
+ throw error
133
+ }
134
+ }
135
+ }
136
+
137
+ export function createVercelGatewayImage<
138
+ TModel extends VercelGatewayImageModel,
139
+ >(
140
+ model: TModel,
141
+ apiKey: string,
142
+ config?: Omit<VercelGatewayImageConfig, 'apiKey'>,
143
+ ): VercelGatewayImageAdapter<TModel> {
144
+ return new VercelGatewayImageAdapter({ apiKey, ...config }, model)
145
+ }
146
+
147
+ export function vercelGatewayImage<TModel extends VercelGatewayImageModel>(
148
+ model: TModel,
149
+ config?: Omit<VercelGatewayImageConfig, 'apiKey'>,
150
+ ): VercelGatewayImageAdapter<TModel> {
151
+ return createVercelGatewayImage(
152
+ model,
153
+ getVercelGatewayApiKeyFromEnv(),
154
+ config,
155
+ )
156
+ }