@tanstack/ai 0.25.0 → 0.26.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.
@@ -13,13 +13,24 @@ import { Modality } from './types.js';
13
13
  * ] as const
14
14
  * ```
15
15
  */
16
- export interface ExtendedModelDef<TName extends string = string, TInput extends ReadonlyArray<Modality> = ReadonlyArray<Modality>, TOptions = unknown> {
16
+ export interface ExtendedModelDef<TName extends string = string, TInput extends ReadonlyArray<Modality> = ReadonlyArray<Modality>, TOptions = unknown, TFeatures extends ReadonlyArray<string> = ReadonlyArray<string>, TTools extends ReadonlyArray<string> = ReadonlyArray<string>> {
17
17
  /** The model name identifier */
18
18
  name: TName;
19
19
  /** Supported input modalities for this model */
20
20
  input: TInput;
21
21
  /** Type brand for provider options - use `{} as YourOptionsType` */
22
22
  modelOptions: TOptions;
23
+ /** Optional declared features (e.g. 'reasoning', 'structured_outputs') */
24
+ features?: TFeatures;
25
+ /** Optional declared provider tools (e.g. 'web_search') */
26
+ tools?: TTools;
27
+ }
28
+ /** Capability bag accepted by the object form of `createModel`. */
29
+ export interface ModelCapabilities<TInput extends ReadonlyArray<Modality> = ReadonlyArray<Modality>, TFeatures extends ReadonlyArray<string> = ReadonlyArray<string>, TTools extends ReadonlyArray<string> = ReadonlyArray<string>, TOptions = unknown> {
30
+ input?: TInput;
31
+ features?: TFeatures;
32
+ tools?: TTools;
33
+ modelOptions?: TOptions;
23
34
  }
24
35
  /**
25
36
  * Creates a custom model definition for use with `extendAdapter`.
@@ -47,8 +58,19 @@ export interface ExtendedModelDef<TName extends string = string, TInput extends
47
58
  *
48
59
  * const myOpenai = extendAdapter(openaiText, customModels)
49
60
  * ```
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * // Capabilities object form - declare features and provider tools
65
+ * const reasoner = createModel('reasoner', {
66
+ * input: ['text'],
67
+ * features: ['reasoning', 'structured_outputs'],
68
+ * tools: ['web_search'],
69
+ * })
70
+ * ```
50
71
  */
51
72
  export declare function createModel<const TName extends string, const TInput extends ReadonlyArray<Modality>>(name: TName, input: TInput): ExtendedModelDef<TName, TInput>;
73
+ export declare function createModel<const TName extends string, const TCaps extends ModelCapabilities>(name: TName, capabilities: TCaps): ExtendedModelDef<TName, TCaps['input'] extends ReadonlyArray<Modality> ? TCaps['input'] : ReadonlyArray<Modality>, TCaps['modelOptions'], TCaps['features'] extends ReadonlyArray<string> ? TCaps['features'] : ReadonlyArray<string>, TCaps['tools'] extends ReadonlyArray<string> ? TCaps['tools'] : ReadonlyArray<string>>;
52
74
  /**
53
75
  * Extract the model name union from an array of model definitions.
54
76
  */
@@ -1,8 +1,14 @@
1
- function createModel(name, input) {
1
+ function createModel(name, second) {
2
+ if (Array.isArray(second)) {
3
+ return { name, input: second, modelOptions: {} };
4
+ }
5
+ const caps = second;
2
6
  return {
3
7
  name,
4
- input,
5
- modelOptions: {}
8
+ input: caps.input ?? ["text"],
9
+ modelOptions: caps.modelOptions ?? {},
10
+ features: caps.features,
11
+ tools: caps.tools
6
12
  };
7
13
  }
8
14
  function extendAdapter(factory, _customModels) {
@@ -1 +1 @@
1
- {"version":3,"file":"extend-adapter.js","sources":["../../src/extend-adapter.ts"],"sourcesContent":["import type { Modality } from './types'\n\n// ===========================\n// Extended Model Definition\n// ===========================\n\n/**\n * Definition for a custom model to add to an adapter.\n *\n * @template TName - The model name as a literal string type\n * @template TInput - Array of supported input modalities\n * @template TOptions - Provider options type for this model\n *\n * @example\n * ```typescript\n * const customModels = [\n * createModel('my-custom-model', ['text', 'image']),\n * ] as const\n * ```\n */\nexport interface ExtendedModelDef<\n TName extends string = string,\n TInput extends ReadonlyArray<Modality> = ReadonlyArray<Modality>,\n TOptions = unknown,\n> {\n /** The model name identifier */\n name: TName\n /** Supported input modalities for this model */\n input: TInput\n /** Type brand for provider options - use `{} as YourOptionsType` */\n modelOptions: TOptions\n}\n\n/**\n * Creates a custom model definition for use with `extendAdapter`.\n *\n * This is a helper function that provides proper type inference without\n * requiring manual `as const` casts on individual properties.\n *\n * @template TName - The model name (inferred from argument)\n * @template TInput - The input modalities array (inferred from argument)\n *\n * @param name - The model name identifier (literal string)\n * @param input - Array of supported input modalities\n * @returns A properly typed model definition for use with `extendAdapter`\n *\n * @example\n * ```typescript\n * import { extendAdapter, createModel } from '@tanstack/ai'\n * import { openaiText } from '@tanstack/ai-openai'\n *\n * // Define custom models with full type inference\n * const customModels = [\n * createModel('my-fine-tuned-gpt4', ['text', 'image']),\n * createModel('local-llama', ['text']),\n * ] as const\n *\n * const myOpenai = extendAdapter(openaiText, customModels)\n * ```\n */\nexport function createModel<\n const TName extends string,\n const TInput extends ReadonlyArray<Modality>,\n>(name: TName, input: TInput): ExtendedModelDef<TName, TInput> {\n return {\n name,\n input,\n modelOptions: {},\n }\n}\n\n// ===========================\n// Type Extraction Utilities\n// ===========================\n\n/**\n * Extract the model name union from an array of model definitions.\n */\ntype ExtractCustomModelNames<TDefs extends ReadonlyArray<ExtendedModelDef>> =\n TDefs[number]['name']\n\n// ===========================\n// Factory Type Inference\n// ===========================\n\n/**\n * Infer the model parameter type from an adapter factory function.\n * For generic functions like `<T extends Union>(model: T)`, this gets `T` which\n * TypeScript treats as the constraint union when used in parameter position.\n */\ntype InferFactoryModels<TFactory> = TFactory extends (\n model: infer TModel,\n ...args: Array<any>\n) => any\n ? TModel extends string\n ? TModel\n : string\n : string\n\n/**\n * Infer the config parameter type from an adapter factory function.\n */\ntype InferConfig<TFactory> = TFactory extends (\n model: any,\n config?: infer TConfig,\n) => any\n ? TConfig\n : undefined\n\n/**\n * Infer the adapter return type from a factory function.\n */\ntype InferAdapterReturn<TFactory> = TFactory extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn\n : never\n\n// ===========================\n// extendAdapter Function\n// ===========================\n\n/**\n * Extends an existing adapter factory with additional custom models.\n *\n * The extended adapter accepts both original models (with full original type inference)\n * and custom models (with types from your definitions).\n *\n * At runtime, this simply passes through to the original factory - no validation is performed.\n * The original factory's signature is fully preserved, including any config parameters.\n *\n * @param factory - The original adapter factory function (e.g., `openaiText`, `anthropicText`)\n * @param models - Array of custom model definitions with `name` and `input`\n * @returns A new factory function that accepts both original and custom models\n *\n * @example\n * ```typescript\n * import { extendAdapter, createModel } from '@tanstack/ai'\n * import { openaiText } from '@tanstack/ai-openai'\n *\n * // Define custom models\n * const customModels = [\n * createModel('my-fine-tuned-gpt4', ['text', 'image']),\n * createModel('local-llama', ['text']),\n * ] as const\n *\n * // Create extended adapter\n * const myOpenai = extendAdapter(openaiText, customModels)\n *\n * // Use with original models - full type inference preserved\n * const gpt4 = myOpenai('gpt-4o')\n *\n * // Use with custom models\n * const custom = myOpenai('my-fine-tuned-gpt4')\n *\n * // Type error: 'invalid-model' is not a valid model\n * // myOpenai('invalid-model')\n *\n * // Works with chat()\n * chat({\n * adapter: myOpenai('my-fine-tuned-gpt4'),\n * messages: [...]\n * })\n * ```\n */\nexport function extendAdapter<\n TFactory extends (...args: Array<any>) => any,\n const TDefs extends ReadonlyArray<ExtendedModelDef>,\n>(\n factory: TFactory,\n _customModels: TDefs,\n): (\n model: InferFactoryModels<TFactory> | ExtractCustomModelNames<TDefs>,\n ...args: InferConfig<TFactory> extends undefined\n ? []\n : [config?: InferConfig<TFactory>]\n) => InferAdapterReturn<TFactory> {\n // At runtime, we simply pass through to the original factory.\n // The _customModels parameter is only used for type inference.\n // No runtime validation - users are trusted to pass valid model names.\n return factory as any\n}\n"],"names":[],"mappings":"AA4DO,SAAS,YAGd,MAAa,OAAgD;AAC7D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,cAAc,CAAA;AAAA,EAAC;AAEnB;AAgGO,SAAS,cAId,SACA,eAMgC;AAIhC,SAAO;AACT;"}
1
+ {"version":3,"file":"extend-adapter.js","sources":["../../src/extend-adapter.ts"],"sourcesContent":["import type { Modality } from './types'\n\n// ===========================\n// Extended Model Definition\n// ===========================\n\n/**\n * Definition for a custom model to add to an adapter.\n *\n * @template TName - The model name as a literal string type\n * @template TInput - Array of supported input modalities\n * @template TOptions - Provider options type for this model\n *\n * @example\n * ```typescript\n * const customModels = [\n * createModel('my-custom-model', ['text', 'image']),\n * ] as const\n * ```\n */\nexport interface ExtendedModelDef<\n TName extends string = string,\n TInput extends ReadonlyArray<Modality> = ReadonlyArray<Modality>,\n TOptions = unknown,\n TFeatures extends ReadonlyArray<string> = ReadonlyArray<string>,\n TTools extends ReadonlyArray<string> = ReadonlyArray<string>,\n> {\n /** The model name identifier */\n name: TName\n /** Supported input modalities for this model */\n input: TInput\n /** Type brand for provider options - use `{} as YourOptionsType` */\n modelOptions: TOptions\n /** Optional declared features (e.g. 'reasoning', 'structured_outputs') */\n features?: TFeatures\n /** Optional declared provider tools (e.g. 'web_search') */\n tools?: TTools\n}\n\n/** Capability bag accepted by the object form of `createModel`. */\nexport interface ModelCapabilities<\n TInput extends ReadonlyArray<Modality> = ReadonlyArray<Modality>,\n TFeatures extends ReadonlyArray<string> = ReadonlyArray<string>,\n TTools extends ReadonlyArray<string> = ReadonlyArray<string>,\n TOptions = unknown,\n> {\n input?: TInput\n features?: TFeatures\n tools?: TTools\n modelOptions?: TOptions\n}\n\n/**\n * Creates a custom model definition for use with `extendAdapter`.\n *\n * This is a helper function that provides proper type inference without\n * requiring manual `as const` casts on individual properties.\n *\n * @template TName - The model name (inferred from argument)\n * @template TInput - The input modalities array (inferred from argument)\n *\n * @param name - The model name identifier (literal string)\n * @param input - Array of supported input modalities\n * @returns A properly typed model definition for use with `extendAdapter`\n *\n * @example\n * ```typescript\n * import { extendAdapter, createModel } from '@tanstack/ai'\n * import { openaiText } from '@tanstack/ai-openai'\n *\n * // Define custom models with full type inference\n * const customModels = [\n * createModel('my-fine-tuned-gpt4', ['text', 'image']),\n * createModel('local-llama', ['text']),\n * ] as const\n *\n * const myOpenai = extendAdapter(openaiText, customModels)\n * ```\n *\n * @example\n * ```typescript\n * // Capabilities object form - declare features and provider tools\n * const reasoner = createModel('reasoner', {\n * input: ['text'],\n * features: ['reasoning', 'structured_outputs'],\n * tools: ['web_search'],\n * })\n * ```\n */\n// Overload 1 — legacy positional input array (unchanged behavior)\nexport function createModel<\n const TName extends string,\n const TInput extends ReadonlyArray<Modality>,\n>(name: TName, input: TInput): ExtendedModelDef<TName, TInput>\n// Overload 2 — capabilities object\nexport function createModel<\n const TName extends string,\n const TCaps extends ModelCapabilities,\n>(\n name: TName,\n capabilities: TCaps,\n): ExtendedModelDef<\n TName,\n TCaps['input'] extends ReadonlyArray<Modality>\n ? TCaps['input']\n : ReadonlyArray<Modality>,\n TCaps['modelOptions'],\n TCaps['features'] extends ReadonlyArray<string>\n ? TCaps['features']\n : ReadonlyArray<string>,\n TCaps['tools'] extends ReadonlyArray<string>\n ? TCaps['tools']\n : ReadonlyArray<string>\n>\n// Implementation\nexport function createModel(\n name: string,\n second: ReadonlyArray<Modality> | ModelCapabilities,\n): ExtendedModelDef {\n if (Array.isArray(second)) {\n return { name, input: second, modelOptions: {} }\n }\n const caps = second as ModelCapabilities\n return {\n name,\n input: caps.input ?? (['text'] as ReadonlyArray<Modality>),\n modelOptions: caps.modelOptions ?? {},\n features: caps.features,\n tools: caps.tools,\n }\n}\n\n// ===========================\n// Type Extraction Utilities\n// ===========================\n\n/**\n * Extract the model name union from an array of model definitions.\n */\ntype ExtractCustomModelNames<TDefs extends ReadonlyArray<ExtendedModelDef>> =\n TDefs[number]['name']\n\n// ===========================\n// Factory Type Inference\n// ===========================\n\n/**\n * Infer the model parameter type from an adapter factory function.\n * For generic functions like `<T extends Union>(model: T)`, this gets `T` which\n * TypeScript treats as the constraint union when used in parameter position.\n */\ntype InferFactoryModels<TFactory> = TFactory extends (\n model: infer TModel,\n ...args: Array<any>\n) => any\n ? TModel extends string\n ? TModel\n : string\n : string\n\n/**\n * Infer the config parameter type from an adapter factory function.\n */\ntype InferConfig<TFactory> = TFactory extends (\n model: any,\n config?: infer TConfig,\n) => any\n ? TConfig\n : undefined\n\n/**\n * Infer the adapter return type from a factory function.\n */\ntype InferAdapterReturn<TFactory> = TFactory extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn\n : never\n\n// ===========================\n// extendAdapter Function\n// ===========================\n\n/**\n * Extends an existing adapter factory with additional custom models.\n *\n * The extended adapter accepts both original models (with full original type inference)\n * and custom models (with types from your definitions).\n *\n * At runtime, this simply passes through to the original factory - no validation is performed.\n * The original factory's signature is fully preserved, including any config parameters.\n *\n * @param factory - The original adapter factory function (e.g., `openaiText`, `anthropicText`)\n * @param models - Array of custom model definitions with `name` and `input`\n * @returns A new factory function that accepts both original and custom models\n *\n * @example\n * ```typescript\n * import { extendAdapter, createModel } from '@tanstack/ai'\n * import { openaiText } from '@tanstack/ai-openai'\n *\n * // Define custom models\n * const customModels = [\n * createModel('my-fine-tuned-gpt4', ['text', 'image']),\n * createModel('local-llama', ['text']),\n * ] as const\n *\n * // Create extended adapter\n * const myOpenai = extendAdapter(openaiText, customModels)\n *\n * // Use with original models - full type inference preserved\n * const gpt4 = myOpenai('gpt-4o')\n *\n * // Use with custom models\n * const custom = myOpenai('my-fine-tuned-gpt4')\n *\n * // Type error: 'invalid-model' is not a valid model\n * // myOpenai('invalid-model')\n *\n * // Works with chat()\n * chat({\n * adapter: myOpenai('my-fine-tuned-gpt4'),\n * messages: [...]\n * })\n * ```\n */\nexport function extendAdapter<\n TFactory extends (...args: Array<any>) => any,\n const TDefs extends ReadonlyArray<ExtendedModelDef>,\n>(\n factory: TFactory,\n _customModels: TDefs,\n): (\n model: InferFactoryModels<TFactory> | ExtractCustomModelNames<TDefs>,\n ...args: InferConfig<TFactory> extends undefined\n ? []\n : [config?: InferConfig<TFactory>]\n) => InferAdapterReturn<TFactory> {\n // At runtime, we simply pass through to the original factory.\n // The _customModels parameter is only used for type inference.\n // No runtime validation - users are trusted to pass valid model names.\n return factory as any\n}\n"],"names":[],"mappings":"AAmHO,SAAS,YACd,MACA,QACkB;AAClB,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,WAAO,EAAE,MAAM,OAAO,QAAQ,cAAc,CAAA,EAAC;AAAA,EAC/C;AACA,QAAM,OAAO;AACb,SAAO;AAAA,IACL;AAAA,IACA,OAAO,KAAK,SAAU,CAAC,MAAM;AAAA,IAC7B,cAAc,KAAK,gBAAgB,CAAA;AAAA,IACnC,UAAU,KAAK;AAAA,IACf,OAAO,KAAK;AAAA,EAAA;AAEhB;AAgGO,SAAS,cAId,SACA,eAMgC;AAIhC,SAAO;AACT;"}
@@ -31,6 +31,6 @@ export { uiMessagesToWire } from './utilities/ag-ui-wire.js';
31
31
  export type { WireMessage } from './utilities/ag-ui-wire.js';
