Skip to content

Commit 6cc84db

Browse files
feat: improve devtools ui and fix up bugs (#70)
* feat: improve devtools ui and fix up bugs * ci: apply automated fixes * make colors more subtle * update docs --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 3d80e49 commit 6cc84db

42 files changed

Lines changed: 723 additions & 524 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/getting-started/quick-start.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,15 @@ export async function POST(request: Request) {
5353
);
5454
}
5555

56-
const { messages } = await request.json();
56+
const { messages, conversationId } = await request.json();
5757

5858
try {
5959
// Create a streaming chat response
6060
const stream = chat({
6161
adapter: openai(),
6262
messages,
6363
model: "gpt-4o",
64+
conversationId
6465
});
6566

6667
// Convert stream to HTTP response

docs/guides/multimodal-content.md

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,12 @@ const openai = new OpenAI({ apiKey: 'your-key' })
9292

9393
// Image with detail level metadata
9494
const message = {
95-
role: 'user' as const,
95+
role: 'user' ,
9696
content: [
97-
{ type: 'text' as const, text: 'Describe this image' },
97+
{ type: 'text' , text: 'Describe this image' },
9898
{
99-
type: 'image' as const,
100-
source: { type: 'data' as const, value: imageBase64 },
99+
type: 'image' ,
100+
source: { type: 'data' , value: imageBase64 },
101101
metadata: { detail: 'high' } // 'auto' | 'low' | 'high'
102102
}
103103
]
@@ -119,25 +119,25 @@ const anthropic = new Anthropic({ apiKey: 'your-key' })
119119

120120
// Image with media type
121121
const imageMessage = {
122-
role: 'user' as const,
122+
role: 'user' ,
123123
content: [
124-
{ type: 'text' as const, text: 'What do you see?' },
124+
{ type: 'text' , text: 'What do you see?' },
125125
{
126-
type: 'image' as const,
127-
source: { type: 'data' as const, value: imageBase64 },
126+
type: 'image' ,
127+
source: { type: 'data' , value: imageBase64 },
128128
metadata: { media_type: 'image/jpeg' }
129129
}
130130
]
131131
}
132132

133133
// PDF document
134134
const docMessage = {
135-
role: 'user' as const,
135+
role: 'user',
136136
content: [
137-
{ type: 'text' as const, text: 'Summarize this document' },
137+
{ type: 'text', text: 'Summarize this document' },
138138
{
139-
type: 'document' as const,
140-
source: { type: 'data' as const, value: pdfBase64 }
139+
type: 'document',
140+
source: { type: 'data', value: pdfBase64 }
141141
}
142142
]
143143
}
@@ -158,12 +158,12 @@ const gemini = new GeminiAdapter({ apiKey: 'your-key' })
158158

159159
// Image with mimeType
160160
const message = {
161-
role: 'user' as const,
161+
role: 'user',
162162
content: [
163-
{ type: 'text' as const, text: 'Analyze this image' },
163+
{ type: 'text', text: 'Analyze this image' },
164164
{
165-
type: 'image' as const,
166-
source: { type: 'data' as const, value: imageBase64 },
165+
type: 'image',
166+
source: { type: 'data', value: imageBase64 },
167167
metadata: { mimeType: 'image/png' }
168168
}
169169
]
@@ -185,12 +185,12 @@ const ollama = new OllamaAdapter({ host: 'http://localhost:11434' })
185185

186186
// Image as base64
187187
const message = {
188-
role: 'user' as const,
188+
role: 'user',
189189
content: [
190-
{ type: 'text' as const, text: 'What is in this image?' },
190+
{ type: 'text', text: 'What is in this image?' },
191191
{
192-
type: 'image' as const,
193-
source: { type: 'data' as const, value: imageBase64 }
192+
type: 'image',
193+
source: { type: 'data', value: imageBase64 }
194194
}
195195
]
196196
}
@@ -208,9 +208,9 @@ Use `type: 'data'` for inline base64-encoded content:
208208

209209
```typescript
210210
const imagePart = {
211-
type: 'image' as const,
211+
type: 'image',
212212
source: {
213-
type: 'data' as const,
213+
type: 'data',
214214
value: 'iVBORw0KGgoAAAANSUhEUgAAAAUA...' // Base64 string
215215
}
216216
}
@@ -222,9 +222,9 @@ Use `type: 'url'` for content hosted at a URL:
222222

223223
```typescript
224224
const imagePart = {
225-
type: 'image' as const,
225+
type: 'image' ,
226226
source: {
227-
type: 'url' as const,
227+
type: 'url' ,
228228
value: 'https://example.com/image.jpg'
229229
}
230230
}

docs/guides/per-model-type-safety.md

Lines changed: 5 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -3,80 +3,10 @@ title: Per-Model Type Safety
33
id: per-model-type-safety
44
---
55

6-
The AI SDK now provides **model-specific type safety** for `providerOptions`. Each model's capabilities (defined in `model-meta.ts`) determine which provider options are allowed, and TypeScript will enforce this at compile time.
6+
The AI SDK provides **model-specific type safety** for `providerOptions`. Each model's capabilities determine which provider options are allowed, and TypeScript will enforce this at compile time.
77

88
## How It Works
9-
10-
### Architecture
11-
12-
1. **Manual Type Map per Adapter**
13-
Each adapter (e.g., OpenAI) defines an explicit type map that associates each model name with its allowed provider option fragments:
14-
15-
```typescript
16-
// In ai-openai/src/model-meta.ts
17-
export type OpenAIChatModelProviderOptionsByName = {
18-
// Models WITH structured output support (include OpenAIStructuredOutputOptions)
19-
"gpt-5": OpenAIBaseOptions &
20-
OpenAIReasoningOptions &
21-
OpenAIStructuredOutputOptions &
22-
OpenAIToolsOptions &
23-
OpenAIStreamingOptions &
24-
OpenAIMetadataOptions;
25-
"gpt-4o": OpenAIBaseOptions &
26-
OpenAIStructuredOutputOptions &
27-
OpenAIToolsOptions &
28-
OpenAIStreamingOptions &
29-
OpenAIMetadataOptions;
30-
31-
// Models WITHOUT structured output support (exclude OpenAIStructuredOutputOptions)
32-
"gpt-4-turbo": OpenAIBaseOptions &
33-
OpenAIToolsOptions &
34-
OpenAIStreamingOptions &
35-
OpenAIMetadataOptions;
36-
"gpt-4": OpenAIBaseOptions &
37-
OpenAIToolsOptions &
38-
OpenAIStreamingOptions &
39-
OpenAIMetadataOptions;
40-
// ...
41-
};
42-
```
43-
44-
2. **Adapter Integration**
45-
The adapter passes this type map as the 5th generic parameter to `BaseAdapter`:
46-
47-
```typescript
48-
class OpenAI extends BaseAdapter<
49-
typeof OPENAI_CHAT_MODELS,
50-
typeof OPENAI_EMBEDDING_MODELS,
51-
OpenAIProviderOptions,
52-
OpenAIEmbeddingProviderOptions,
53-
OpenAIChatModelProviderOptionsByName // <-- 5th generic
54-
> {
55-
_modelProviderOptionsByName!: OpenAIChatModelProviderOptionsByName; // Type-only property
56-
}
57-
```
58-
59-
3. **Core Type System**
60-
The core standalone functions use generic type helpers to extract model-specific options:
61-
62-
```typescript
63-
type ExtractModelSpecificProviderOptions<TAdapter, TModel> =
64-
TModel extends keyof TAdapter["_modelProviderOptionsByName"]
65-
? TAdapter["_modelProviderOptionsByName"][TModel]
66-
: TAdapter["_modelProviderOptionsByName"][string];
67-
```
68-
69-
4. **Function Signatures**
70-
The `chat` function is generic in `TModel` and constrains `providerOptions` accordingly:
71-
72-
```typescript
73-
chat<TAdapter extends AIAdapter<any, any, any, any, any>>(
74-
options: ChatStreamOptionsUnion<TAdapter> & {
75-
model: ExtractModelsFromAdapter<TAdapter>
76-
}
77-
)
78-
```
79-
9+
8010
## Usage Examples
8111

8212
### ✅ Correct Usage
@@ -93,8 +23,8 @@ const validCall = chat({
9323
model: "gpt-5",
9424
messages: [],
9525
providerOptions: {
26+
// OK - text is included for gpt-5
9627
text: {
97-
// OK - OpenAIStructuredOutputOptions is included for gpt-5
9828
type: "json_schema",
9929
json_schema: {
10030
/* ... */
@@ -121,106 +51,9 @@ const invalidCall = chat({
12151
TypeScript will produce:
12252

12353
```
124-
error TS2353: Object literal may only specify known properties, and 'text' does not exist in type 'OpenAIBaseOptions & OpenAIToolsOptions & OpenAIStreamingOptions & OpenAIMetadataOptions'.
125-
```
126-
127-
## Adding New Models
128-
129-
When adding a new model to an adapter:
130-
131-
1. **Add model metadata** with capabilities in `model-meta.ts`:
132-
133-
```typescript
134-
export const NEW_MODEL_META = {
135-
// ... metadata
136-
} as const satisfies ModelMeta<
137-
OpenAIBaseOptions & OpenAIStructuredOutputOptions
138-
>;
139-
```
140-
141-
2. **Update the type map** in `OpenAIChatModelProviderOptionsByName`:
142-
143-
```typescript
144-
export type OpenAIChatModelProviderOptionsByName = {
145-
// ... existing models
146-
"new-model": OpenAIBaseOptions &
147-
OpenAIStructuredOutputOptions &
148-
OpenAIToolsOptions &
149-
OpenAIStreamingOptions &
150-
OpenAIMetadataOptions;
151-
};
152-
```
153-
154-
3. **Add to model list**:
155-
```typescript
156-
export const OPENAI_CHAT_MODELS = [
157-
// ... existing
158-
"new-model",
159-
] as const;
160-
```
161-
162-
## Adding New Adapters
163-
164-
To implement per-model typing in a new adapter:
165-
166-
1. **Define provider option fragments**:
167-
168-
```typescript
169-
interface MyProviderBaseOptions {
170-
/* ... */
171-
}
172-
interface MyProviderStructuredOptions {
173-
/* ... */
174-
}
175-
interface MyProviderToolOptions {
176-
/* ... */
177-
}
178-
```
179-
180-
2. **Create the model type map**:
181-
182-
```typescript
183-
export type MyProviderModelProviderOptionsByName = {
184-
"model-a": MyProviderBaseOptions & MyProviderStructuredOptions;
185-
"model-b": MyProviderBaseOptions & MyProviderToolOptions;
186-
// ...
187-
};
188-
```
189-
190-
3. **Pass as 5th generic to BaseAdapter**:
191-
```typescript
192-
class MyProviderAdapter extends BaseAdapter<
193-
typeof MY_CHAT_MODELS,
194-
typeof MY_EMBEDDING_MODELS,
195-
MyProviderOptions,
196-
MyProviderEmbeddingOptions,
197-
MyProviderModelProviderOptionsByName // <-- Type map
198-
> {
199-
_modelProviderOptionsByName!: MyProviderModelProviderOptionsByName;
200-
}
201-
```
202-
203-
## Technical Notes
204-
205-
### Why Manual Type Maps?
206-
207-
Initially, we attempted automatic type extraction from `ModelMeta` using conditional types:
208-
209-
```typescript
210-
type OpenAIModelProviderOptions<TModel> =
211-
OpenAIModelMetaMap[TModel] extends ModelMeta<infer TProviderOptions>
212-
? TProviderOptions
213-
: unknown;
54+
error TS2353: Object literal may only specify known properties, and 'text' does not exist in type ...'.
21455
```
215-
216-
This approach failed because TypeScript's `satisfies` operator validates types but doesn't preserve generic type parameters in the resulting value. The value retains its concrete object type, not `ModelMeta<T>`, so the conditional type extraction fails.
217-
218-
**Solution**: Manually define the type map with explicit intersections for each model. This ensures accurate type narrowing and is the recommended approach.
219-
220-
### Type-Only Property
221-
222-
The `_modelProviderOptionsByName` property on adapters is a **type-only** property (definite assignment `!`). It exists purely for TypeScript's type system and is never accessed at runtime. This allows the core standalone functions to extract model-specific types via indexed access without runtime overhead.
223-
56+
22457
## Benefits
22558

22659
- **Compile-time safety**: Catch incorrect provider options before deployment

docs/reference/functions/chat.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ title: chat
99
function chat<TAdapter, TModel>(options): AsyncIterable<StreamChunk>;
1010
```
1111

12-
Defined in: [core/chat.ts:739](https://github.com/TanStack/ai/blob/main/packages/typescript/ai/src/core/chat.ts#L739)
12+
Defined in: [core/chat.ts:741](https://github.com/TanStack/ai/blob/main/packages/typescript/ai/src/core/chat.ts#L741)
1313

1414
Standalone chat streaming function with type inference from adapter
1515
Returns an async iterable of StreamChunks for streaming responses

0 commit comments

Comments
 (0)