@tanstack/ai 0.51.0 → 0.52.0

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.
@@ -99,6 +99,26 @@ function pruneMap(map: NullWideningMap): NullWideningMap | undefined {
99
99
  return Object.keys(map).length > 0 ? map : undefined
100
100
  }
101
101
 
102
+ function coerceArrayItems(items: JSONSchema | Array<JSONSchema>): {
103
+ schema: JSONSchema | Array<JSONSchema>
104
+ itemMap: NullWideningMap | Array<NullWideningMap> | undefined
105
+ } {
106
+ if (Array.isArray(items)) {
107
+ const nested = items.map((item) =>
108
+ makeStructuredOutputCompatible(item, item.required || []),
109
+ )
110
+ const itemMaps = nested.map((entry) => entry.nullWidening ?? {})
111
+ return {
112
+ schema: nested.map((entry) => entry.schema),
113
+ itemMap: itemMaps.some((entry) => Object.keys(entry).length > 0)
114
+ ? itemMaps
115
+ : undefined,
116
+ }
117
+ }
118
+ const nested = makeStructuredOutputCompatible(items, items.required || [])
119
+ return { schema: nested.schema, itemMap: nested.nullWidening }
120
+ }
121
+
102
122
  /**
103
123
  * Transform a JSON schema to be compatible with OpenAI's structured output requirements.
104
124
  * OpenAI requires:
@@ -146,18 +166,15 @@ function makeStructuredOutputCompatible(
146
166
  widenedHere = wasOptional
147
167
  childMap = nested.nullWidening
148
168
  } else if (prop.type === 'array' && prop.items) {
149
- const items = Array.isArray(prop.items) ? prop.items[0] : prop.items
150
- const nestedItems = items
151
- ? makeStructuredOutputCompatible(items, items.required || [])
152
- : undefined
169
+ const nestedItems = coerceArrayItems(prop.items)
153
170
  properties[propName] = {
154
171
  ...prop,
155
- items: nestedItems ? nestedItems.schema : prop.items,
172
+ items: nestedItems.schema,
156
173
  ...(wasOptional ? { type: ['array', 'null'] } : {}),
157
174
  }
158
175
  widenedHere = wasOptional
159
- childMap = nestedItems?.nullWidening
160
- ? { items: nestedItems.nullWidening }
176
+ childMap = nestedItems.itemMap
177
+ ? { items: nestedItems.itemMap }
161
178
  : undefined
162
179
  } else if (wasOptional) {
163
180
  // Make optional fields nullable by adding null to the type. Mark
@@ -188,17 +205,13 @@ function makeStructuredOutputCompatible(
188
205
  if (Object.keys(propertyMaps).length > 0) map.properties = propertyMaps
189
206
  }
190
207
 
191
- // Handle array types with object items
208
+ // Handle array item schemas. A tuple (`items: [a, b, …]`) keeps every
209
+ // position. A homogeneous schema stays a single items map so
210
+ // `undoNullWidening` applies it to every element.
192
211
  if (result.type === 'array' && result.items) {
193
- const items = Array.isArray(result.items) ? result.items[0] : result.items
194
- if (items) {
195
- const nestedItems = makeStructuredOutputCompatible(
196
- items,
197
- items.required || [],
198
- )
199
- result.items = nestedItems.schema
200
- if (nestedItems.nullWidening) map.items = nestedItems.nullWidening
201
- }
212
+ const nestedItems = coerceArrayItems(result.items)
213
+ result.items = nestedItems.schema
214
+ if (nestedItems.itemMap) map.items = nestedItems.itemMap
202
215
  }
203
216
 
204
217
  return { schema: result, nullWidening: pruneMap(map) }
package/src/index.ts CHANGED
@@ -283,6 +283,9 @@ export {
283
283
  createCapability,
284
284
  defineChatMiddleware,
285
285
  createChatMiddleware,
286
+ MetadataCapability,
287
+ getMetadata,
288
+ provideMetadata,
286
289
  } from './activities/chat/middleware/index'
287
290
  export type {
288
291
  Capability,
@@ -292,6 +295,7 @@ export type {
292
295
  CapabilityProvider,
293
296
  DefinedChatMiddleware,
294
297
  AnyChatMiddleware,
298
+ MetadataStore,
295
299
  } from './activities/chat/middleware/index'
296
300
  // Locks are a distributed-mutex primitive — coordination, not chat state — and
297
301
  // live behind their own subpath: `@tanstack/ai/locks` (see ./locks.ts).