32
32
  export { isContentPart, isContentPartArray, normalizeToolResult, } from './utilities/tool-result.js';
33
33
  export { createModel, extendAdapter } from './extend-adapter.js';
34
- export type { ExtendedModelDef } from './extend-adapter.js';
34
+ export type { ExtendedModelDef, ModelCapabilities } from './extend-adapter.js';
35
35
  export type { Logger, DebugCategories, DebugConfig, DebugOption, } from './logger/types.js';
36
36
  export { ConsoleLogger } from './logger/console-logger.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.25.0",
3
+ "version": "0.26.1",
4
4
  "description": "Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -68,7 +68,7 @@
68
68
  "@ag-ui/core": "^0.0.52",
69
69
  "@standard-schema/spec": "^1.1.0",
70
70
  "partial-json": "^0.1.7",
71
- "@tanstack/ai-event-client": "0.5.0"
71
+ "@tanstack/ai-event-client": "0.5.2"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "@opentelemetry/api": ">=1.9.0"
@@ -2,9 +2,11 @@
2
2
  name: ai-core/adapter-configuration
3
3
  description: >
4
4
  Provider adapter selection and configuration: openaiText, anthropicText,
5
- geminiText, ollamaText, grokText, groqText, openRouterText. Per-model
5
+ geminiText, ollamaText, grokText, groqText, openRouterText, openaiCompatible. Per-model
6
6
  type safety with modelOptions, reasoning/thinking configuration,
