@tanstack/openai-base 0.9.1 → 0.9.3
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/package.json +3 -3
- package/src/utils/schema-converter.ts +73 -8
- package/dist/esm/adapters/chat-completions-text.d.ts +0 -116
- package/dist/esm/adapters/chat-completions-text.js +0 -967
- package/dist/esm/adapters/chat-completions-text.js.map +0 -1
- package/dist/esm/adapters/chat-completions-tool-converter.d.ts +0 -35
- package/dist/esm/adapters/chat-completions-tool-converter.js +0 -42
- package/dist/esm/adapters/chat-completions-tool-converter.js.map +0 -1
- package/dist/esm/adapters/responses-text.d.ts +0 -135
- package/dist/esm/adapters/responses-text.js +0 -1324
- package/dist/esm/adapters/responses-text.js.map +0 -1
- package/dist/esm/adapters/responses-tool-converter.d.ts +0 -42
- package/dist/esm/adapters/responses-tool-converter.js +0 -38
- package/dist/esm/adapters/responses-tool-converter.js.map +0 -1
- package/dist/esm/index.d.ts +0 -7
- package/dist/esm/index.js +0 -59
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/tools/apply-patch-tool.d.ts +0 -16
- package/dist/esm/tools/apply-patch-tool.js +0 -17
- package/dist/esm/tools/apply-patch-tool.js.map +0 -1
- package/dist/esm/tools/code-interpreter-tool.d.ts +0 -17
- package/dist/esm/tools/code-interpreter-tool.js +0 -22
- package/dist/esm/tools/code-interpreter-tool.js.map +0 -1
- package/dist/esm/tools/computer-use-tool.d.ts +0 -16
- package/dist/esm/tools/computer-use-tool.js +0 -23
- package/dist/esm/tools/computer-use-tool.js.map +0 -1
- package/dist/esm/tools/custom-tool.d.ts +0 -13
- package/dist/esm/tools/custom-tool.js +0 -25
- package/dist/esm/tools/custom-tool.js.map +0 -1
- package/dist/esm/tools/file-search-tool.d.ts +0 -18
- package/dist/esm/tools/file-search-tool.js +0 -35
- package/dist/esm/tools/file-search-tool.js.map +0 -1
- package/dist/esm/tools/function-tool.d.ts +0 -23
- package/dist/esm/tools/function-tool.js +0 -33
- package/dist/esm/tools/function-tool.js.map +0 -1
- package/dist/esm/tools/image-generation-tool.d.ts +0 -23
- package/dist/esm/tools/image-generation-tool.js +0 -28
- package/dist/esm/tools/image-generation-tool.js.map +0 -1
- package/dist/esm/tools/index.d.ts +0 -27
- package/dist/esm/tools/local-shell-tool.d.ts +0 -17
- package/dist/esm/tools/local-shell-tool.js +0 -17
- package/dist/esm/tools/local-shell-tool.js.map +0 -1
- package/dist/esm/tools/mcp-tool.d.ts +0 -18
- package/dist/esm/tools/mcp-tool.js +0 -31
- package/dist/esm/tools/mcp-tool.js.map +0 -1
- package/dist/esm/tools/shell-tool.d.ts +0 -26
- package/dist/esm/tools/shell-tool.js +0 -25
- package/dist/esm/tools/shell-tool.js.map +0 -1
- package/dist/esm/tools/tool-choice.d.ts +0 -17
- package/dist/esm/tools/tool-converter.d.ts +0 -6
- package/dist/esm/tools/tool-converter.js +0 -61
- package/dist/esm/tools/tool-converter.js.map +0 -1
- package/dist/esm/tools/web-search-preview-tool.d.ts +0 -19
- package/dist/esm/tools/web-search-preview-tool.js +0 -19
- package/dist/esm/tools/web-search-preview-tool.js.map +0 -1
- package/dist/esm/tools/web-search-tool.d.ts +0 -20
- package/dist/esm/tools/web-search-tool.js +0 -19
- package/dist/esm/tools/web-search-tool.js.map +0 -1
- package/dist/esm/usage.d.ts +0 -34
- package/dist/esm/usage.js +0 -83
- package/dist/esm/usage.js.map +0 -1
- package/dist/esm/utils/request-options.d.ts +0 -14
- package/dist/esm/utils/request-options.js +0 -11
- package/dist/esm/utils/request-options.js.map +0 -1
- package/dist/esm/utils/schema-converter.d.ts +0 -34
- package/dist/esm/utils/schema-converter.js +0 -109
- package/dist/esm/utils/schema-converter.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/openai-base",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Shared OpenAI SDK base adapters for TanStack AI providers using Chat Completions and Responses APIs.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"@tanstack/ai-utils": "0.3.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@tanstack/ai": "^0.
|
|
50
|
+
"@tanstack/ai": "^0.36.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@vitest/coverage-v8": "4.0.14",
|
|
54
54
|
"vite": "^7.3.3",
|
|
55
55
|
"zod": "^4.2.0",
|
|
56
|
-
"@tanstack/ai": "0.
|
|
56
|
+
"@tanstack/ai": "0.36.0"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "vite build",
|
|
@@ -88,17 +88,43 @@ const STRICT_UNSUPPORTED_KEYWORDS: ReadonlyArray<string> = [
|
|
|
88
88
|
]
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
91
|
+
* Keys that give a schema node a resolvable type under OpenAI's strict subset.
|
|
92
|
+
* A schema-position node carrying none of these is *typeless* (e.g. the empty
|
|
93
|
+
* `{}` that `z.any()` / `z.unknown()` emit). Strict mode requires every schema
|
|
94
|
+
* to declare a type, so a typeless node 400s the whole request — such tools
|
|
95
|
+
* must be sent with `strict: false` instead. (`oneOf`/`allOf`/`$ref` count as
|
|
96
|
+
* type indicators here even though they're independently strict-unsupported;
|
|
97
|
+
* the keyword check below already rejects them.)
|
|
98
|
+
*/
|
|
99
|
+
const TYPE_INDICATOR_KEYWORDS: ReadonlyArray<string> = [
|
|
100
|
+
'type',
|
|
101
|
+
'enum',
|
|
102
|
+
'const',
|
|
103
|
+
'anyOf',
|
|
104
|
+
'oneOf',
|
|
105
|
+
'allOf',
|
|
106
|
+
'$ref',
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Returns `false` when `schema` cannot be made strict-compatible and must be
|
|
111
|
+
* sent with `strict: false`. Two ways that happens:
|
|
112
|
+
*
|
|
113
|
+
* 1. It uses a JSON-Schema keyword outside OpenAI's strict subset anywhere in
|
|
114
|
+
* the tree (`oneOf`/`allOf`/`not`/`$ref`/`$defs`).
|
|
115
|
+
* 2. It contains a *typeless* schema node — a property/items/anyOf entry with
|
|
116
|
+
* no `type` (nor `enum`/`const`/combinator), e.g. the `{}` that `z.any()`
|
|
117
|
+
* produces. Strict mode rejects typeless schemas.
|
|
94
118
|
*
|
|
95
|
-
* Conservative by design: keywords are matched as object keys, so a
|
|
96
|
-
* literally named e.g. `oneOf` also trips it. That only costs that one
|
|
97
|
-
* strict mode, which is strictly safer than a false "compatible"
|
|
98
|
-
* 400s the whole request.
|
|
119
|
+
* Conservative by design: for (1) keywords are matched as object keys, so a
|
|
120
|
+
* property literally named e.g. `oneOf` also trips it. That only costs that one
|
|
121
|
+
* tool its strict mode, which is strictly safer than a false "compatible"
|
|
122
|
+
* verdict that 400s the whole request.
|
|
99
123
|
*/
|
|
100
124
|
export function isStrictModeCompatible(schema: unknown): boolean {
|
|
101
|
-
return
|
|
125
|
+
return (
|
|
126
|
+
!containsStrictUnsupportedKeyword(schema) && !containsTypelessSchema(schema)
|
|
127
|
+
)
|
|
102
128
|
}
|
|
103
129
|
|
|
104
130
|
function containsStrictUnsupportedKeyword(node: unknown): boolean {
|
|
@@ -113,6 +139,45 @@ function containsStrictUnsupportedKeyword(node: unknown): boolean {
|
|
|
113
139
|
return false
|
|
114
140
|
}
|
|
115
141
|
|
|
142
|
+
/** A schema-position node that declares no type and so 400s strict mode. */
|
|
143
|
+
function isTypelessSchema(node: unknown): boolean {
|
|
144
|
+
if (node === null || typeof node !== 'object' || Array.isArray(node)) {
|
|
145
|
+
// boolean schemas (`true`/`false`) and non-objects aren't typeless props.
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
return !TYPE_INDICATOR_KEYWORDS.some((key) => key in node)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Walks the genuine schema positions (property values, `items`, `anyOf`
|
|
153
|
+
* variants) and reports whether any is typeless. Unlike the keyword walk this
|
|
154
|
+
* must respect structure: an empty `{}` is only a problem at a schema position,
|
|
155
|
+
* not e.g. an empty `properties` map.
|
|
156
|
+
*/
|
|
157
|
+
function containsTypelessSchema(node: unknown): boolean {
|
|
158
|
+
if (node === null || typeof node !== 'object' || Array.isArray(node)) {
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
const schema = node as Record<string, any>
|
|
162
|
+
|
|
163
|
+
const children: Array<unknown> = []
|
|
164
|
+
if (schema.properties && typeof schema.properties === 'object') {
|
|
165
|
+
children.push(...Object.values(schema.properties))
|
|
166
|
+
}
|
|
167
|
+
if (schema.items !== undefined) {
|
|
168
|
+
children.push(
|
|
169
|
+
...(Array.isArray(schema.items) ? schema.items : [schema.items]),
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
if (Array.isArray(schema.anyOf)) {
|
|
173
|
+
children.push(...schema.anyOf)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return children.some(
|
|
177
|
+
(child) => isTypelessSchema(child) || containsTypelessSchema(child),
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
116
181
|
/**
|
|
117
182
|
* Strict-mode structural rewrite (required widening, nullability,
|
|
118
183
|
* additionalProperties). Kept private so the public entry point can apply the
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from '@tanstack/ai/adapters';
|
|
2
|
-
import { default as OpenAI } from 'openai';
|
|
3
|
-
import { ChatCompletionChunk, ChatCompletionContentPart, ChatCompletionCreateParamsStreaming, ChatCompletionMessageParam } from 'openai/resources/chat/completions/completions';
|
|
4
|
-
import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, StreamChunk, TextOptions } from '@tanstack/ai';
|
|
5
|
-
/**
|
|
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.
|
|
10
|
-
*/
|
|
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> {
|
|
12
|
-
readonly kind: "text";
|
|
13
|
-
readonly name: string;
|
|
14
|
-
protected client: OpenAI;
|
|
15
|
-
constructor(model: TModel, name: string, client: OpenAI);
|
|
16
|
-
chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
|
|
17
|
-
/**
|
|
18
|
-
* Generate structured output using the provider's JSON Schema response format.
|
|
19
|
-
* Uses stream: false to get the complete response in one call.
|
|
20
|
-
*
|
|
21
|
-
* OpenAI-compatible APIs have strict requirements for structured output:
|
|
22
|
-
* - All properties must be in the `required` array
|
|
23
|
-
* - Optional fields should have null added to their type union
|
|
24
|
-
* - additionalProperties must be false for all objects
|
|
25
|
-
*
|
|
26
|
-
* The outputSchema is already JSON Schema (converted in the ai layer).
|
|
27
|
-
* We apply provider-specific transformations for structured output compatibility.
|
|
28
|
-
*/
|
|
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;
|
|
47
|
-
/**
|
|
48
|
-
* Applies provider-specific transformations for structured output compatibility.
|
|
49
|
-
* Override this in subclasses to handle provider-specific quirks.
|
|
50
|
-
*/
|
|
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 is a passthrough.
|
|
65
|
-
*
|
|
66
|
-
* Provider `null`s are no longer stripped here: strict-mode null-widening is
|
|
67
|
-
* now undone precisely by the engine (`undoNullWidening`, driven by the
|
|
68
|
-
* schema's null-widening map) the moment the result is captured, so a blind
|
|
69
|
-
* `transformNullsToUndefined` at the adapter would only destroy genuine
|
|
70
|
-
* `.nullable()` nulls. Subclasses may still override to remap or reshape the
|
|
71
|
-
* provider's structured output.
|
|
72
|
-
*/
|
|
73
|
-
protected transformStructuredOutput(parsed: unknown): unknown;
|
|
74
|
-
/**
|
|
75
|
-
* Processes streamed chunks from the Chat Completions API and yields AG-UI events.
|
|
76
|
-
* Override this in subclasses to handle provider-specific stream behavior.
|
|
77
|
-
*/
|
|
78
|
-
protected processStreamChunks(stream: AsyncIterable<ChatCompletionChunk>, options: TextOptions, aguiState: {
|
|
79
|
-
runId: string;
|
|
80
|
-
threadId: string;
|
|
81
|
-
messageId: string;
|
|
82
|
-
hasEmittedRunStarted: boolean;
|
|
83
|
-
}): AsyncIterable<StreamChunk>;
|
|
84
|
-
/**
|
|
85
|
-
* Maps common TextOptions to Chat Completions API request format.
|
|
86
|
-
* Override this in subclasses to add provider-specific options.
|
|
87
|
-
*/
|
|
88
|
-
protected mapOptionsToRequest(options: TextOptions): ChatCompletionCreateParamsStreaming;
|
|
89
|
-
/**
|
|
90
|
-
* Modern OpenAI-compatible Chat Completions APIs support `tools` and
|
|
91
|
-
* `response_format: json_schema` together in a single streaming request
|
|
92
|
-
* (per issue #605). Subclasses can override — Groq, for instance, must
|
|
93
|
-
* return `false` because its API rejects schema + tools + stream with a
|
|
94
|
-
* 400.
|
|
95
|
-
*/
|
|
96
|
-
supportsCombinedToolsAndSchema(): boolean;
|
|
97
|
-
/**
|
|
98
|
-
* Converts a single ModelMessage to the Chat Completions API message format.
|
|
99
|
-
* Override this in subclasses to handle provider-specific message formats.
|
|
100
|
-
*/
|
|
101
|
-
protected convertMessage(message: ModelMessage): ChatCompletionMessageParam;
|
|
102
|
-
/**
|
|
103
|
-
* Converts a single ContentPart to the Chat Completions API content part format.
|
|
104
|
-
* Override this in subclasses to handle additional content types or provider-specific metadata.
|
|
105
|
-
*/
|
|
106
|
-
protected convertContentPart(part: ContentPart): ChatCompletionContentPart | null;
|
|
107
|
-
/**
|
|
108
|
-
* Normalizes message content to an array of ContentPart.
|
|
109
|
-
* Handles backward compatibility with string content.
|
|
110
|
-
*/
|
|
111
|
-
protected normalizeContent(content: string | null | Array<ContentPart>): Array<ContentPart>;
|
|
112
|
-
/**
|
|
113
|
-
* Extracts text content from a content value that may be string, null, or ContentPart array.
|
|
114
|
-
*/
|
|
115
|
-
protected extractTextContent(content: string | null | Array<ContentPart>): string;
|
|
116
|
-
}
|