@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.
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/esm/adapters/embedding.d.ts +21 -0
- package/dist/esm/adapters/embedding.js +73 -0
- package/dist/esm/adapters/embedding.js.map +1 -0
- package/dist/esm/adapters/factory.d.ts +16 -0
- package/dist/esm/adapters/factory.js +28 -0
- package/dist/esm/adapters/factory.js.map +1 -0
- package/dist/esm/adapters/image.d.ts +22 -0
- package/dist/esm/adapters/image.js +87 -0
- package/dist/esm/adapters/image.js.map +1 -0
- package/dist/esm/adapters/responses-text.d.ts +26 -0
- package/dist/esm/adapters/responses-text.js +38 -0
- package/dist/esm/adapters/responses-text.js.map +1 -0
- package/dist/esm/adapters/summarize.d.ts +9 -0
- package/dist/esm/adapters/summarize.js +17 -0
- package/dist/esm/adapters/summarize.js.map +1 -0
- package/dist/esm/adapters/text.d.ts +60 -0
- package/dist/esm/adapters/text.js +29 -0
- package/dist/esm/adapters/text.js.map +1 -0
- package/dist/esm/embedding/embedding-provider-options.d.ts +5 -0
- package/dist/esm/image/image-provider-options.d.ts +7 -0
- package/dist/esm/index.d.ts +17 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/message-types.d.ts +44 -0
- package/dist/esm/model-meta.d.ts +493 -0
- package/dist/esm/model-meta.js +351 -0
- package/dist/esm/model-meta.js.map +1 -0
- package/dist/esm/text/responses-provider-options.d.ts +3 -0
- package/dist/esm/text/text-provider-options.d.ts +40 -0
- package/dist/esm/tools/function-tool.d.ts +16 -0
- package/dist/esm/tools/function-tool.js +30 -0
- package/dist/esm/tools/function-tool.js.map +1 -0
- package/dist/esm/tools/index.d.ts +2 -0
- package/dist/esm/tools/index.js +3 -0
- package/dist/esm/tools/tool-converter.d.ts +7 -0
- package/dist/esm/tools/tool-converter.js +15 -0
- package/dist/esm/tools/tool-converter.js.map +1 -0
- package/dist/esm/utils/client.d.ts +8 -0
- package/dist/esm/utils/client.js +29 -0
- package/dist/esm/utils/client.js.map +1 -0
- package/dist/esm/utils/map-gateway-options.d.ts +4 -0
- package/dist/esm/utils/map-gateway-options.js +14 -0
- package/dist/esm/utils/map-gateway-options.js.map +1 -0
- package/package.json +73 -0
- package/src/adapters/embedding.ts +122 -0
- package/src/adapters/factory.ts +81 -0
- package/src/adapters/image.ts +156 -0
- package/src/adapters/responses-text.ts +99 -0
- package/src/adapters/summarize.ts +44 -0
- package/src/adapters/text.ts +68 -0
- package/src/embedding/embedding-provider-options.ts +6 -0
- package/src/image/image-provider-options.ts +13 -0
- package/src/index.ts +78 -0
- package/src/message-types.ts +45 -0
- package/src/model-meta.ts +2512 -0
- package/src/text/responses-provider-options.ts +10 -0
- package/src/text/text-provider-options.ts +47 -0
- package/src/tools/function-tool.ts +46 -0
- package/src/tools/index.ts +5 -0
- package/src/tools/tool-converter.ts +15 -0
- package/src/utils/client.ts +40 -0
- 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,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,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
|
+
}
|