7
7
  runtime adapter switching, extendAdapter() for custom models, createModel().
8
+ Generic OpenAI-compatible providers (DeepSeek, Together, Fireworks, etc.) via
9
+ openaiCompatible({ baseURL, apiKey, models }) from @tanstack/ai-openai/compatible.
8
10
  API key env vars: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY/GEMINI_API_KEY,
9
11
  XAI_API_KEY, GROQ_API_KEY, OPENROUTER_API_KEY, OLLAMA_HOST.
10
12
  type: sub-skill
@@ -60,15 +62,16 @@ into the factory, not into `chat()`.
60
62
  Each provider has a dedicated package with tree-shakeable adapter factories.
61
63
  The text adapter is the primary one for chat/completions:
62
64
 
63
- | Provider | Package | Factory | Env Var |
64
- | ---------- | ------------------------- | ---------------- | ------------------------------------------------- |
65
- | OpenAI | `@tanstack/ai-openai` | `openaiText` | `OPENAI_API_KEY` |
66
- | Anthropic | `@tanstack/ai-anthropic` | `anthropicText` | `ANTHROPIC_API_KEY` |
67
- | Gemini | `@tanstack/ai-gemini` | `geminiText` | `GOOGLE_API_KEY` or `GEMINI_API_KEY` |
68
- | Grok (xAI) | `@tanstack/ai-grok` | `grokText` | `XAI_API_KEY` |
69
- | Groq | `@tanstack/ai-groq` | `groqText` | `GROQ_API_KEY` |
70
- | OpenRouter | `@tanstack/ai-openrouter` | `openRouterText` | `OPENROUTER_API_KEY` |
71
- | Ollama | `@tanstack/ai-ollama` | `ollamaText` | `OLLAMA_HOST` (default: `http://localhost:11434`) |
65
+ | Provider | Package | Factory | Env Var |
66
+ | ----------------- | -------------------------------- | ------------------------------------------- | ------------------------------------------------- |
67
+ | OpenAI | `@tanstack/ai-openai` | `openaiText` | `OPENAI_API_KEY` |
68
+ | Anthropic | `@tanstack/ai-anthropic` | `anthropicText` | `ANTHROPIC_API_KEY` |
69
+ | Gemini | `@tanstack/ai-gemini` | `geminiText` | `GOOGLE_API_KEY` or `GEMINI_API_KEY` |
70
+ | Grok (xAI) | `@tanstack/ai-grok` | `grokText` | `XAI_API_KEY` |
71
+ | Groq | `@tanstack/ai-groq` | `groqText` | `GROQ_API_KEY` |
72
+ | OpenRouter | `@tanstack/ai-openrouter` | `openRouterText` | `OPENROUTER_API_KEY` |
73
+ | Ollama | `@tanstack/ai-ollama` | `ollamaText` | `OLLAMA_HOST` (default: `http://localhost:11434`) |
74
+ | OpenAI-compatible | `@tanstack/ai-openai/compatible` | `openaiCompatible` / `openaiCompatibleText` | provider-specific (passed via `apiKey`) |
72
75
 
