@tanstack/openai-base 0.2.1 → 0.3.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 (79) hide show
  1. package/README.md +121 -0
  2. package/dist/esm/adapters/chat-completions-text.d.ts +49 -21
  3. package/dist/esm/adapters/chat-completions-text.js +480 -68
  4. package/dist/esm/adapters/chat-completions-text.js.map +1 -1
  5. package/dist/esm/adapters/chat-completions-tool-converter.d.ts +8 -4
  6. package/dist/esm/adapters/chat-completions-tool-converter.js.map +1 -1
  7. package/dist/esm/adapters/responses-text.d.ts +46 -33
  8. package/dist/esm/adapters/responses-text.js +661 -142
  9. package/dist/esm/adapters/responses-text.js.map +1 -1
  10. package/dist/esm/index.d.ts +2 -9
  11. package/dist/esm/index.js +4 -16
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/tools/apply-patch-tool.d.ts +2 -2
  14. package/dist/esm/tools/apply-patch-tool.js.map +1 -1
  15. package/dist/esm/tools/code-interpreter-tool.d.ts +3 -2
  16. package/dist/esm/tools/code-interpreter-tool.js.map +1 -1
  17. package/dist/esm/tools/computer-use-tool.d.ts +2 -2
  18. package/dist/esm/tools/computer-use-tool.js.map +1 -1
  19. package/dist/esm/tools/custom-tool.d.ts +2 -2
  20. package/dist/esm/tools/custom-tool.js.map +1 -1
  21. package/dist/esm/tools/file-search-tool.d.ts +2 -2
  22. package/dist/esm/tools/file-search-tool.js.map +1 -1
  23. package/dist/esm/tools/function-tool.d.ts +2 -2
  24. package/dist/esm/tools/function-tool.js.map +1 -1
  25. package/dist/esm/tools/image-generation-tool.d.ts +3 -2
  26. package/dist/esm/tools/image-generation-tool.js.map +1 -1
  27. package/dist/esm/tools/local-shell-tool.d.ts +3 -2
  28. package/dist/esm/tools/local-shell-tool.js.map +1 -1
  29. package/dist/esm/tools/mcp-tool.d.ts +3 -2
  30. package/dist/esm/tools/mcp-tool.js.map +1 -1
  31. package/dist/esm/tools/shell-tool.d.ts +2 -2
  32. package/dist/esm/tools/shell-tool.js.map +1 -1
  33. package/dist/esm/tools/web-search-preview-tool.d.ts +2 -2
  34. package/dist/esm/tools/web-search-preview-tool.js.map +1 -1
  35. package/dist/esm/tools/web-search-tool.d.ts +2 -2
  36. package/dist/esm/tools/web-search-tool.js.map +1 -1
  37. package/package.json +6 -6
  38. package/src/adapters/chat-completions-text.ts +605 -117
  39. package/src/adapters/chat-completions-tool-converter.ts +9 -5
  40. package/src/adapters/responses-text.ts +869 -210
  41. package/src/index.ts +2 -12
  42. package/src/tools/apply-patch-tool.ts +2 -2
  43. package/src/tools/code-interpreter-tool.ts +4 -2
  44. package/src/tools/computer-use-tool.ts +2 -2
  45. package/src/tools/custom-tool.ts +2 -2
  46. package/src/tools/file-search-tool.ts +3 -3
  47. package/src/tools/function-tool.ts +2 -2
  48. package/src/tools/image-generation-tool.ts +4 -2
  49. package/src/tools/local-shell-tool.ts +4 -2
  50. package/src/tools/mcp-tool.ts +4 -2
  51. package/src/tools/shell-tool.ts +2 -2
  52. package/src/tools/web-search-preview-tool.ts +2 -2
  53. package/src/tools/web-search-tool.ts +2 -2
  54. package/dist/esm/adapters/image.d.ts +0 -32
  55. package/dist/esm/adapters/image.js +0 -89
  56. package/dist/esm/adapters/image.js.map +0 -1
  57. package/dist/esm/adapters/summarize.d.ts +0 -28
  58. package/dist/esm/adapters/summarize.js +0 -112
  59. package/dist/esm/adapters/summarize.js.map +0 -1
  60. package/dist/esm/adapters/transcription.d.ts +0 -34
  61. package/dist/esm/adapters/transcription.js +0 -131
  62. package/dist/esm/adapters/transcription.js.map +0 -1
  63. package/dist/esm/adapters/tts.d.ts +0 -26
  64. package/dist/esm/adapters/tts.js +0 -78
  65. package/dist/esm/adapters/tts.js.map +0 -1
  66. package/dist/esm/adapters/video.d.ts +0 -72
  67. package/dist/esm/adapters/video.js +0 -238
  68. package/dist/esm/adapters/video.js.map +0 -1
  69. package/dist/esm/types/config.d.ts +0 -4
  70. package/dist/esm/utils/client.d.ts +0 -3
  71. package/dist/esm/utils/client.js +0 -8
  72. package/dist/esm/utils/client.js.map +0 -1
  73. package/src/adapters/image.ts +0 -158
  74. package/src/adapters/summarize.ts +0 -174
  75. package/src/adapters/transcription.ts +0 -194
  76. package/src/adapters/tts.ts +0 -124
  77. package/src/adapters/video.ts +0 -385
  78. package/src/types/config.ts +0 -5
  79. package/src/utils/client.ts +0 -8
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @tanstack/openai-base
2
+
3
+ Shared base adapters for providers that drive the official `openai` SDK
4
+ against a different `baseURL`.
5
+
6
+ ## TL;DR
7
+
8
+ Several providers ship endpoints that the official `openai` Node SDK can
9
+ talk to verbatim — you just point it at a different `baseURL`. xAI's Grok
10
+ endpoint, Groq's `/openai/v1` endpoint, and OpenAI itself all fall in this
11
+ camp. This package contains the shared logic for both wire formats those
12
+ endpoints expose:
13
+
14
+ - `OpenAIBaseChatCompletionsTextAdapter` — for `/v1/chat/completions`
15
+ - `OpenAIBaseResponsesTextAdapter` — for `/v1/responses`
16
+
17
+ Provider packages (`@tanstack/ai-openai`, `@tanstack/ai-groq`,
18
+ `@tanstack/ai-grok`) construct an `OpenAI` client with their own `baseURL`,
19
+ pass it to the relevant base adapter, and override a small set of
20
+ protected hooks for SDK-shape variance and provider-specific quirks.
21
+
22
+ ## Why this package exists
23
+
24
+ Every text adapter in TanStack AI — regardless of provider — emits
25
+ [AG-UI](https://github.com/CopilotKit/ag-ui) events (`RUN_STARTED`,
26
+ `TEXT_MESSAGE_*`, `TOOL_CALL_*`, `RUN_FINISHED`, …) as its output stream.
27
+ That is the universal unification.
28
+
29
+ Input protocols differ. The OpenAI Chat Completions and Responses wire
30
+ formats both have multiple implementers in the ecosystem, so it pays to
31
+ write the streaming-chunk assembly, partial-JSON tool-arg buffering,
32
+ tool-call deduplication, and structured-output coercion once and share
33
+ it. That shared code lives here.
34
+
35
+ Providers whose native API doesn't match either OpenAI wire format
36
+ (Anthropic, Gemini, Ollama's native API, OpenRouter's own SDK) extend
37
+ `BaseTextAdapter` from `@tanstack/ai` directly — there's nothing to
38
+ share, so they don't pay the indirection cost.
39
+
40
+ ## What goes here vs. in `@tanstack/ai-openai`
41
+
42
+ | Belongs in `@tanstack/openai-base` | Belongs in `@tanstack/ai-openai` |
43
+ | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
44
+ | Logic for the Chat Completions wire format | OpenAI-specific tool types (`web_search_preview`, `code_interpreter`, `local_shell`, `apply_patch`, `computer_use`, `mcp`, …) |
45
+ | Logic for the Responses wire format | OpenAI model metadata, model lists, capability matrices |
46
+ | Streaming chunk assembly, AG-UI lifecycle, partial-JSON tool-arg buffering, tool-call deduplication | OpenAI-only request/response fields that no other consumer of the base sets |
47
+ | Schema converters and structured-output coercion that all consumers accept | OpenAI's media adapters (image/TTS/video/transcription) that Grok/Groq don't implement |
48
+
49
+ **Rule of thumb**: if a field would be useful to at least two of the
50
+ consuming packages (`ai-openai`, `ai-grok`, `ai-groq`), it belongs here.
51
+ Otherwise it belongs in the provider's own package, plumbed in via a
52
+ subclass override or a hook.
53
+
54
+ ## How providers extend the bases
55
+
56
+ The base constructor takes a pre-built `OpenAI` client. Subclasses
57
+ construct the SDK with their own `baseURL` (and any other client
58
+ options) and pass it to `super`:
59
+
60
+ ```ts
61
+ class GrokTextAdapter extends OpenAIBaseChatCompletionsTextAdapter<…> {
62
+ constructor(config: GrokConfig, model: TModel) {
63
+ super(model, 'grok', new OpenAI(withGrokDefaults(config)))
64
+ }
65
+ }
66
+ ```
67
+
68
+ Per-provider quirks are handled via protected hooks:
69
+
70
+ - `convertMessage`, `mapOptionsToRequest` — bridge request-shape
71
+ differences (extra fields, omitted fields, alternative encodings).
72
+ - `extractReasoning` — surface a provider's reasoning channel into the
73
+ shared `REASONING_*` AG-UI lifecycle.
74
+ - `transformStructuredOutput`, `makeStructuredOutputCompatible` —
75
+ adjust structured-output handling for provider quirks (e.g. Groq's
76
+ schema-shape requirements).
77
+ - `processStreamChunks` — wrap the shared chunk processor for last-mile
78
+ fixups (e.g. Groq's `x_groq.usage` → `chunk.usage` promotion).
79
+ - `extractTextFromResponse` — pull the assistant text out of the
80
+ provider's non-streaming response shape.
81
+
82
+ Each provider typically overrides 2–6 hooks and inherits everything else.
83
+
84
+ ## Architecture
85
+
86
+ ```
87
+ @tanstack/ai
88
+ └── BaseTextAdapter (abstract — emits AG-UI events)
89
+ │
90
+ ├── @tanstack/openai-base::OpenAIBaseChatCompletionsTextAdapter
91
+ │ ├── ai-groq
92
+ │ └── ai-grok
93
+ │
94
+ ├── @tanstack/openai-base::OpenAIBaseResponsesTextAdapter
95
+ │ └── ai-openai (Responses is OpenAI's preferred API)
96
+ │
97
+ ├── ai-anthropic::AnthropicTextAdapter extends BaseTextAdapter directly
98
+ ├── ai-gemini::GeminiTextAdapter extends BaseTextAdapter directly
99
+ ├── ai-ollama::OllamaTextAdapter extends BaseTextAdapter directly
100
+ └── ai-openrouter (text + responses) extends BaseTextAdapter directly
101
+ (uses @openrouter/sdk natively)
102
+ ```
103
+
104
+ Note: `ai-openai` ships only the Responses-based text adapter. For pure
105
+ Chat Completions use cases without OpenAI-specific behaviour, use
106
+ `ai-grok` or `ai-groq`, or build a new provider package extending
107
+ `OpenAIBaseChatCompletionsTextAdapter`.
108
+
109
+ ## Direct use
110
+
111
+ Most users don't import from this package directly; they install a
112
+ provider package and the adapter from there does the work.
113
+
114
+ If you're building an adapter for a new endpoint that the official
115
+ `openai` SDK can talk to verbatim (vLLM, Together, Fireworks, a
116
+ self-hosted gateway, …), import the abstract adapters from this package
117
+ and subclass them. The existing providers are worked examples —
118
+ `@tanstack/ai-grok` is the simplest (xAI's API is a near-direct OpenAI
119
+ Chat Completions clone), `@tanstack/ai-groq` shows the
120
+ `processStreamChunks` and `makeStructuredOutputCompatible` override
121
+ pattern.
@@ -1,25 +1,18 @@
1
1
  import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from '@tanstack/ai/adapters';
2
- import { default as OpenAI_SDK } from 'openai';
2
+ import { default as OpenAI } from 'openai';
3
+ import { ChatCompletionChunk, ChatCompletionContentPart, ChatCompletionCreateParamsStreaming, ChatCompletionMessageParam } from 'openai/resources/chat/completions/completions';
3
4
  import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, StreamChunk, TextOptions } from '@tanstack/ai';
