@tanstack/ai-worldlabs 0.0.0 → 0.1.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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -2
  3. package/dist/esm/adapters/world.d.ts +38 -0
  4. package/dist/esm/adapters/world.js +314 -0
  5. package/dist/esm/adapters/world.js.map +1 -0
  6. package/dist/esm/byok.d.ts +1 -0
  7. package/dist/esm/byok.js +11 -0
  8. package/dist/esm/byok.js.map +1 -0
  9. package/dist/esm/generated/client/client.gen.d.ts +2 -0
  10. package/dist/esm/generated/client/client.gen.js +174 -0
  11. package/dist/esm/generated/client/client.gen.js.map +1 -0
  12. package/dist/esm/generated/client/index.d.ts +10 -0
  13. package/dist/esm/generated/client/index.js +5 -0
  14. package/dist/esm/generated/client/types.gen.d.ts +120 -0
  15. package/dist/esm/generated/client/utils.gen.d.ts +37 -0
  16. package/dist/esm/generated/client/utils.gen.js +177 -0
  17. package/dist/esm/generated/client/utils.gen.js.map +1 -0
  18. package/dist/esm/generated/client.d.ts +1 -0
  19. package/dist/esm/generated/client.gen.d.ts +12 -0
  20. package/dist/esm/generated/client.gen.js +9 -0
  21. package/dist/esm/generated/client.gen.js.map +1 -0
  22. package/dist/esm/generated/client.js +5 -0
  23. package/dist/esm/generated/core/auth.gen.d.ts +25 -0
  24. package/dist/esm/generated/core/auth.gen.js +12 -0
  25. package/dist/esm/generated/core/auth.gen.js.map +1 -0
  26. package/dist/esm/generated/core/bodySerializer.gen.d.ts +25 -0
  27. package/dist/esm/generated/core/bodySerializer.gen.js +6 -0
  28. package/dist/esm/generated/core/bodySerializer.gen.js.map +1 -0
  29. package/dist/esm/generated/core/params.gen.d.ts +43 -0
  30. package/dist/esm/generated/core/params.gen.js +9 -0
  31. package/dist/esm/generated/core/params.gen.js.map +1 -0
  32. package/dist/esm/generated/core/pathSerializer.gen.d.ts +33 -0
  33. package/dist/esm/generated/core/pathSerializer.gen.js +82 -0
  34. package/dist/esm/generated/core/pathSerializer.gen.js.map +1 -0
  35. package/dist/esm/generated/core/queryKeySerializer.gen.d.ts +18 -0
  36. package/dist/esm/generated/core/serverSentEvents.gen.d.ts +71 -0
  37. package/dist/esm/generated/core/serverSentEvents.gen.js +96 -0
  38. package/dist/esm/generated/core/serverSentEvents.gen.js.map +1 -0
  39. package/dist/esm/generated/core/types.gen.d.ts +83 -0
  40. package/dist/esm/generated/core/utils.gen.d.ts +19 -0
  41. package/dist/esm/generated/core/utils.gen.js +78 -0
  42. package/dist/esm/generated/core/utils.gen.js.map +1 -0
  43. package/dist/esm/generated/index.d.ts +2 -0
  44. package/dist/esm/generated/sdk.gen.d.ts +275 -0
  45. package/dist/esm/generated/sdk.gen.js +79 -0
  46. package/dist/esm/generated/sdk.gen.js.map +1 -0
  47. package/dist/esm/generated/types.gen.d.ts +1610 -0
  48. package/dist/esm/index.d.ts +5 -0
  49. package/dist/esm/index.js +4 -0
  50. package/dist/esm/model-meta.d.ts +63 -0
  51. package/dist/esm/model-meta.js +17 -0
  52. package/dist/esm/model-meta.js.map +1 -0
  53. package/dist/esm/utils/client.d.ts +16 -0
  54. package/dist/esm/utils/client.js +35 -0
  55. package/dist/esm/utils/client.js.map +1 -0
  56. package/package.json +54 -6
  57. package/src/adapters/world.ts +535 -0
  58. package/src/byok.ts +7 -0
  59. package/src/generated/client/client.gen.ts +301 -0
  60. package/src/generated/client/index.ts +27 -0
  61. package/src/generated/client/types.gen.ts +247 -0
  62. package/src/generated/client/utils.gen.ts +332 -0
  63. package/src/generated/client.gen.ts +26 -0
  64. package/src/generated/client.ts +3 -0
  65. package/src/generated/core/auth.gen.ts +48 -0
  66. package/src/generated/core/bodySerializer.gen.ts +96 -0
  67. package/src/generated/core/params.gen.ts +186 -0
  68. package/src/generated/core/pathSerializer.gen.ts +186 -0
  69. package/src/generated/core/queryKeySerializer.gen.ts +139 -0
  70. package/src/generated/core/serverSentEvents.gen.ts +265 -0
  71. package/src/generated/core/types.gen.ts +124 -0
  72. package/src/generated/core/utils.gen.ts +146 -0
  73. package/src/generated/index.ts +113 -0
  74. package/src/generated/sdk.gen.ts +531 -0
  75. package/src/generated/types.gen.ts +1767 -0
  76. package/src/index.ts +20 -0
  77. package/src/model-meta.ts +79 -0
  78. package/src/utils/client.ts +50 -0