73
76
  ```typescript
74
77
  // Each factory takes model as first arg, optional config as second
@@ -252,6 +255,63 @@ Subclasses can override to narrow the capability. When extending an
252
255
  adapter for a custom model that doesn't support the combination, return
253
256
  `false` explicitly.
254
257
 
258
+ ### 6. OpenAI-Compatible Providers
259
+
260
+ Any provider that implements the OpenAI **Chat Completions** API (DeepSeek,
261
+ Moonshot/Kimi, Together, Fireworks, Cerebras, Qwen/DashScope, Perplexity,
262
+ NVIDIA NIM, LM Studio, etc.) can be used through the generic
263
+ `openaiCompatible` factory from `@tanstack/ai-openai/compatible` — no
264
+ dedicated package required.
265
+
266
+ ```typescript
267
+ import { openaiCompatible } from '@tanstack/ai-openai/compatible'
268
+ import { createModel } from '@tanstack/ai'
269
+
270
+ // Provider-factory: configure baseURL + apiKey + models ONCE,
271
+ // then select a model per call (the model arg is a type-safe union).
272
+ const deepseek = openaiCompatible({
273
+ name: 'deepseek', // optional label for devtools/errors (default 'openai-compatible')
274
+ baseURL: 'https://api.deepseek.com/v1',
275
+ apiKey: process.env.DEEPSEEK_API_KEY!,
276
+ models: [
277
+ 'deepseek-chat', // bare string → optimistic defaults: text/image in, streaming, tools, structured output
278
+ createModel('deepseek-reasoner', {
279
+ // rich def → precise per-model capabilities
280
+ input: ['text'],
281
+ features: ['reasoning', 'structured_outputs'],
282
+ }),
283
+ ],
284
+ })
285
+
286
+ chat({ adapter: deepseek('deepseek-chat'), messages })
287
+ chat({ adapter: deepseek('deepseek-reasoner'), messages })
288
+ ```
289
+
290
+ `config` also accepts any OpenAI SDK `ClientOptions` (notably `defaultHeaders`
291
+ and `defaultQuery`) for providers that need extra auth headers or query params.
292
+
293
+ For a single model, use the one-shot helper:
294
+
295
+ ```typescript
296
+ import { openaiCompatibleText } from '@tanstack/ai-openai/compatible'
297
+
298
+ chat({
299
+ adapter: openaiCompatibleText('deepseek-chat', {
300
+ baseURL: 'https://api.deepseek.com/v1',
301
+ apiKey: process.env.DEEPSEEK_API_KEY!,
302
+ }),
303
+ messages,
304
+ })
305
+ ```
306
+
307
+ Pass `api: 'responses'` to target the OpenAI **Responses** API instead of Chat
308
+ Completions (only for the rare compatible provider that implements it, e.g.
309
+ Azure OpenAI); the default is `'chat-completions'`, which is what nearly all
310
+ compatible providers speak.
311
+
312
+ > Verify the provider's current `baseURL` and model ids against its live docs —
313
+ > they drift. See `docs/adapters/openai-compatible.md` for the full provider table.
314
+
255
315
  ## Common Mistakes
256
316
 
257
317
  ### a. HIGH: Confusing legacy monolithic with tree-shakeable adapter
@@ -22,6 +22,8 @@ export interface ExtendedModelDef<
22
22
  TName extends string = string,
23
23
  TInput extends ReadonlyArray<Modality> = ReadonlyArray<Modality>,
24
24
  TOptions = unknown,
25
+ TFeatures extends ReadonlyArray<string> = ReadonlyArray<string>,
26
+ TTools extends ReadonlyArray<string> = ReadonlyArray<string>,
25
27
  > {
26
28
  /** The model name identifier */
27
29
  name: TName
@@ -29,6 +31,23 @@ export interface ExtendedModelDef<
29
31
  input: TInput
30
32
  /** Type brand for provider options - use `{} as YourOptionsType` */
31
33
  modelOptions: TOptions
34
+ /** Optional declared features (e.g. 'reasoning', 'structured_outputs') */
35
+ features?: TFeatures
36
+ /** Optional declared provider tools (e.g. 'web_search') */
37
+ tools?: TTools
38
+ }
39
+
40
+ /** Capability bag accepted by the object form of `createModel`. */
41
+ export interface ModelCapabilities<
42
+ TInput extends ReadonlyArray<Modality> = ReadonlyArray<Modality>,
43
+ TFeatures extends ReadonlyArray<string> = ReadonlyArray<string>,
44
+ TTools extends ReadonlyArray<string> = ReadonlyArray<string>,
45
+ TOptions = unknown,
46
+ > {
47
+ input?: TInput
48
+ features?: TFeatures
49
+ tools?: TTools
50
+ modelOptions?: TOptions
32
51
  }
33
52
 
34
53
  /**
@@ -57,15 +76,57 @@ export interface ExtendedModelDef<
57
76
  *
58
77
  * const myOpenai = extendAdapter(openaiText, customModels)
59
78
  * ```
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * // Capabilities object form - declare features and provider tools
83
+ * const reasoner = createModel('reasoner', {
84
+ * input: ['text'],
85
+ * features: ['reasoning', 'structured_outputs'],
86
+ * tools: ['web_search'],
87
+ * })
88
+ * ```
60
89
  */
90
+ // Overload 1 — legacy positional input array (unchanged behavior)
61
91
  export function createModel<
62
92
  const TName extends string,
63
93
  const TInput extends ReadonlyArray<Modality>,
64
- >(name: TName, input: TInput): ExtendedModelDef<TName, TInput> {
94
+ >(name: TName, input: TInput): ExtendedModelDef<TName, TInput>
95
+ // Overload 2 — capabilities object
96
+ export function createModel<
97
+ const TName extends string,
98
+ const TCaps extends ModelCapabilities,
99
+ >(
100
+ name: TName,
101
+ capabilities: TCaps,
102
+ ): ExtendedModelDef<
103
+ TName,
104
+ TCaps['input'] extends ReadonlyArray<Modality>
105
+ ? TCaps['input']
106
+ : ReadonlyArray<Modality>,
107
+ TCaps['modelOptions'],
108
+ TCaps['features'] extends ReadonlyArray<string>
109
+ ? TCaps['features']
110
+ : ReadonlyArray<string>,
111
+ TCaps['tools'] extends ReadonlyArray<string>
112
+ ? TCaps['tools']
113
+ : ReadonlyArray<string>
114
+ >
115
+ // Implementation
116
+ export function createModel(
117
+ name: string,
118
+ second: ReadonlyArray<Modality> | ModelCapabilities,
119
+ ): ExtendedModelDef {
120
+ if (Array.isArray(second)) {
121
+ return { name, input: second, modelOptions: {} }
122
+ }
123
+ const caps = second as ModelCapabilities
65
124
  return {
66
125
  name,
67
- input,
68
- modelOptions: {},
126
+ input: caps.input ?? (['text'] as ReadonlyArray<Modality>),
127
+ modelOptions: caps.modelOptions ?? {},
128
+ features: caps.features,
129
+ tools: caps.tools,
69
130
  }
70
131
  }
71
132
 
package/src/index.ts CHANGED
@@ -200,7 +200,7 @@ export {
200
200
 
201
201
  // Adapter extension utilities
202
202
  export { createModel, extendAdapter } from './extend-adapter'
203
- export type { ExtendedModelDef } from './extend-adapter'
203
+ export type { ExtendedModelDef, ModelCapabilities } from './extend-adapter'
204
204
 
205
205
  // Logger
206
206
  export type {