@tanstack/ai
Version:
Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.
126 lines (119 loc) • 4.18 kB
text/typescript
import { isProviderId, resolveProviderId } from './providers'
import type { ByokProvider } from './define-provider'
import type { ProviderId } from './providers'
const KEYED_ADAPTER = Symbol.for('tanstack.ai.keyedAdapter')
/**
* An adapter that needs a provider key before it exists. Make one with
* {@link keyedAdapter}. A host finds the key and calls `create` just before a
* call.
*/
export interface KeyedAdapter<TAdapter> {
readonly [KEYED_ADAPTER]: true
/** The provider whose key `create` needs. */
readonly provider: ByokProvider | ProviderId
/** Builds the adapter from the key. */
readonly create: (key: string) => TAdapter
}
/**
* Wrap an adapter factory that needs a provider key. The host finds the key
* and calls `create` just before each call, so the key is not in your code
* or in a `.env` file. Works for every adapter kind: text, image, speech,
* audio, video, and the rest.
*
* @param provider - The provider whose key `create` needs: a BYOK descriptor
* such as `openaiByok` (its `env` names are the fallback), or a provider id.
* @param create - Builds the adapter from the key.
* @throws Error when the provider id is not a valid BYOK slug.
*
* @example
* ```ts
* import { createOpenaiChat } from '@tanstack/ai-openai'
* import { openaiByok } from '@tanstack/ai-openai/byok'
*
* const adapter = keyedAdapter(openaiByok, (key) =>
* createOpenaiChat('gpt-5.5', key),
* )
* // In a server route, with `getByokKey` from `@tanstack/ai/byok/server`:
* const key = getByokKey(request, adapter.provider)
* if (key) chat({ adapter: adapter.create(key), messages })
* ```
*/
export function keyedAdapter<TAdapter>(
provider: ByokProvider | ProviderId,
create: (key: string) => TAdapter,
) {
const id = resolveProviderId(provider)
if (!isProviderId(id)) {
throw new Error(`Invalid BYOK provider id: ${id}`)
}
const keyed: KeyedAdapter<TAdapter> = {
[KEYED_ADAPTER]: true,
provider,
create,
}
return keyed
}
/**
* True when `value` came from {@link keyedAdapter}. A host uses it to decide
* if an adapter needs a key first.
*/
export function isKeyedAdapter<TAdapter>(
value: TAdapter | KeyedAdapter<TAdapter>,
): value is KeyedAdapter<TAdapter> {
return typeof value === 'object' && value !== null && KEYED_ADAPTER in value
}
/** The adapter type that a {@link KeyedAdapter} builds. */
export type KeyedAdapterResult<T> = T extends KeyedAdapter<infer A> ? A : never
/**
* Keyed adapters for several providers in one map. The map key is the
* provider id, so a key goes only to the factory of its own provider. An
* entry is a factory (the key comes only from the `x-byok-<id>` header), or a
* {@link keyedAdapter} made with a BYOK descriptor (its `env` names are the
* fallback). Pick one per request with `keyedAdapterFromRequest` from
* `@tanstack/ai/byok/server`.
*
* @throws Error when a key is not a valid provider id, or when a keyed adapter
* entry is for a different provider than its key.
*
* @example
* ```ts
* const models = keyedAdapters({
* openai: (key) => createOpenaiChat('gpt-6.1-sol', key),
* anthropic: keyedAdapter(anthropicByok, (key) =>
* createAnthropicChat('claude-sonnet-5-5', key),
* ),
* })
* ```
*/
export function keyedAdapters<
const TMap extends Record<
ProviderId,
KeyedAdapter<unknown> | ((key: string) => unknown)
>,
>(
map: TMap,
): {
[K in keyof TMap]: TMap[K] extends KeyedAdapter<infer A>
? KeyedAdapter<A>
: TMap[K] extends (key: string) => infer A
? KeyedAdapter<A>
: never
} {
const entries = Object.entries(map).map(([id, entry]) => {
if (!isKeyedAdapter(entry)) return [id, keyedAdapter(id, entry)] as const
const entryId = resolveProviderId(entry.provider)
if (entryId !== id) {
throw new Error(
`keyedAdapters: the "${id}" entry is a keyed adapter for "${entryId}"`,
)
}
return [id, entry] as const
})
return Object.fromEntries(entries) as {
[K in keyof TMap]: TMap[K] extends KeyedAdapter<infer A>
? KeyedAdapter<A>
: TMap[K] extends (key: string) => infer A
? KeyedAdapter<A>
: never
}
}