4
- import { OpenAICompatibleClientConfig } from '../types/config.js';
5
5
  /**
6
- * OpenAI-compatible Chat Completions Text Adapter
7
- *
8
- * A generalized base class for providers that use the OpenAI Chat Completions API
9
- * (`/v1/chat/completions`). Providers like Grok, Groq, OpenRouter, and others can
10
- * extend this class and only need to:
11
- * - Set `baseURL` in the config
12
- * - Lock the generic type parameters to provider-specific types
13
- * - Override specific methods for quirks
14
- *
15
- * All methods that build requests or process responses are `protected` so subclasses
16
- * can override them.
6
+ * Shared implementation of the OpenAI Chat Completions API. Holds the
7
+ * stream-accumulator + AG-UI lifecycle logic and calls the OpenAI SDK
8
+ * directly. Subclasses (ai-openai, ai-grok, ai-groq) construct an OpenAI
9
+ * client with their provider-specific `baseURL` / headers and pass it in.
17
10
  */
18
- export declare class OpenAICompatibleChatCompletionsTextAdapter<TModel extends string, TProviderOptions extends Record<string, any> = Record<string, any>, TInputModalities extends ReadonlyArray<Modality> = ReadonlyArray<Modality>, TMessageMetadata extends DefaultMessageMetadataByModality = DefaultMessageMetadataByModality, TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>> extends BaseTextAdapter<TModel, TProviderOptions, TInputModalities, TMessageMetadata, TToolCapabilities> {
11
+ export declare abstract class OpenAIBaseChatCompletionsTextAdapter<TModel extends string, TProviderOptions extends Record<string, unknown> = Record<string, unknown>, TInputModalities extends ReadonlyArray<Modality> = ReadonlyArray<Modality>, TMessageMetadata extends DefaultMessageMetadataByModality = DefaultMessageMetadataByModality, TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>> extends BaseTextAdapter<TModel, TProviderOptions, TInputModalities, TMessageMetadata, TToolCapabilities> {
19
12
  readonly kind: "text";
20
13
  readonly name: string;
21
- protected client: OpenAI_SDK;
22
- constructor(config: OpenAICompatibleClientConfig, model: TModel, name?: string);
14
+ protected client: OpenAI;
15
+ constructor(model: TModel, name: string, client: OpenAI);
23
16
  chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
24
17
  /**
25
18
  * Generate structured output using the provider's JSON Schema response format.
@@ -34,36 +27,71 @@ export declare class OpenAICompatibleChatCompletionsTextAdapter<TModel extends s
34
27
  * We apply provider-specific transformations for structured output compatibility.
35
28
  */
36
29
  structuredOutput(options: StructuredOutputOptions<TProviderOptions>): Promise<StructuredOutputResult<unknown>>;
30
+ /**
31
+ * Stream structured output. Single Chat Completions request with
32
+ * `response_format: json_schema` + `stream: true`. Emits the standard
33
+ * AG-UI lifecycle (`RUN_STARTED` → `REASONING_*?` → `TEXT_MESSAGE_*`
34
+ * carrying raw JSON deltas → terminal `CUSTOM 'structured-output.complete'`
35
+ * → `RUN_FINISHED`). Subclasses use the same SDK-call / reasoning /
36
+ * structured-output-transform hooks as `chatStream` / `structuredOutput` —
37
+ * no per-subclass override should be needed.
38
+ */
39
+ structuredOutputStream(options: StructuredOutputOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
40
+ /**
41
+ * Cross-SDK abort detection for `structuredOutputStream`. Default duck-types
42
+ * on `name === 'APIUserAbortError'` (OpenAI SDK), `code === 'ERR_CANCELED'`,
43
+ * and standard `AbortError`s. Subclasses with proprietary error types (e.g.
44
+ * `@openrouter/sdk`'s `RequestAbortedError`) override to extend the check.
45
+ */
46
+ protected isAbortError(error: unknown): boolean;
37
47
  /**
38
48
  * Applies provider-specific transformations for structured output compatibility.
39
49
  * Override this in subclasses to handle provider-specific quirks.
40
50
  */
41
51
  protected makeStructuredOutputCompatible(schema: Record<string, any>, originalRequired?: Array<string>): Record<string, any>;
52
+ /**
53
+ * Extract reasoning content from a stream chunk. Default returns
54
+ * `undefined` because the OpenAI Chat Completions chunk shape doesn't
55
+ * carry reasoning. The chunk param is typed `unknown` so an override can
56
+ * narrow to its own SDK chunk type without an `as` dance — the base only
57
+ * passes through `processStreamChunks`'s structurally-iterated chunk.
58
+ */
59
+ protected extractReasoning(_chunk: unknown): {
60
+ text: string;
61
+ } | undefined;
62
+ /**
63
+ * Final shaping pass applied to parsed structured-output JSON before it is
64
+ * returned to the caller. Default converts `null` values to `undefined` so
65
+ * the result aligns with the original Zod schema's optional-field
66
+ * semantics. Subclasses with different conventions (OpenRouter historically
67
+ * preserves nulls) can override.
68
+ */
69
+ protected transformStructuredOutput(parsed: unknown): unknown;
42
70
  /**
43
71
  * Processes streamed chunks from the Chat Completions API and yields AG-UI events.
44
72
  * Override this in subclasses to handle provider-specific stream behavior.
45
73
  */
46
- protected processStreamChunks(stream: AsyncIterable<OpenAI_SDK.Chat.Completions.ChatCompletionChunk>, options: TextOptions, aguiState: {
74
+ protected processStreamChunks(stream: AsyncIterable<ChatCompletionChunk>, options: TextOptions, aguiState: {
47
75
  runId: string;
76
+ threadId: string;
48
77
  messageId: string;
49
- timestamp: number;
50
78
  hasEmittedRunStarted: boolean;
51
79
  }): AsyncIterable<StreamChunk>;
52
80
  /**
53
81
  * Maps common TextOptions to Chat Completions API request format.
54
82
  * Override this in subclasses to add provider-specific options.
55
83
  */
56
- protected mapOptionsToRequest(options: TextOptions): OpenAI_SDK.Chat.Completions.ChatCompletionCreateParamsStreaming;
84
+ protected mapOptionsToRequest(options: TextOptions): ChatCompletionCreateParamsStreaming;
57
85
  /**
58
86
  * Converts a single ModelMessage to the Chat Completions API message format.
59
87
  * Override this in subclasses to handle provider-specific message formats.
60
88
  */
61
- protected convertMessage(message: ModelMessage): OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam;
89
+ protected convertMessage(message: ModelMessage): ChatCompletionMessageParam;
62
90
  /**
63
91
  * Converts a single ContentPart to the Chat Completions API content part format.
64
92
  * Override this in subclasses to handle additional content types or provider-specific metadata.
65
93
  */
66
- protected convertContentPart(part: ContentPart): OpenAI_SDK.Chat.Completions.ChatCompletionContentPart | null;
94
+ protected convertContentPart(part: ContentPart): ChatCompletionContentPart | null;
67
95
  /**
68
96
  * Normalizes message content to an array of ContentPart.
69
97
  * Handles backward compatibility with string content.