@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.
Files changed (67) hide show
  1. package/package.json +3 -3
  2. package/src/utils/schema-converter.ts +73 -8
  3. package/dist/esm/adapters/chat-completions-text.d.ts +0 -116
  4. package/dist/esm/adapters/chat-completions-text.js +0 -967
  5. package/dist/esm/adapters/chat-completions-text.js.map +0 -1
  6. package/dist/esm/adapters/chat-completions-tool-converter.d.ts +0 -35
  7. package/dist/esm/adapters/chat-completions-tool-converter.js +0 -42
  8. package/dist/esm/adapters/chat-completions-tool-converter.js.map +0 -1
  9. package/dist/esm/adapters/responses-text.d.ts +0 -135
  10. package/dist/esm/adapters/responses-text.js +0 -1324
  11. package/dist/esm/adapters/responses-text.js.map +0 -1
  12. package/dist/esm/adapters/responses-tool-converter.d.ts +0 -42
  13. package/dist/esm/adapters/responses-tool-converter.js +0 -38
  14. package/dist/esm/adapters/responses-tool-converter.js.map +0 -1
  15. package/dist/esm/index.d.ts +0 -7
  16. package/dist/esm/index.js +0 -59
  17. package/dist/esm/index.js.map +0 -1
  18. package/dist/esm/tools/apply-patch-tool.d.ts +0 -16
  19. package/dist/esm/tools/apply-patch-tool.js +0 -17
  20. package/dist/esm/tools/apply-patch-tool.js.map +0 -1
  21. package/dist/esm/tools/code-interpreter-tool.d.ts +0 -17
  22. package/dist/esm/tools/code-interpreter-tool.js +0 -22
  23. package/dist/esm/tools/code-interpreter-tool.js.map +0 -1
  24. package/dist/esm/tools/computer-use-tool.d.ts +0 -16
  25. package/dist/esm/tools/computer-use-tool.js +0 -23
  26. package/dist/esm/tools/computer-use-tool.js.map +0 -1
  27. package/dist/esm/tools/custom-tool.d.ts +0 -13
  28. package/dist/esm/tools/custom-tool.js +0 -25
  29. package/dist/esm/tools/custom-tool.js.map +0 -1
  30. package/dist/esm/tools/file-search-tool.d.ts +0 -18
  31. package/dist/esm/tools/file-search-tool.js +0 -35
  32. package/dist/esm/tools/file-search-tool.js.map +0 -1
  33. package/dist/esm/tools/function-tool.d.ts +0 -23
  34. package/dist/esm/tools/function-tool.js +0 -33
  35. package/dist/esm/tools/function-tool.js.map +0 -1
  36. package/dist/esm/tools/image-generation-tool.d.ts +0 -23
  37. package/dist/esm/tools/image-generation-tool.js +0 -28
  38. package/dist/esm/tools/image-generation-tool.js.map +0 -1
  39. package/dist/esm/tools/index.d.ts +0 -27
  40. package/dist/esm/tools/local-shell-tool.d.ts +0 -17
  41. package/dist/esm/tools/local-shell-tool.js +0 -17
  42. package/dist/esm/tools/local-shell-tool.js.map +0 -1
  43. package/dist/esm/tools/mcp-tool.d.ts +0 -18
  44. package/dist/esm/tools/mcp-tool.js +0 -31
  45. package/dist/esm/tools/mcp-tool.js.map +0 -1
  46. package/dist/esm/tools/shell-tool.d.ts +0 -26
  47. package/dist/esm/tools/shell-tool.js +0 -25
  48. package/dist/esm/tools/shell-tool.js.map +0 -1
  49. package/dist/esm/tools/tool-choice.d.ts +0 -17
  50. package/dist/esm/tools/tool-converter.d.ts +0 -6
  51. package/dist/esm/tools/tool-converter.js +0 -61
  52. package/dist/esm/tools/tool-converter.js.map +0 -1
  53. package/dist/esm/tools/web-search-preview-tool.d.ts +0 -19
  54. package/dist/esm/tools/web-search-preview-tool.js +0 -19
  55. package/dist/esm/tools/web-search-preview-tool.js.map +0 -1
  56. package/dist/esm/tools/web-search-tool.d.ts +0 -20
  57. package/dist/esm/tools/web-search-tool.js +0 -19
  58. package/dist/esm/tools/web-search-tool.js.map +0 -1
  59. package/dist/esm/usage.d.ts +0 -34
  60. package/dist/esm/usage.js +0 -83
  61. package/dist/esm/usage.js.map +0 -1
  62. package/dist/esm/utils/request-options.d.ts +0 -14
  63. package/dist/esm/utils/request-options.js +0 -11
  64. package/dist/esm/utils/request-options.js.map +0 -1
  65. package/dist/esm/utils/schema-converter.d.ts +0 -34
  66. package/dist/esm/utils/schema-converter.js +0 -109
  67. 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.1",
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.35.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.35.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
- * Returns `false` when `schema` (anywhere in the tree) uses a JSON-Schema
92
- * keyword outside OpenAI's strict Structured Outputs subset — i.e. it cannot be
93
- * made strict-compatible and must be sent with `strict: false`.
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 property
96
- * literally named e.g. `oneOf` also trips it. That only costs that one tool its
97
- * strict mode, which is strictly safer than a false "compatible" verdict that
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 !containsStrictUnsupportedKeyword(schema)
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
- }