From 870846afbab52cfdfee609459d6db4ba4a3622d2 Mon Sep 17 00:00:00 2001 From: yardend Date: Mon, 21 Sep 2026 23:20:02 +0300 Subject: [PATCH 1/2] feat(ai-gateway): add typesafe provider selection --- .../types-to-expose.json | 1 + src/index.ts | 1 + src/modules/ai-gateway.ts | 23 +++++++-- src/modules/ai-gateway.types.ts | 35 ++++++++----- tests/types/ai-gateway.types.ts | 14 +++++ tests/unit/ai-gateway.test.ts | 51 +++++++++++++++---- 6 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 tests/types/ai-gateway.types.ts diff --git a/scripts/mintlify-post-processing/types-to-expose.json b/scripts/mintlify-post-processing/types-to-expose.json index 5d7ef016..d0a74828 100644 --- a/scripts/mintlify-post-processing/types-to-expose.json +++ b/scripts/mintlify-post-processing/types-to-expose.json @@ -3,6 +3,7 @@ "AgentNameRegistry", "AgentsModule", "AiGatewayConnection", + "AiGatewayConnectionOptions", "AiGatewayModule", "AnalyticsModule", "AppLogsModule", diff --git a/src/index.ts b/src/index.ts index 8842b8fe..6e1c4a01 100644 --- a/src/index.ts +++ b/src/index.ts @@ -105,6 +105,7 @@ export type { export type { AiGatewayModule, AiGatewayConnection, + AiGatewayConnectionOptions, } from "./modules/ai-gateway.types.js"; export type { AppLogsModule } from "./modules/app-logs.types.js"; diff --git a/src/modules/ai-gateway.ts b/src/modules/ai-gateway.ts index 30939603..b7d37895 100644 --- a/src/modules/ai-gateway.ts +++ b/src/modules/ai-gateway.ts @@ -3,17 +3,32 @@ import { AiGatewayModule, AiGatewayModuleConfig, AiGatewayConnection, + AiGatewayConnectionOptions, } from "./ai-gateway.types.js"; +const PROVIDER_PATHS = { + openai: "openai", + typesafe: "typesafe", +} as const; + export function createAiGatewayModule({ serverUrl, token, appId, }: AiGatewayModuleConfig): AiGatewayModule { - const connection = (): AiGatewayConnection => ({ - baseURL: `${serverUrl}/api/apps/${appId}/ai/openai/v1`, - token: token ?? getAccessToken() ?? "", - }); + const connection = ( + { provider = "openai" }: AiGatewayConnectionOptions = {} + ): AiGatewayConnection => { + if (!Object.prototype.hasOwnProperty.call(PROVIDER_PATHS, provider)) { + throw new Error(`Unsupported AI Gateway provider: ${provider}`); + } + const providerPath = PROVIDER_PATHS[provider]; + + return { + baseURL: `${serverUrl}/api/apps/${appId}/ai/${providerPath}/v1`, + token: token ?? getAccessToken() ?? "", + }; + }; return { connection, diff --git a/src/modules/ai-gateway.types.ts b/src/modules/ai-gateway.types.ts index c94fac71..760ee82f 100644 --- a/src/modules/ai-gateway.types.ts +++ b/src/modules/ai-gateway.types.ts @@ -2,7 +2,7 @@ * Connection details for the Base44 AI Gateway. */ export interface AiGatewayConnection { - /** Base URL of the gateway's OpenAI-compatible Chat Completions endpoint. */ + /** Base URL of the selected gateway provider. */ baseURL: string; /** * Bearer token that authenticates the request. Empty string when the caller is @@ -11,6 +11,12 @@ export interface AiGatewayConnection { token: string; } +/** Options for selecting an AI Gateway provider. */ +export interface AiGatewayConnectionOptions { + /** Gateway provider to connect to. Defaults to `openai`. */ + provider?: "openai" | "typesafe"; +} + /** * Configuration for the AI Gateway module. * @internal @@ -28,13 +34,10 @@ export interface AiGatewayModuleConfig { * AI Gateway module for calling Base44's managed AI models from your own code. * * `connection()` hands you a `baseURL` and `token` that authenticate as your - * Base44 app. An OpenAI-compatible client is any library, such as the `openai` - * SDK or the Vercel AI SDK, that has the same request and response format - * as OpenAI's Chat Completions API and lets you point it at a custom `baseURL` - * instead of OpenAI's own servers. Pass `connection()`'s values to one of - * these clients and it works against Base44's gateway exactly as it would - * against the provider directly, no separate account, API key, or billing - * setup with the underlying model provider required. + * Base44 app. It defaults to the OpenAI-compatible Chat Completions provider; + * pass `{ provider: "typesafe" }` for the TypeSafe evaluation provider. Pass + * the returned values to the corresponding client with no separate provider + * account, API key, or billing setup. * * Call `connection()` from a backend function rather than the browser. This * keeps your instructions, tools, and business logic server-side, and lets @@ -42,7 +45,13 @@ export interface AiGatewayModuleConfig { * `token` it returns is the caller's regular session token, the same one * used for every other SDK call. * - * ## Models + * ## Providers + * + * - **OpenAI-compatible** (default): Works with clients such as the `openai` + * SDK or Vercel AI SDK clients that accept a custom `baseURL`. + * - **TypeSafe**: Works with `@ai-sdk/typesafe-ai` for structured evaluations. + * + * ## OpenAI-compatible models * * You can use any of the [models available through `InvokeLLM`](/developers/references/sdk/docs/type-aliases/integrations#invokellm). * Pass `'automatic'` to let Base44 choose one, or pin a specific model such @@ -71,13 +80,15 @@ export interface AiGatewayModule { /** * Gets the connection details for the Base44 AI Gateway. * - * Returns the `baseURL` and `token` to pass to any OpenAI-compatible client. + * Returns the `baseURL` and `token` to pass to the selected provider client. + * + * @param options - Provider selection. Omit it to use the OpenAI-compatible gateway. * * @returns The gateway {@linkcode AiGatewayConnection | connection} (`baseURL` and `token`). * * @example * ```typescript - * // Call a model directly + * // Call an OpenAI-compatible model directly * import { createClientFromRequest } from "@base44/sdk"; * import OpenAI from "openai"; * @@ -137,5 +148,5 @@ export interface AiGatewayModule { * await agent.generate({ prompt: `Review this return request: ${JSON.stringify(returnRequest)}` }); * ``` */ - connection(): AiGatewayConnection; + connection(options?: AiGatewayConnectionOptions): AiGatewayConnection; } diff --git a/tests/types/ai-gateway.types.ts b/tests/types/ai-gateway.types.ts new file mode 100644 index 00000000..9bb22023 --- /dev/null +++ b/tests/types/ai-gateway.types.ts @@ -0,0 +1,14 @@ +import type { + AiGatewayConnectionOptions, + AiGatewayModule, +} from "../../src/index.js"; + +declare const aiGateway: AiGatewayModule; + +const options: AiGatewayConnectionOptions = { provider: "typesafe" }; +aiGateway.connection(options); +aiGateway.connection(); +aiGateway.connection({ provider: "openai" }); + +// @ts-expect-error Only gateway providers exposed by the SDK are accepted. +aiGateway.connection({ provider: "unsupported" }); diff --git a/tests/unit/ai-gateway.test.ts b/tests/unit/ai-gateway.test.ts index 3d36bc94..9aae215f 100644 --- a/tests/unit/ai-gateway.test.ts +++ b/tests/unit/ai-gateway.test.ts @@ -5,11 +5,29 @@ describe("AI Gateway Module", () => { const appId = "test-app-id"; const serverUrl = "https://api.base44.com"; const baseURL = `${serverUrl}/api/apps/${appId}/ai/openai/v1`; + const typesafeBaseURL = `${serverUrl}/api/apps/${appId}/ai/typesafe/v1`; describe("connection", () => { - test("should return the OpenAI-compatible gateway baseURL", () => { + test("should default to OpenAI and accept it explicitly", () => { const base44 = createClient({ serverUrl, appId }); expect(base44.aiGateway.connection().baseURL).toBe(baseURL); + expect( + base44.aiGateway.connection({ provider: "openai" }).baseURL + ).toBe(baseURL); + }); + + test("should return the TypeSafe gateway baseURL", () => { + const base44 = createClient({ serverUrl, appId }); + expect( + base44.aiGateway.connection({ provider: "typesafe" }).baseURL + ).toBe(typesafeBaseURL); + }); + + test("should reject unsupported providers", () => { + const base44 = createClient({ serverUrl, appId }); + expect(() => + base44.aiGateway.connection({ provider: "unsupported" } as never) + ).toThrow("Unsupported AI Gateway provider: unsupported"); }); test("should return an empty token when unauthenticated", () => { @@ -17,38 +35,49 @@ describe("AI Gateway Module", () => { expect(base44.aiGateway.connection().token).toBe(""); }); - test("should use the user token when authenticated", () => { + test("should use the user token for TypeSafe when authenticated", () => { const base44 = createClient({ serverUrl, appId, token: "user-token" }); - expect(base44.aiGateway.connection()).toEqual({ - baseURL, + expect(base44.aiGateway.connection({ provider: "typesafe" })).toEqual({ + baseURL: typesafeBaseURL, token: "user-token", }); }); - test("should build from the Base44-Api-Url header in backend functions", () => { + test("should propagate the backend request host and both authentication modes", () => { + const backendServerUrl = "https://backend.example.com"; + const backendTypesafeBaseURL = `${backendServerUrl}/api/apps/${appId}/ai/typesafe/v1`; const request = new Request("https://functions.internal/run", { headers: { "Base44-App-Id": appId, - "Base44-Api-Url": serverUrl, + "Base44-Api-Url": backendServerUrl, Authorization: "Bearer user-token", + "Base44-Service-Authorization": "Bearer service-token", }, }); const base44 = createClientFromRequest(request); - expect(base44.aiGateway.connection()).toEqual({ - baseURL, + expect(base44.aiGateway.connection({ provider: "typesafe" })).toEqual({ + baseURL: backendTypesafeBaseURL, token: "user-token", }); + expect( + base44.asServiceRole.aiGateway.connection({ provider: "typesafe" }) + ).toEqual({ + baseURL: backendTypesafeBaseURL, + token: "service-token", + }); }); - test("should use the service-role token via asServiceRole", () => { + test("should use the service-role token for TypeSafe via asServiceRole", () => { const base44 = createClient({ serverUrl, appId, token: "user-token", serviceToken: "service-token", }); - expect(base44.asServiceRole.aiGateway.connection()).toEqual({ - baseURL, + expect( + base44.asServiceRole.aiGateway.connection({ provider: "typesafe" }) + ).toEqual({ + baseURL: typesafeBaseURL, token: "service-token", }); }); From c8ee0a8ac68675068bb7ce29ae65cffd9d28c887 Mon Sep 17 00:00:00 2001 From: yardend Date: Wed, 23 Sep 2026 11:39:59 +0300 Subject: [PATCH 2/2] docs(ai-gateway): preserve existing connection guidance --- src/modules/ai-gateway.types.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/modules/ai-gateway.types.ts b/src/modules/ai-gateway.types.ts index 760ee82f..d400a91b 100644 --- a/src/modules/ai-gateway.types.ts +++ b/src/modules/ai-gateway.types.ts @@ -34,10 +34,17 @@ export interface AiGatewayModuleConfig { * AI Gateway module for calling Base44's managed AI models from your own code. * * `connection()` hands you a `baseURL` and `token` that authenticate as your - * Base44 app. It defaults to the OpenAI-compatible Chat Completions provider; - * pass `{ provider: "typesafe" }` for the TypeSafe evaluation provider. Pass - * the returned values to the corresponding client with no separate provider - * account, API key, or billing setup. + * Base44 app. An OpenAI-compatible client is any library, such as the `openai` + * SDK or the Vercel AI SDK, that has the same request and response format + * as OpenAI's Chat Completions API and lets you point it at a custom `baseURL` + * instead of OpenAI's own servers. Pass `connection()`'s values to one of + * these clients and it works against Base44's gateway exactly as it would + * against the provider directly, no separate account, API key, or billing + * setup with the underlying model provider required. + * + * By default, `connection()` uses the OpenAI-compatible provider. Pass + * `{ provider: "typesafe" }` to connect to TypeSafe for structured evaluations + * with `@ai-sdk/typesafe-ai`. * * Call `connection()` from a backend function rather than the browser. This * keeps your instructions, tools, and business logic server-side, and lets @@ -45,12 +52,6 @@ export interface AiGatewayModuleConfig { * `token` it returns is the caller's regular session token, the same one * used for every other SDK call. * - * ## Providers - * - * - **OpenAI-compatible** (default): Works with clients such as the `openai` - * SDK or Vercel AI SDK clients that accept a custom `baseURL`. - * - **TypeSafe**: Works with `@ai-sdk/typesafe-ai` for structured evaluations. - * * ## OpenAI-compatible models * * You can use any of the [models available through `InvokeLLM`](/developers/references/sdk/docs/type-aliases/integrations#invokellm). @@ -88,7 +89,7 @@ export interface AiGatewayModule { * * @example * ```typescript - * // Call an OpenAI-compatible model directly + * // Call a model directly * import { createClientFromRequest } from "@base44/sdk"; * import OpenAI from "openai"; *