@tanstack/ai 0.52.3 → 0.54.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.
Files changed (74) hide show
  1. package/README.md +14 -13
  2. package/dist/esm/activities/chat/index.js +5 -3
  3. package/dist/esm/activities/chat/index.js.map +1 -1
  4. package/dist/esm/activities/generateLiveVideo/adapter.d.ts +69 -0
  5. package/dist/esm/activities/generateLiveVideo/adapter.js +23 -0
  6. package/dist/esm/activities/generateLiveVideo/adapter.js.map +1 -0
  7. package/dist/esm/activities/generateLiveVideo/index.d.ts +99 -0
  8. package/dist/esm/activities/generateLiveVideo/index.js +162 -0
  9. package/dist/esm/activities/generateLiveVideo/index.js.map +1 -0
  10. package/dist/esm/activities/generateVideo/index.js +3 -1
  11. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  12. package/dist/esm/activities/generateWorld/adapter.d.ts +69 -0
  13. package/dist/esm/activities/generateWorld/adapter.js +23 -0
  14. package/dist/esm/activities/generateWorld/adapter.js.map +1 -0
  15. package/dist/esm/activities/generateWorld/index.d.ts +99 -0
  16. package/dist/esm/activities/generateWorld/index.js +162 -0
  17. package/dist/esm/activities/generateWorld/index.js.map +1 -0
  18. package/dist/esm/activities/index.d.ts +8 -2
  19. package/dist/esm/activities/index.js +11 -7
  20. package/dist/esm/activities/middleware/types.d.ts +1 -1
  21. package/dist/esm/activities/summarize/chat-stream-summarize.js +2 -1
  22. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  23. package/dist/esm/byok/define-provider.d.ts +6 -0
  24. package/dist/esm/byok/define-provider.js +2 -1
  25. package/dist/esm/byok/define-provider.js.map +1 -1
  26. package/dist/esm/byok/get-key.d.ts +7 -0
  27. package/dist/esm/byok/get-key.js +8 -1
  28. package/dist/esm/byok/get-key.js.map +1 -1
  29. package/dist/esm/byok/server.d.ts +1 -1
  30. package/dist/esm/byok/server.js +2 -2
  31. package/dist/esm/client.d.ts +4 -2
  32. package/dist/esm/client.js +3 -1
  33. package/dist/esm/client.js.map +1 -1
  34. package/dist/esm/index.d.ts +4 -2
  35. package/dist/esm/index.js +3 -1
  36. package/dist/esm/middlewares/otel.js +3 -1
  37. package/dist/esm/middlewares/otel.js.map +1 -1
  38. package/dist/esm/types.d.ts +112 -0
  39. package/package.json +2 -2
  40. package/skills/ai-core/adapter-configuration/SKILL.md +103 -54
  41. package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +39 -21
  42. package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +5 -0
  43. package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +14 -6
  44. package/skills/ai-core/adapter-configuration/references/grok-adapter.md +33 -25
  45. package/skills/ai-core/adapter-configuration/references/groq-adapter.md +7 -2
  46. package/skills/ai-core/adapter-configuration/references/ollama-adapter.md +25 -12
  47. package/skills/ai-core/adapter-configuration/references/openai-adapter.md +19 -9
  48. package/skills/ai-core/adapter-configuration/references/openrouter-adapter.md +34 -21
  49. package/skills/ai-core/ag-ui-protocol/SKILL.md +16 -10
  50. package/skills/ai-core/chat-experience/SKILL.md +228 -108
  51. package/skills/ai-core/client-persistence/SKILL.md +21 -9
  52. package/skills/ai-core/custom-backend-integration/SKILL.md +86 -52
  53. package/skills/ai-core/debug-logging/SKILL.md +100 -18
  54. package/skills/ai-core/locks/SKILL.md +35 -7
  55. package/skills/ai-core/media-generation/SKILL.md +114 -49
  56. package/skills/ai-core/middleware/SKILL.md +174 -69
  57. package/skills/ai-core/structured-outputs/SKILL.md +99 -49
  58. package/skills/ai-core/tool-calling/SKILL.md +245 -158
  59. package/src/activities/chat/index.ts +6 -7
  60. package/src/activities/generateLiveVideo/adapter.ts +99 -0
  61. package/src/activities/generateLiveVideo/index.ts +339 -0
  62. package/src/activities/generateVideo/index.ts +3 -4
  63. package/src/activities/generateWorld/adapter.ts +96 -0
  64. package/src/activities/generateWorld/index.ts +339 -0
  65. package/src/activities/index.ts +44 -0
  66. package/src/activities/middleware/types.ts +2 -0
  67. package/src/activities/summarize/chat-stream-summarize.ts +2 -0
  68. package/src/byok/define-provider.ts +7 -0
  69. package/src/byok/get-key.ts +18 -0
  70. package/src/byok/server.ts +1 -1
  71. package/src/client.ts +8 -0
  72. package/src/index.ts +8 -0
  73. package/src/middlewares/otel.ts +2 -0
  74. package/src/types.ts +128 -0