@@ -0,0 +1,535 @@
1
+ import { BaseWorldAdapter } from '@tanstack/ai/adapters'
2
+ import {
3
+ generateWorldMarbleV1WorldsGeneratePost,
4
+ getOperationMarbleV1OperationsOperationIdGet,
5
+ } from '../generated/sdk.gen'
6
+ import {
7
+ createWorldLabsClient,
8
+ getWorldLabsApiKeyFromEnv,
9
+ } from '../utils/client'
10
+ import type {
11
+ WorldGenerationAssets,
12
+ WorldGenerationOptions,
13
+ WorldGenerationResult,
14
+ } from '@tanstack/ai'
15
+ import type { Client } from '../generated/client'
16
+ import type {
17
+ GenerateWorldResponse,
18
+ GetOperationResponseUnionWorldPanoDepthToRgbResultExportWorldResult,
19
+ World,
20
+ WorldsGenerateRequest,
21
+ } from '../generated/types.gen'
22
+ import type {
23
+ WorldLabsMediaRef,
24
+ WorldLabsWorldModel,
25
+ WorldLabsWorldProviderOptions,
26
+ } from '../model-meta'
27
+ import type { WorldLabsClientConfig } from '../utils/client'
28
+
29
+ export type { WorldLabsClientConfig }
30
+
31
+ type WorldOperation =
32
+ | GenerateWorldResponse
33
+ | GetOperationResponseUnionWorldPanoDepthToRgbResultExportWorldResult
34
+
35
+ const DEFAULT_POLL_INTERVAL_MS = 2_000
36
+
37
+ /**
38
+ * World Labs Marble adapter. Starts a world generation job and, by default,
39
+ * polls until the world is ready. `modelOptions.wait === false` returns
40
+ * `operationId` at once. A later `generateWorld` call cannot resume that id.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import { generateWorld } from '@tanstack/ai'
45
+ * import { worldlabsWorld } from '@tanstack/ai-worldlabs'
46
+ *
47
+ * const world = await generateWorld({
48
+ * adapter: worldlabsWorld('marble-1.1'),
49
+ * prompt: 'A mystical forest with glowing mushrooms',
50
+ * })
51
+ * ```
52
+ */
53
+ export class WorldLabsWorldAdapter<
54
+ TModel extends WorldLabsWorldModel,
55
+ > extends BaseWorldAdapter<TModel, WorldLabsWorldProviderOptions> {
56
+ readonly name = 'worldlabs' as const
57
+ private readonly client: Client
58
+
59
+ constructor(model: TModel, config: WorldLabsClientConfig = {}) {
60
+ super(model, {})
61
+ this.client = createWorldLabsClient(config).client
62
+ }
63
+
64
+ async createWorld(
65
+ options: WorldGenerationOptions<WorldLabsWorldProviderOptions>,
66
+ ): Promise<WorldGenerationResult> {
67
+ const { logger, prompt, abortSignal, modelOptions } = options
68
+
69
+ logger.request(
70
+ `activity=generateWorld provider=worldlabs model=${this.model}`,
71
+ { provider: 'worldlabs', model: this.model },
72
+ )
73
+
74
+ try {
75
+ const started = await this.startGeneration({
76
+ prompt,
77
+ modelOptions,
78
+ abortSignal,
79
+ })
80
+ requireOperationId(started)
81
+
82
+ if (started.done || started.error) {
83
+ return logWorldResult(
84
+ logger,
85
+ this.model,
86
+ completedResult({
87
+ prompt,
88
+ model: this.model,
89
+ operation: started,
90
+ }),
91
+ )
92
+ }
93
+
94
+ if (modelOptions?.wait === false) {
95
+ return logWorldResult(
96
+ logger,
97
+ this.model,
98
+ pendingResult({
99
+ prompt,
100
+ model: this.model,
101
+ operation: started,
102
+ }),
103
+ )
104
+ }
105
+
106
+ const finished = await this.pollUntilDone({
107
+ operationId: started.operation_id,
108
+ abortSignal,
109
+ pollIntervalMs: modelOptions?.pollIntervalMs,
110
+ })
111
+ return logWorldResult(
112
+ logger,
113
+ this.model,
114
+ completedResult({
115
+ prompt,
116
+ model: this.model,
117
+ operation: finished,
118
+ }),
119
+ )
120
+ } catch (error) {
121
+ if (!isAbortError(error)) {
122
+ logger.errors('worldlabs.createWorld failed', {
123
+ error,
124
+ source: 'worldlabs.createWorld',
125
+ })
126
+ }
127
+ throw error
128
+ }
129
+ }
130
+
131
+ private async startGeneration(args: {
132
+ prompt: string
133
+ modelOptions?: WorldLabsWorldProviderOptions
134
+ abortSignal?: AbortSignal
135
+ }): Promise<GenerateWorldResponse> {
136
+ const body: WorldsGenerateRequest = {
137
+ model: this.model,
138
+ world_prompt: buildWorldPrompt(args.prompt, args.modelOptions),
139
+ }
140
+ if (args.modelOptions?.displayName !== undefined) {
141
+ body.display_name = args.modelOptions.displayName
142
+ }
143
+ if (args.modelOptions?.seed !== undefined) {
144
+ body.seed = args.modelOptions.seed
145
+ }
146
+ if (args.modelOptions?.tags !== undefined) {
147
+ body.tags = args.modelOptions.tags
148
+ }
149
+ if (args.modelOptions?.permission !== undefined) {
150
+ body.permission = args.modelOptions.permission
151
+ }
152
+
153
+ const result = await generateWorldMarbleV1WorldsGeneratePost({
154
+ client: this.client,
155
+ body,
156
+ ...(args.abortSignal ? { signal: args.abortSignal } : {}),
157
+ })
158
+
159
+ if (result.error || !result.data) {
160
+ throw worldLabsRequestError(
161
+ 'World Labs generate request failed',
162
+ result.response,
163
+ result.error,
164
+ )
165
+ }
166
+
167
+ return result.data
168
+ }
169
+
170
+ private async pollUntilDone(args: {
171
+ operationId: string
172
+ abortSignal?: AbortSignal
173
+ pollIntervalMs?: number
174
+ }): Promise<WorldOperation> {
175
+ const interval =
176
+ args.pollIntervalMs && args.pollIntervalMs > 0
177
+ ? args.pollIntervalMs
178
+ : DEFAULT_POLL_INTERVAL_MS
179
+
180
+ for (;;) {
181
+ await sleep(interval, args.abortSignal)
182
+ const result = await getOperationMarbleV1OperationsOperationIdGet({
183
+ client: this.client,
184
+ path: { operation_id: args.operationId },
185
+ ...(args.abortSignal ? { signal: args.abortSignal } : {}),
186
+ })
187
+
188
+ if (result.error || !result.data) {
189
+ throw worldLabsRequestError(
190
+ 'World Labs operation poll failed',
191
+ result.response,
192
+ result.error,
193
+ )
194
+ }
195
+
196
+ if (typeof result.data.done !== 'boolean') {
197
+ throw new Error(
198
+ `World Labs operation ${args.operationId} returned an invalid poll payload.`,
199
+ )
200
+ }
201
+
202
+ if (result.data.done) {
203
+ return result.data
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Create a World Labs world adapter. Reads `WORLDLABS_API_KEY` when `apiKey`
211
+ * is omitted.
212
+ */
213
+ export function worldlabsWorld<TModel extends WorldLabsWorldModel>(
214
+ model: TModel,
215
+ config?: WorldLabsClientConfig,
216
+ ): WorldLabsWorldAdapter<TModel> {
217
+ const apiKey = config?.apiKey ?? getWorldLabsApiKeyFromEnv()
218
+ return createWorldLabsWorld(model, apiKey, config)
219
+ }
220
+
221
+ /**
222
+ * Create a World Labs world adapter with an explicit API key.
223
+ */
224
+ export function createWorldLabsWorld<TModel extends WorldLabsWorldModel>(
225
+ model: TModel,
226
+ apiKey: string,
227
+ config?: Omit<WorldLabsClientConfig, 'apiKey'>,
228
+ ): WorldLabsWorldAdapter<TModel> {
229
+ return new WorldLabsWorldAdapter(model, { ...config, apiKey })
230
+ }
231
+
232
+ function buildWorldPrompt(
233
+ prompt: string,
234
+ options?: WorldLabsWorldProviderOptions,
235
+ ): WorldsGenerateRequest['world_prompt'] {
236
+ if (
237
+ [options?.image, options?.images, options?.video].filter(Boolean).length > 1
238
+ ) {
239
+ throw new Error(
240
+ 'World Labs: pass only one of modelOptions.image, modelOptions.images, or modelOptions.video.',
241
+ )
242
+ }
243
+
244
+ const textPrompt = prompt.trim().length > 0 ? prompt : undefined
245
+ const shared = {
246
+ ...(textPrompt !== undefined ? { text_prompt: textPrompt } : {}),
247
+ ...(options?.disableRecaption !== undefined
248
+ ? { disable_recaption: options.disableRecaption }
249
+ : {}),
250
+ }
251
+
252
+ if (options?.image) {
253
+ return {
254
+ type: 'image',
255
+ image_prompt: toContentRef(options.image),
256
+ ...shared,
257
+ ...(options.isPano !== undefined ? { is_pano: options.isPano } : {}),
258
+ }
259
+ }
260
+
261
+ if (options?.images) {
262
+ return {
263
+ type: 'multi-image',
264
+ multi_image_prompt: options.images.map((image) => ({
265
+ content: toContentRef(image),
266
+ ...(image.azimuth !== undefined ? { azimuth: image.azimuth } : {}),
267
+ })),
268
+ ...shared,
269
+ }
270
+ }
271
+
272
+ if (options?.video) {
273
+ return {
274
+ type: 'video',
275
+ video_prompt: toContentRef(options.video),
276
+ ...shared,
277
+ }
278
+ }
279
+
280
+ return {
281
+ type: 'text',
282
+ text_prompt: prompt,
283
+ ...(options?.disableRecaption !== undefined
284
+ ? { disable_recaption: options.disableRecaption }
285
+ : {}),
286
+ }
287
+ }
288
+
289
+ function toContentRef(
290
+ ref: WorldLabsMediaRef,
291
+ ):
292
+ | { source: 'uri'; uri: string }
293
+ | { source: 'media_asset'; media_asset_id: string }
294
+ | { source: 'data_base64'; data_base64: string; extension?: string } {
295
+ const fields = [ref.uri, ref.mediaAssetId, ref.dataBase64].filter(
296
+ (value) => typeof value === 'string' && value.length > 0,
297
+ )
298
+ if (fields.length !== 1) {
299
+ throw new Error(
300
+ 'World Labs media ref needs exactly one of uri, mediaAssetId, or dataBase64.',
301
+ )
302
+ }
303
+ if (ref.uri) return { source: 'uri', uri: ref.uri }
304
+ if (ref.mediaAssetId) {
305
+ return { source: 'media_asset', media_asset_id: ref.mediaAssetId }
306
+ }
307
+ if (!ref.dataBase64) {
308
+ throw new Error(
309
+ 'World Labs media ref needs exactly one of uri, mediaAssetId, or dataBase64.',
310
+ )
311
+ }
312
+ return {
313
+ source: 'data_base64',
314
+ data_base64: ref.dataBase64,
315
+ ...(ref.extension !== undefined ? { extension: ref.extension } : {}),
316
+ }
317
+ }
318
+
319
+ function pendingResult(args: {
320
+ prompt: string
321
+ model: string
322
+ operation: GenerateWorldResponse
323
+ }): WorldGenerationResult {
324
+ const expiresAt = expiresAtMs(args.operation.expires_at)
325
+ return {
326
+ id: args.operation.operation_id,
327
+ model: args.model,
328
+ prompt: args.prompt,
329
+ status: 'waiting',
330
+ operationId: args.operation.operation_id,
331
+ worldId: worldIdFromMetadata(args.operation.metadata),
332
+ ...(expiresAt !== undefined ? { expiresAt } : {}),
333
+ }
334
+ }
335
+
336
+ function completedResult(args: {
337
+ prompt: string
338
+ model: string
339
+ operation: WorldOperation
340
+ }): WorldGenerationResult {
341
+ if (args.operation.error) {
342
+ const raw = args.operation.error.message?.trim()
343
+ const code = args.operation.error.code
344
+ throw new Error(
345
+ raw ||
346
+ `World Labs operation failed${code != null ? ` (code ${code})` : ''}`,
347
+ )
348
+ }
349
+
350
+ const world = worldFromResponse(args.operation.response)
351
+ if (!world) {
352
+ throw new Error(
353
+ `World Labs operation ${args.operation.operation_id} completed without a world.`,
354
+ )
355
+ }
356
+
357
+ const assets = mapAssets(world.assets)
358
+ const expiresAt = expiresAtMs(args.operation.expires_at)
359
+ return {
360
+ id: world.world_id,
361
+ model: world.model ?? args.model,
362
+ prompt: args.prompt,
363
+ status: 'ready',
364
+ url: world.world_marble_url,
365
+ worldId: world.world_id,
366
+ operationId: args.operation.operation_id,
367
+ ...(assets ? { assets } : {}),
368
+ ...(expiresAt !== undefined ? { expiresAt } : {}),
369
+ }
370
+ }
371
+
372
+ function worldFromResponse(
373
+ response: WorldOperation['response'],
374
+ ): World | undefined {
375
+ if (!response || typeof response !== 'object') return undefined
376
+ if (!('world_id' in response) || !('world_marble_url' in response)) {
377
+ return undefined
378
+ }
379
+ const worldId = response.world_id
380
+ const url = response.world_marble_url
381
+ if (typeof worldId !== 'string' || worldId.length === 0) return undefined
382
+ if (typeof url !== 'string' || url.length === 0) return undefined
383
+ return response as World
384
+ }
385
+
386
+ function requireOperationId(data: GenerateWorldResponse): void {
387
+ if (typeof data.operation_id !== 'string' || data.operation_id.length === 0) {
388
+ throw new Error('World Labs generate request failed: missing operation_id')
389
+ }
390
+ }
391
+
392
+ function logWorldResult(
393
+ logger: WorldGenerationOptions['logger'],
394
+ model: string,
395
+ result: WorldGenerationResult,
396
+ ): WorldGenerationResult {
397
+ logger.output(`activity=generateWorld provider=worldlabs model=${model}`, {
398
+ model,
399
+ status: result.status,
400
+ })
401
+ return result
402
+ }
403
+
404
+ function isAbortError(error: unknown): boolean {
405
+ return (
406
+ error instanceof Error &&
407
+ (error.name === 'AbortError' ||
408
+ error.message === 'The operation was aborted')
409
+ )
410
+ }
411
+
412
+ function worldIdFromMetadata(
413
+ metadata: GenerateWorldResponse['metadata'],
414
+ ): string | undefined {
415
+ if (!metadata || typeof metadata !== 'object') return undefined
416
+ const worldId = metadata.world_id
417
+ return typeof worldId === 'string' && worldId.length > 0 ? worldId : undefined
418
+ }
419
+
420
+ function nonEmpty<T extends object>(value: T): T | undefined {
421
+ return Object.keys(value).length > 0 ? value : undefined
422
+ }
423
+
424
+ function mapAssets(assets: World['assets']): WorldGenerationAssets | undefined {
425
+ if (!assets) return undefined
426
+ const splats = mapSplatAssets(assets.splats)
427
+ const mesh = mapMeshAssets(assets.mesh)
428
+ return nonEmpty({
429
+ ...(assets.caption ? { caption: assets.caption } : {}),
430
+ ...(assets.thumbnail_url ? { thumbnailUrl: assets.thumbnail_url } : {}),
431
+ ...(splats ? { splats } : {}),
432
+ ...(mesh ? { mesh } : {}),
433
+ ...(assets.imagery?.pano_url
434
+ ? { imagery: { panoUrl: assets.imagery.pano_url } }
435
+ : {}),
436
+ })
437
+ }
438
+
439
+ function mapSplatAssets(
440
+ splats: NonNullable<World['assets']>['splats'],
441
+ ): WorldGenerationAssets['splats'] | undefined {
442
+ if (!splats) return undefined
443
+ return nonEmpty({
444
+ ...(splats.spz_urls && Object.keys(splats.spz_urls).length > 0
445
+ ? { spzUrls: splats.spz_urls }
446
+ : {}),
447
+ ...(splats.semantics_metadata?.metric_scale_factor != null
448
+ ? { metricScaleFactor: splats.semantics_metadata.metric_scale_factor }
449
+ : {}),
450
+ ...(splats.semantics_metadata?.ground_plane_offset != null
451
+ ? { groundPlaneOffset: splats.semantics_metadata.ground_plane_offset }
452
+ : {}),
453
+ })
454
+ }
455
+
456
+ function mapMeshAssets(
457
+ mesh: NonNullable<World['assets']>['mesh'],
458
+ ): WorldGenerationAssets['mesh'] | undefined {
459
+ if (!mesh) return undefined
460
+ return nonEmpty({
461
+ ...(mesh.collider_mesh_url
462
+ ? { colliderMeshUrl: mesh.collider_mesh_url }
463
+ : {}),
464
+ ...(mesh.hq_mesh_url ? { hqMeshUrl: mesh.hq_mesh_url } : {}),
465
+ ...(mesh.full_res_mesh_url
466
+ ? { fullResMeshUrl: mesh.full_res_mesh_url }
467
+ : {}),
468
+ })
469
+ }
470
+
471
+ function expiresAtMs(value: string | null | undefined): number | undefined {
472
+ if (!value) return undefined
473
+ const parsed = Date.parse(value)
474
+ return Number.isFinite(parsed) ? parsed : undefined
475
+ }
476
+
477
+ function worldLabsRequestError(
478
+ prefix: string,
479
+ response: Response | undefined,
480
+ error: unknown,
481
+ ): Error {
482
+ const status = response
483
+ ? `${response.status}${response.statusText ? ` ${response.statusText}` : ''}`
484
+ : undefined
485
+ const detail = errorDetail(error)
486
+ return new Error(
487
+ `${prefix}${status ? ` (${status})` : ''}${detail ? `: ${detail}` : ''}`,
488
+ )
489
+ }
490
+
491
+ function errorDetail(error: unknown): string | undefined {
492
+ if (typeof error === 'string' && error.length > 0) return error
493
+ if (typeof error === 'object' && error !== null) {
494
+ const record = error as { detail?: unknown; message?: unknown }
495
+ if (typeof record.detail === 'string' && record.detail.length > 0) {
496
+ return record.detail
497
+ }
498
+ if (typeof record.message === 'string' && record.message.length > 0) {
499
+ return record.message
500
+ }
501
+ try {
502
+ return JSON.stringify(error)
503
+ } catch {
504
+ return undefined
505
+ }
506
+ }
507
+ return undefined
508
+ }
509
+
510
+ function sleep(ms: number, signal?: AbortSignal): Promise<void> {
511
+ if (!signal) {
512
+ return new Promise((resolve) => setTimeout(resolve, ms))
513
+ }
514
+ if (signal.aborted) {
515
+ return Promise.reject(abortError(signal))
516
+ }
517
+ return new Promise((resolve, reject) => {
518
+ const timer = setTimeout(() => {
519
+ signal.removeEventListener('abort', onAbort)
520
+ resolve()
521
+ }, ms)
522
+ const onAbort = () => {
523
+ clearTimeout(timer)
524
+ reject(abortError(signal))
525
+ }
526
+ signal.addEventListener('abort', onAbort, { once: true })
527
+ })
528
+ }
529
+
530
+ function abortError(signal?: AbortSignal): Error {
531
+ if (signal?.reason instanceof Error) return signal.reason
532
+ const error = new Error('The operation was aborted')
533
+ error.name = 'AbortError'
534
+ return error
535
+ }
package/src/byok.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { defineByokProvider } from '@tanstack/ai/byok'
2
+
3
+ export const worldlabsByok = defineByokProvider({
4
+ id: 'worldlabs',
5
+ label: 'World Labs',
6
+ env: 'WORLDLABS_API_KEY',
7
+ })