@@ -1245,7 +1245,7 @@ class TextEngine<
1245
1245
  ...this.deferredModelRunFinishedChunks,
1246
1246
  )
1247
1247
  this.deferredModelRunFinishedChunks = []
1248
- } else {
1248
+ } else if (!this.finalStructuredOutput?.nativeCombined) {
1249
1249
  yield* this.flushDeferredModelRunFinishedChunks()
1250
1250
  }
1251
1251
  } else {
@@ -1279,6 +1279,9 @@ class TextEngine<
1279
1279
  ) {
1280
1280
  if (this.finalStructuredOutput.nativeCombined === true) {
1281
1281
  yield* this.harvestCombinedStructuredOutput()
1282
+ if (!this.finalizationError && !this.isCancelled()) {
1283
+ yield* this.flushDeferredModelRunFinishedChunks()
1284
+ }
1282
1285
  } else {
1283
1286
  yield* this.runStructuredFinalization()
1284
1287
  }
@@ -3926,12 +3929,8 @@ class TextEngine<
3926
3929
  }
3927
3930
 
3928
3931
  // On success, emit the synthetic `structured-output.complete` carrying
3929
- // the parsed object + raw text. Pin the messageId so the client-side
3930
- // handler can target the right UIMessage even when the agent loop's
3931
- // terminal RUN_FINISHED has already cleared `activeMessageIds` (the
3932
- // complete event yields AFTER the loop ends, by which point
3933
- // `getActiveAssistantMessageId()` returns null and would otherwise drop
3934
- // the event silently).
3932
+ // the parsed object + raw text before the deferred RUN_FINISHED. Pin
3933
+ // the messageId so the client targets the schema-constrained turn.
3935
3934
  if (
3936
3935
  this.structuredOutputResult &&
3937
3936
  !this.finalizationError &&
@@ -0,0 +1,99 @@
1
+ import type {
2
+ LiveVideoGenerationOptions,
3
+ LiveVideoGenerationResult,
4
+ } from '../../types'
5
+
6
+ /**
7
+ * Configuration for live generation adapter instances.
8
+ *
9
+ * @experimental Live generation is an experimental feature and may change.
10
+ */
11
+ export interface LiveVideoAdapterConfig {
12
+ apiKey?: string
13
+ baseUrl?: string
14
+ timeout?: number
15
+ maxRetries?: number
16
+ headers?: Record<string, string>
17
+ }
18
+
19
+ /**
20
+ * Live adapter interface with pre-resolved generics.
21
+ *
22
+ * An adapter is created by a provider function: `provider('model')` → `adapter`.
23
+ * All type resolution happens at the provider call site, not in this interface.
24
+ *
25
+ * Generic parameters:
26
+ * - TModel: The specific model name (e.g. 'helios')
27
+ * - TProviderOptions: Provider-specific options (already resolved)
28
+ *
29
+ * @experimental Live generation is an experimental feature and may change.
30
+ */
31
+ export interface LiveVideoAdapter<
32
+ TModel extends string = string,
33
+ TProviderOptions extends object = Record<string, unknown>,
34
+ > {
35
+ /** Discriminator for adapter kind - used to determine API shape */
36
+ readonly kind: 'liveVideo'
37
+ /** Adapter name identifier */
38
+ readonly name: string
39
+ /** The model this adapter is configured for */
40
+ readonly model: TModel
41
+
42
+ /**
43
+ * @internal Type-only properties for inference. Not assigned at runtime.
44
+ */
45
+ '~types': {
46
+ providerOptions: TProviderOptions
47
+ }
48
+
49
+ /**
50
+ * Open a live video session from a prompt.
51
+ *
52
+ * Server adapters typically mint a short-lived token and return it with the
53
+ * prompt so a browser can connect, set the prompt, and start streaming.
54
+ */
55
+ createLiveVideo: (
56
+ options: LiveVideoGenerationOptions<TProviderOptions>,
57
+ ) => Promise<LiveVideoGenerationResult>
58
+ }
59
+
60
+ /**
61
+ * A LiveVideoAdapter with any/unknown type parameters.
62
+ * Useful as a constraint in generic functions and interfaces.
63
+ */
64
+ export type AnyLiveVideoAdapter = LiveVideoAdapter<any, any>
65
+
66
+ /**
67
+ * Abstract base class for live generation adapters.
68
+ * Extend this class to implement a live adapter for a specific provider.
69
+ *
70
+ * @experimental Live generation is an experimental feature and may change.
71
+ */
72
+ export abstract class BaseLiveVideoAdapter<
73
+ TModel extends string = string,
74
+ TProviderOptions extends object = Record<string, unknown>,
75
+ > implements LiveVideoAdapter<TModel, TProviderOptions> {
76
+ readonly kind = 'liveVideo' as const
77
+ abstract readonly name: string
78
+ readonly model: TModel
79
+
80
+ // Type-only property - never assigned at runtime
81
+ declare '~types': {
82
+ providerOptions: TProviderOptions
83
+ }
84
+
85
+ protected config: LiveVideoAdapterConfig
86
+
87
+ constructor(model: TModel, config: LiveVideoAdapterConfig = {}) {
88
+ this.config = config
89
+ this.model = model
90
+ }
91
+
92
+ abstract createLiveVideo(
93
+ options: LiveVideoGenerationOptions<TProviderOptions>,
94
+ ): Promise<LiveVideoGenerationResult>
95
+
96
+ protected generateId(): string {
97
+ return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}`
98
+ }
99
+ }
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Live Activity (Experimental)
3
+ *
4
+ * Mints a session token for a live, prompt-steerable video session. Unlike
5
+ * generateVideo (a job that finishes with a URL), the browser then connects
6
+ * with the token, sets the prompt, and streams until stop/close.
7
+ *
8
+ * @experimental Live generation is an experimental feature and may change.
9
+ */
10
+
11
+ import { aiEventClient } from '@tanstack/ai-event-client'
12
+ import { streamGenerationResult } from '../stream-generation-result.js'
13
+ import { resolveDebugOption } from '../../logger/resolve'
14
+ import {
15
+ applyGenerationResultTransforms,
16
+ createGenerationContext,
17
+ runGenerationAbort,
18
+ runGenerationError,
19
+ runGenerationFinish,
20
+ runGenerationStart,
21
+ runGenerationUsage,
22
+ } from '../middleware/run'
23
+ import {
24
+ abortReasonMessage,
25
+ createActivityAbortControls,
26
+ isActivityAbortError,
27
+ raceWithAbort,
28
+ } from '../../utilities/activity-abort'
29
+ import type { InternalLogger } from '../../logger/internal-logger'
30
+ import type { DebugOption } from '../../logger/types'
31
+ import type { GenerationMiddleware } from '../middleware/types'
32
+ import type { LiveVideoAdapter } from './adapter'
33
+ import type { StreamChunk, LiveVideoGenerationResult } from '../../types'
34
+
35
+ // ===========================
36
+ // Activity Kind
37
+ // ===========================
38
+
39
+ /** The adapter kind this activity handles */
40
+ export const kind = 'liveVideo' as const
41
+
42
+ // ===========================
43
+ // Type Extraction Helpers
44
+ // ===========================
45
+
46
+ /**
47
+ * Extract provider options from a LiveVideoAdapter via ~types.
48
+ */
49
+ export type LiveVideoProviderOptions<TAdapter> = TAdapter extends {
50
+ '~types': { providerOptions: infer P extends object }
51
+ }
52
+ ? P
53
+ : object
54
+
55
+ // ===========================
56
+ // Activity Options Type
57
+ // ===========================
58
+
59
+ /**
60
+ * Options for the live generation activity.
61
+ * The model is extracted from the adapter's model property.
62
+ *
63
+ * @template TAdapter - The live adapter type
64
+ * @template TStream - Whether to stream the output
65
+ *
66
+ * @experimental Live generation is an experimental feature and may change.
67
+ */
68
+ export interface LiveVideoActivityOptions<
69
+ TAdapter extends LiveVideoAdapter<string, LiveVideoProviderOptions<TAdapter>>,
70
+ TStream extends boolean = false,
71
+ > {
72
+ /** The live adapter to use (must be created with a model) */
73
+ adapter: TAdapter & { kind: typeof kind }
74
+ /** Natural-language description of the shot or scene */
75
+ prompt: string
76
+ /** Provider-specific options for live generation */
77
+ modelOptions?: LiveVideoProviderOptions<TAdapter>
78
+ /**
79
+ * Whether to wrap the token result as StreamChunks for SSE transport.
80
+ * This is not the live video. When false or omitted, returns
81
+ * Promise<LiveVideoGenerationResult>.
82
+ *
83
+ * @default false
84
+ */
85
+ stream?: TStream
86
+ /**
87
+ * Enable debug logging. Pass `true` to enable all categories, `false` to
88
+ * silence everything including errors, or a `DebugConfig` object for granular
89
+ * control and/or a custom `Logger`.
90
+ */
91
+ debug?: DebugOption
92
+ /**
93
+ * Observe-only middleware notified on start, usage, success, and error. Pass
94
+ * `otelMiddleware()` to emit OpenTelemetry spans, or implement the
95
+ * `GenerationMiddleware` contract for a custom backend.
96
+ */
97
+ middleware?: Array<GenerationMiddleware>
98
+ /** Stable conversation/thread id for correlating this run when persisted. */
99
+ threadId?: string
100
+ /** Stable run id for correlating this run when persisted. */
101
+ runId?: string
102
+ /**
103
+ * Maximum duration of the token mint in milliseconds.
104
+ * No SDK-wide default. Composed with {@link abortSignal}; the first abort wins.
105
+ */
106
+ timeout?: number
107
+ /**
108
+ * Caller cancellation signal (request disconnects, job/runtime cancellation).
109
+ * Composed with {@link timeout} into an effective signal forwarded to the
110
+ * adapter. Request-specific — not stored on global provider client config.
111
+ */
112
+ abortSignal?: AbortSignal
113
+ }
114
+
115
+ // ===========================
116
+ // Activity Result Type
117
+ // ===========================
118
+
119
+ /**
120
+ * Result type for the live generation activity.
121
+ * - If stream is true: AsyncIterable<StreamChunk>
122
+ * - Otherwise: Promise<LiveVideoGenerationResult>
123
+ */
124
+ export type LiveVideoActivityResult<TStream extends boolean = false> =
125
+ TStream extends true
126
+ ? AsyncIterable<StreamChunk>
127
+ : Promise<LiveVideoGenerationResult>
128
+
129
+ function createId(prefix: string): string {
130
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
131
+ }
132
+
133
+ // ===========================
134
+ // Activity Implementation
135
+ // ===========================
136
+
137
+ /**
138
+ * Live generation activity - opens a live, prompt-steerable video session.
139
+ *
140
+ * @example Mint a session token on the server
141
+ * ```ts
142
+ * import { generateLiveVideo } from '@tanstack/ai'
143
+ * import { reactorVideo } from '@tanstack/ai-reactor'
144
+ *
145
+ * const live = await generateLiveVideo({
146
+ * adapter: reactorVideo('helios'),
147
+ * prompt: 'A red sports car powerslides a mountain hairpin',
148
+ * })
149
+ *
150
+ * // Hand live.token, live.model, and live.prompt to the browser.
151
+ * ```
152
+ *
153
+ * @experimental Live generation is an experimental feature and may change.
154
+ */
155
+ export function generateLiveVideo<
156
+ TAdapter extends LiveVideoAdapter<string, LiveVideoProviderOptions<TAdapter>>,
157
+ TStream extends boolean = false,
158
+ >(
159
+ options: LiveVideoActivityOptions<TAdapter, TStream>,
160
+ ): LiveVideoActivityResult<TStream> {
161
+ if (options.stream) {
162
+ return streamGenerationResult(
163
+ (resolved) => runGenerateLiveVideo({ ...options, runId: resolved.runId }),
164
+ options,
165
+ ) as LiveVideoActivityResult<TStream>
166
+ }
167
+ return runGenerateLiveVideo(options) as LiveVideoActivityResult<TStream>
168
+ }
169
+
170
+ /**
171
+ * Run the core live generation logic (non-streaming).
172
+ */
173
+ async function runGenerateLiveVideo<
174
+ TAdapter extends LiveVideoAdapter<string, LiveVideoProviderOptions<TAdapter>>,
175
+ >(
176
+ options: LiveVideoActivityOptions<TAdapter, boolean>,
177
+ ): Promise<LiveVideoGenerationResult> {
178
+ const {
179
+ adapter,
180
+ stream: _stream,
181
+ debug: _debug,
182
+ middleware,
183
+ threadId,
184
+ runId,
185
+ timeout,
186
+ abortSignal: callerAbortSignal,
187
+ ...rest
188
+ } = options
189
+ const model = adapter.model
190
+ const requestId = createId('liveVideo')
191
+ const startTime = Date.now()
192
+ const logger: InternalLogger = resolveDebugOption(options.debug)
193
+ const abortControls = createActivityAbortControls({
194
+ timeout,
195
+ abortSignal: callerAbortSignal,
196
+ })
197
+ const providerName =
198
+ (adapter as { name?: string; provider?: string }).provider ??
199
+ (adapter as { name?: string }).name ??
200
+ 'unknown'
201
+
202
+ const mwCtx = createGenerationContext({
203
+ requestId,
204
+ activity: 'liveVideo',
205
+ provider: adapter.name,
206
+ model,
207
+ modelOptions: rest.modelOptions,
208
+ threadId,
209
+ runId,
210
+ artifactInputs: { prompt: rest.prompt },
211
+ createId,
212
+ })
213
+
214
+ await runGenerationStart(middleware, mwCtx)
215
+
216
+ aiEventClient.emit('liveVideo:request:started', {
217
+ requestId,
218
+ provider: adapter.name,
219
+ model,
220
+ prompt: rest.prompt,
221
+ timestamp: startTime,
222
+ ...(rest.modelOptions !== undefined && {
223
+ modelOptions: rest.modelOptions as Record<string, unknown>,
224
+ }),
225
+ })
226
+
227
+ logger.request(`activity=generateLiveVideo provider=${providerName}`, {
228
+ provider: providerName,
229
+ model,
230
+ })
231
+
232
+ try {
233
+ const rawResult = await raceWithAbort(
234
+ adapter.createLiveVideo({
235
+ ...rest,
236
+ model,
237
+ logger,
238
+ ...(abortControls.signal ? { abortSignal: abortControls.signal } : {}),
239
+ }),
240
+ abortControls.signal,
241
+ )
242
+ abortControls.clear()
243
+ const result = await applyGenerationResultTransforms(mwCtx, rawResult)
244
+ const elapsedMs = Date.now() - startTime
245
+
246
+ aiEventClient.emit('liveVideo:request:completed', {
247
+ requestId,
248
+ provider: adapter.name,
249
+ model: result.model,
250
+ prompt: result.prompt,
251
+ status: result.status,
252
+ duration: elapsedMs,
253
+ timestamp: Date.now(),
254
+ ...(rest.modelOptions !== undefined && {
255
+ modelOptions: rest.modelOptions as Record<string, unknown>,
256
+ }),
257
+ })
258
+
259
+ if (result.usage) {
260
+ aiEventClient.emit('liveVideo:usage', {
261
+ requestId,
262
+ model: result.model,
263
+ usage: result.usage,
264
+ timestamp: Date.now(),
265
+ ...(rest.modelOptions !== undefined && {
266
+ modelOptions: rest.modelOptions as Record<string, unknown>,
267
+ }),
268
+ })
269
+ }
270
+
271
+ logger.output(`activity=generateLiveVideo provider=${providerName}`, {
272
+ model: result.model,
273
+ status: result.status,
274
+ })
275
+
276
+ if (result.usage) await runGenerationUsage(middleware, mwCtx, result.usage)
277
+ await runGenerationFinish(middleware, mwCtx, {
278
+ duration: elapsedMs,
279
+ usage: result.usage,
280
+ })
281
+
282
+ return result
283
+ } catch (error) {
284
+ abortControls.clear()
285
+ const elapsedMs = Date.now() - startTime
286
+ const err = error as Error
287
+ aiEventClient.emit('liveVideo:request:error', {
288
+ requestId,
289
+ provider: adapter.name,
290
+ model,
291
+ error: { message: err.message, name: err.name },
292
+ duration: elapsedMs,
293
+ timestamp: Date.now(),
294
+ ...(rest.modelOptions !== undefined && {
295
+ modelOptions: rest.modelOptions as Record<string, unknown>,
296
+ }),
297
+ })
298
+ if (isActivityAbortError(error, abortControls.signal)) {
299
+ await runGenerationAbort(middleware, mwCtx, {
300
+ reason: abortReasonMessage(error, abortControls.signal),
301
+ duration: elapsedMs,
302
+ })
303
+ } else {
304
+ await runGenerationError(middleware, mwCtx, {
305
+ error,
306
+ duration: elapsedMs,
307
+ })
308
+ }
309
+ logger.errors('generateLiveVideo activity failed', {
310
+ error,
311
+ source: 'generateLiveVideo',
312
+ })
313
+ throw error
314
+ }
315
+ }
316
+
317
+ // ===========================
318
+ // Options Factory
319
+ // ===========================
320
+
321
+ /**
322
+ * Create typed options for the generateLiveVideo() function without executing.
323
+ */
324
+ export function createLiveVideoOptions<
325
+ TAdapter extends LiveVideoAdapter<string, LiveVideoProviderOptions<TAdapter>>,
326
+ TStream extends boolean = false,
327
+ >(
328
+ options: LiveVideoActivityOptions<TAdapter, TStream>,
329
+ ): LiveVideoActivityOptions<TAdapter, TStream> {
330
+ return options
331
+ }
332
+
333
+ // Re-export adapter types
334
+ export type {
335
+ LiveVideoAdapter,
336
+ LiveVideoAdapterConfig,
337
+ AnyLiveVideoAdapter,
338
+ } from './adapter'
339
+ export { BaseLiveVideoAdapter } from './adapter'
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Video Activity (Experimental)
3
3
  *
4
- * Generates videos from text prompts using a jobs/polling architecture.
4
+ * Generates videos from text prompts. Adapters use a jobs/polling
5
+ * architecture: create a job, poll for status, then fetch a download URL.
6
+ * For a live, prompt-steerable stream, use generateLiveVideo().
5
7
  * This is a self-contained module with implementation, types, and JSDoc.
6
8
  *
7
9
  * @experimental Video generation is an experimental feature and may change.
@@ -537,9 +539,6 @@ async function runCreateVideoJob<
537
539
 
538
540
  const mwCtx = contextFor(videoRunIdForJob(adapter.name, jobResult.jobId))
539
541
  await runGenerationStart(middleware, mwCtx)
540
- // Transforms see the submission result (no url yet, so nothing to copy into a
541
- // blob store) purely so the run record captures the jobId and any prompt
542
- // inputs. No finish hook: the run is still running.
543
542
  return await applyGenerationResultTransforms(mwCtx, jobResult)
544
543
  }
545
544
 
@@ -0,0 +1,96 @@
1
+ import type { WorldGenerationOptions, WorldGenerationResult } from '../../types'
2
+
3
+ /**
4
+ * Configuration for world generation adapter instances.
5
+ *
6
+ * @experimental World generation is an experimental feature and may change.
7
+ */
8
+ export interface WorldAdapterConfig {
9
+ apiKey?: string
10
+ baseUrl?: string
11
+ timeout?: number
12
+ maxRetries?: number
13
+ headers?: Record<string, string>
14
+ }
15
+
16
+ /**
17
+ * World adapter interface with pre-resolved generics.
18
+ *
19
+ * An adapter is created by a provider function: `provider('model')` → `adapter`.
20
+ * All type resolution happens at the provider call site, not in this interface.
21
+ *
22
+ * Generic parameters:
23
+ * - TModel: The specific model name (e.g. 'visko-orbis-stable')
24
+ * - TProviderOptions: Provider-specific options (already resolved)
25
+ *
26
+ * @experimental World generation is an experimental feature and may change.
27
+ */
28
+ export interface WorldAdapter<
29
+ TModel extends string = string,
30
+ TProviderOptions extends object = Record<string, unknown>,
31
+ > {
32
+ /** Discriminator for adapter kind - used to determine API shape */
33
+ readonly kind: 'world'
34
+ /** Adapter name identifier */
35
+ readonly name: string
36
+ /** The model this adapter is configured for */
37
+ readonly model: TModel
38
+
39
+ /**
40
+ * @internal Type-only properties for inference. Not assigned at runtime.
41
+ */
42
+ '~types': {
43
+ providerOptions: TProviderOptions
44
+ }
45
+
46
+ /**
47
+ * Open a world session from a prompt.
48
+ *
49
+ * Server adapters typically mint a short-lived token and return it with the
50
+ * prompt so a browser can connect, set the prompt, and start streaming.
51
+ */
52
+ createWorld: (
53
+ options: WorldGenerationOptions<TProviderOptions>,
54
+ ) => Promise<WorldGenerationResult>
55
+ }
56
+
57
+ /**
58
+ * A WorldAdapter with any/unknown type parameters.
59
+ * Useful as a constraint in generic functions and interfaces.
60
+ */
61
+ export type AnyWorldAdapter = WorldAdapter<any, any>
62
+
63
+ /**
64
+ * Abstract base class for world generation adapters.
65
+ * Extend this class to implement a world adapter for a specific provider.
66
+ *
67
+ * @experimental World generation is an experimental feature and may change.
68
+ */
69
+ export abstract class BaseWorldAdapter<
70
+ TModel extends string = string,
71
+ TProviderOptions extends object = Record<string, unknown>,
72
+ > implements WorldAdapter<TModel, TProviderOptions> {
73
+ readonly kind = 'world' as const
74
+ abstract readonly name: string
75
+ readonly model: TModel
76
+
77
+ // Type-only property - never assigned at runtime
78
+ declare '~types': {
79
+ providerOptions: TProviderOptions
80
+ }
81
+
82
+ protected config: WorldAdapterConfig
83
+
84
+ constructor(model: TModel, config: WorldAdapterConfig = {}) {
85
+ this.config = config
86
+ this.model = model
87
+ }
88
+
89
+ abstract createWorld(
90
+ options: WorldGenerationOptions<TProviderOptions>,
91
+ ): Promise<WorldGenerationResult>
92
+
93
+ protected generateId(): string {
94
+ return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}`
95
+ }
96
+ }