@tanstack/ai-reactor 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tanner Linsley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ <div align="center">
2
+ <picture>
3
+ <source
4
+ media="(prefers-color-scheme: dark)"
5
+ srcset="https://tanstack.com/api/readme/ai.png?theme=dark"
6
+ />
7
+ <source
8
+ media="(prefers-color-scheme: light)"
9
+ srcset="https://tanstack.com/api/readme/ai.png"
10
+ />
11
+ <img
12
+ src="https://tanstack.com/api/readme/ai.png"
13
+ alt="TanStack AI"
14
+ width="900"
15
+ />
16
+ </picture>
17
+ </div>
18
+
19
+ <br />
20
+
21
+ # @tanstack/ai-reactor
22
+
23
+ Reactor adapter for TanStack AI live world and video generation (Orbis, Happy Oyster, LingBot, Helios, FastH3).
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install @tanstack/ai-reactor @tanstack/ai
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ### Server: mint a session token
34
+
35
+ ```typescript
36
+ import { generateWorld } from '@tanstack/ai'
37
+ import { reactorWorld } from '@tanstack/ai-reactor'
38
+
39
+ const world = await generateWorld({
40
+ adapter: reactorWorld('visko-orbis-stable'),
41
+ prompt: 'A neon cyberpunk city at night',
42
+ })
43
+
44
+ // Hand world.token, world.model, and world.prompt to the browser.
45
+ ```
46
+
47
+ ### Server: mint a live video session token
48
+
49
+ ```typescript
50
+ import { generateLiveVideo } from '@tanstack/ai'
51
+ import { reactorVideo } from '@tanstack/ai-reactor'
52
+
53
+ const live = await generateLiveVideo({
54
+ adapter: reactorVideo('helios'),
55
+ prompt: 'A neon cyberpunk city at night',
56
+ })
57
+
58
+ // Hand live.token, live.model, and live.prompt to the browser.
59
+ ```
60
+
61
+ Set `REACTOR_API_KEY`, or pass `apiKey` in the adapter config. The browser connects with `@reactor-team/js-sdk`. See the [Reactor adapter docs](https://tanstack.com/ai/latest/docs/adapters/reactor).
@@ -0,0 +1,35 @@
1
+ import { BaseLiveVideoAdapter } from '@tanstack/ai/adapters';
2
+ import { ReactorClientConfig } from '../utils/client.js';
3
+ import { LiveVideoGenerationOptions, LiveVideoGenerationResult } from '@tanstack/ai';
4
+ import { ReactorVideoModel, ReactorVideoProviderOptions } from '../model-meta.js';
5
+ /**
6
+ * Reactor live-video adapter. Mints a session-scoped JWT so a browser can
7
+ * connect with `@reactor-team/js-sdk`, set the prompt, and start the stream.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { generateLiveVideo } from '@tanstack/ai'
12
+ * import { reactorVideo } from '@tanstack/ai-reactor'
13
+ *
14
+ * const live = await generateLiveVideo({
15
+ * adapter: reactorVideo('helios'),
16
+ * prompt: 'A neon cyberpunk city at night',
17
+ * })
18
+ * ```
19
+ */
20
+ export declare class ReactorVideoAdapter<TModel extends ReactorVideoModel> extends BaseLiveVideoAdapter<TModel, ReactorVideoProviderOptions> {
21
+ readonly name: "reactor";
22
+ private readonly clientConfig;
23
+ private readonly apiKey;
24
+ constructor(model: TModel, config?: ReactorClientConfig);
25
+ createLiveVideo(options: LiveVideoGenerationOptions<ReactorVideoProviderOptions>): Promise<LiveVideoGenerationResult>;
26
+ }
27
+ /**
28
+ * Create a Reactor live-video adapter. Reads `REACTOR_API_KEY` when `apiKey` is
29
+ * omitted.
30
+ */
31
+ export declare function reactorVideo<TModel extends ReactorVideoModel>(model: TModel, config?: ReactorClientConfig): ReactorVideoAdapter<TModel>;
32
+ /**
33
+ * Create a Reactor live-video adapter with an explicit API key.
34
+ */
35
+ export declare function createReactorVideo<TModel extends ReactorVideoModel>(model: TModel, apiKey: string, config?: Omit<ReactorClientConfig, 'apiKey'>): ReactorVideoAdapter<TModel>;
@@ -0,0 +1,87 @@
1
+ import { mintReactorSessionToken, resolveReactorApiKey, resolveReactorApiUrl } from "../utils/client.js";
2
+ import { REACTOR_VIDEO_SLUGS } from "../model-meta.js";
3
+ import { BaseLiveVideoAdapter } from "@tanstack/ai/adapters";
4
+ import { generateId } from "@tanstack/ai-utils";
5
+ //#region src/adapters/video.ts
6
+ /**
7
+ * Reactor live-video adapter. Mints a session-scoped JWT so a browser can
8
+ * connect with `@reactor-team/js-sdk`, set the prompt, and start the stream.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { generateLiveVideo } from '@tanstack/ai'
13
+ * import { reactorVideo } from '@tanstack/ai-reactor'
14
+ *
15
+ * const live = await generateLiveVideo({
16
+ * adapter: reactorVideo('helios'),
17
+ * prompt: 'A neon cyberpunk city at night',
18
+ * })
19
+ * ```
20
+ */
21
+ var ReactorVideoAdapter = class extends BaseLiveVideoAdapter {
22
+ name = "reactor";
23
+ clientConfig;
24
+ apiKey;
25
+ constructor(model, config = {}) {
26
+ super(model, {});
27
+ this.apiKey = resolveReactorApiKey(config);
28
+ this.clientConfig = config;
29
+ }
30
+ async createLiveVideo(options) {
31
+ const { logger, prompt, abortSignal } = options;
32
+ const modelSlug = REACTOR_VIDEO_SLUGS[this.model];
33
+ const apiUrl = resolveReactorApiUrl(this.clientConfig);
34
+ logger.request(`activity=generateLiveVideo provider=reactor model=${modelSlug}`, {
35
+ provider: "reactor",
36
+ model: modelSlug
37
+ });
38
+ try {
39
+ const token = await mintReactorSessionToken({
40
+ apiKey: this.apiKey,
41
+ apiUrl,
42
+ modelSlug,
43
+ fetchImpl: this.clientConfig.fetch,
44
+ abortSignal
45
+ });
46
+ const result = {
47
+ id: generateId("liveVideo"),
48
+ model: modelSlug,
49
+ token: token.jwt,
50
+ expiresAt: token.expires_at * 1e3,
51
+ prompt,
52
+ status: "ready"
53
+ };
54
+ logger.output(`activity=generateLiveVideo provider=reactor model=${modelSlug}`, {
55
+ model: modelSlug,
56
+ status: result.status
57
+ });
58
+ return result;
59
+ } catch (error) {
60
+ logger.errors("reactor.createLiveVideo failed", {
61
+ error,
62
+ source: "reactor.createLiveVideo"
63
+ });
64
+ throw error;
65
+ }
66
+ }
67
+ };
68
+ /**
69
+ * Create a Reactor live-video adapter. Reads `REACTOR_API_KEY` when `apiKey` is
70
+ * omitted.
71
+ */
72
+ function reactorVideo(model, config) {
73
+ return new ReactorVideoAdapter(model, config ?? {});
74
+ }
75
+ /**
76
+ * Create a Reactor live-video adapter with an explicit API key.
77
+ */
78
+ function createReactorVideo(model, apiKey, config) {
79
+ return new ReactorVideoAdapter(model, {
80
+ ...config,
81
+ apiKey
82
+ });
83
+ }
84
+ //#endregion
85
+ export { ReactorVideoAdapter, createReactorVideo, reactorVideo };
86
+
87
+ //# sourceMappingURL=video.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video.js","names":[],"sources":["../../../src/adapters/video.ts"],"sourcesContent":["import { BaseLiveVideoAdapter } from '@tanstack/ai/adapters'\nimport { generateId } from '@tanstack/ai-utils'\nimport {\n mintReactorSessionToken,\n resolveReactorApiKey,\n resolveReactorApiUrl,\n} from '../utils/client'\nimport { REACTOR_VIDEO_SLUGS } from '../model-meta'\nimport type { ReactorClientConfig } from '../utils/client'\nimport type {\n LiveVideoGenerationOptions,\n LiveVideoGenerationResult,\n} from '@tanstack/ai'\nimport type {\n ReactorVideoModel,\n ReactorVideoProviderOptions,\n} from '../model-meta'\n\n/**\n * Reactor live-video adapter. Mints a session-scoped JWT so a browser can\n * connect with `@reactor-team/js-sdk`, set the prompt, and start the stream.\n *\n * @example\n * ```ts\n * import { generateLiveVideo } from '@tanstack/ai'\n * import { reactorVideo } from '@tanstack/ai-reactor'\n *\n * const live = await generateLiveVideo({\n * adapter: reactorVideo('helios'),\n * prompt: 'A neon cyberpunk city at night',\n * })\n * ```\n */\nexport class ReactorVideoAdapter<\n TModel extends ReactorVideoModel,\n> extends BaseLiveVideoAdapter<TModel, ReactorVideoProviderOptions> {\n readonly name = 'reactor' as const\n private readonly clientConfig: ReactorClientConfig\n private readonly apiKey: string\n\n constructor(model: TModel, config: ReactorClientConfig = {}) {\n super(model, {})\n this.apiKey = resolveReactorApiKey(config)\n this.clientConfig = config\n }\n\n async createLiveVideo(\n options: LiveVideoGenerationOptions<ReactorVideoProviderOptions>,\n ): Promise<LiveVideoGenerationResult> {\n const { logger, prompt, abortSignal } = options\n const modelSlug = REACTOR_VIDEO_SLUGS[this.model]\n const apiUrl = resolveReactorApiUrl(this.clientConfig)\n\n logger.request(\n `activity=generateLiveVideo provider=reactor model=${modelSlug}`,\n { provider: 'reactor', model: modelSlug },\n )\n\n try {\n const token = await mintReactorSessionToken({\n apiKey: this.apiKey,\n apiUrl,\n modelSlug,\n fetchImpl: this.clientConfig.fetch,\n abortSignal,\n })\n\n const result: LiveVideoGenerationResult = {\n id: generateId('liveVideo'),\n model: modelSlug,\n token: token.jwt,\n expiresAt: token.expires_at * 1000,\n prompt,\n status: 'ready',\n }\n\n logger.output(\n `activity=generateLiveVideo provider=reactor model=${modelSlug}`,\n { model: modelSlug, status: result.status },\n )\n\n return result\n } catch (error) {\n logger.errors('reactor.createLiveVideo failed', {\n error,\n source: 'reactor.createLiveVideo',\n })\n throw error\n }\n }\n}\n\n/**\n * Create a Reactor live-video adapter. Reads `REACTOR_API_KEY` when `apiKey` is\n * omitted.\n */\nexport function reactorVideo<TModel extends ReactorVideoModel>(\n model: TModel,\n config?: ReactorClientConfig,\n): ReactorVideoAdapter<TModel> {\n return new ReactorVideoAdapter(model, config ?? {})\n}\n\n/**\n * Create a Reactor live-video adapter with an explicit API key.\n */\nexport function createReactorVideo<TModel extends ReactorVideoModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<ReactorClientConfig, 'apiKey'>,\n): ReactorVideoAdapter<TModel> {\n return new ReactorVideoAdapter(model, { ...config, apiKey })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,sBAAb,cAEU,qBAA0D;CAClE,OAAgB;CAChB;CACA;CAEA,YAAY,OAAe,SAA8B,CAAC,GAAG;EAC3D,MAAM,OAAO,CAAC,CAAC;EACf,KAAK,SAAS,qBAAqB,MAAM;EACzC,KAAK,eAAe;CACtB;CAEA,MAAM,gBACJ,SACoC;EACpC,MAAM,EAAE,QAAQ,QAAQ,gBAAgB;EACxC,MAAM,YAAY,oBAAoB,KAAK;EAC3C,MAAM,SAAS,qBAAqB,KAAK,YAAY;EAErD,OAAO,QACL,qDAAqD,aACrD;GAAE,UAAU;GAAW,OAAO;EAAU,CAC1C;EAEA,IAAI;GACF,MAAM,QAAQ,MAAM,wBAAwB;IAC1C,QAAQ,KAAK;IACb;IACA;IACA,WAAW,KAAK,aAAa;IAC7B;GACF,CAAC;GAED,MAAM,SAAoC;IACxC,IAAI,WAAW,WAAW;IAC1B,OAAO;IACP,OAAO,MAAM;IACb,WAAW,MAAM,aAAa;IAC9B;IACA,QAAQ;GACV;GAEA,OAAO,OACL,qDAAqD,aACrD;IAAE,OAAO;IAAW,QAAQ,OAAO;GAAO,CAC5C;GAEA,OAAO;EACT,SAAS,OAAO;GACd,OAAO,OAAO,kCAAkC;IAC9C;IACA,QAAQ;GACV,CAAC;GACD,MAAM;EACR;CACF;AACF;;;;;AAMA,SAAgB,aACd,OACA,QAC6B;CAC7B,OAAO,IAAI,oBAAoB,OAAO,UAAU,CAAC,CAAC;AACpD;;;;AAKA,SAAgB,mBACd,OACA,QACA,QAC6B;CAC7B,OAAO,IAAI,oBAAoB,OAAO;EAAE,GAAG;EAAQ;CAAO,CAAC;AAC7D"}
@@ -0,0 +1,36 @@
1
+ import { BaseWorldAdapter } from '@tanstack/ai/adapters';
2
+ import { ReactorClientConfig } from '../utils/client.js';
3
+ import { WorldGenerationOptions, WorldGenerationResult } from '@tanstack/ai';
4
+ import { ReactorWorldModel, ReactorWorldProviderOptions } from '../model-meta.js';
5
+ export type { ReactorClientConfig };
6
+ /**
7
+ * Reactor world adapter. Mints a session-scoped JWT so a browser can connect
8
+ * with `@reactor-team/js-sdk`, set the prompt, and start the live stream.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { generateWorld } from '@tanstack/ai'
13
+ * import { reactorWorld } from '@tanstack/ai-reactor'
14
+ *
15
+ * const world = await generateWorld({
16
+ * adapter: reactorWorld('visko-orbis-stable'),
17
+ * prompt: 'A neon cyberpunk city at night',
18
+ * })
19
+ * ```
20
+ */
21
+ export declare class ReactorWorldAdapter<TModel extends ReactorWorldModel> extends BaseWorldAdapter<TModel, ReactorWorldProviderOptions> {
22
+ readonly name: "reactor";
23
+ private readonly clientConfig;
24
+ private readonly apiKey;
25
+ constructor(model: TModel, config?: ReactorClientConfig);
26
+ createWorld(options: WorldGenerationOptions<ReactorWorldProviderOptions>): Promise<WorldGenerationResult>;
27
+ }
28
+ /**
29
+ * Create a Reactor world adapter. Reads `REACTOR_API_KEY` when `apiKey` is
30
+ * omitted.
31
+ */
32
+ export declare function reactorWorld<TModel extends ReactorWorldModel>(model: TModel, config?: ReactorClientConfig): ReactorWorldAdapter<TModel>;
33
+ /**
34
+ * Create a Reactor world adapter with an explicit API key.
35
+ */
36
+ export declare function createReactorWorld<TModel extends ReactorWorldModel>(model: TModel, apiKey: string, config?: Omit<ReactorClientConfig, 'apiKey'>): ReactorWorldAdapter<TModel>;
@@ -0,0 +1,87 @@
1
+ import { mintReactorSessionToken, resolveReactorApiKey, resolveReactorApiUrl } from "../utils/client.js";
2
+ import { REACTOR_WORLD_SLUGS } from "../model-meta.js";
3
+ import { BaseWorldAdapter } from "@tanstack/ai/adapters";
4
+ import { generateId } from "@tanstack/ai-utils";
5
+ //#region src/adapters/world.ts
6
+ /**
7
+ * Reactor world adapter. Mints a session-scoped JWT so a browser can connect
8
+ * with `@reactor-team/js-sdk`, set the prompt, and start the live stream.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { generateWorld } from '@tanstack/ai'
13
+ * import { reactorWorld } from '@tanstack/ai-reactor'
14
+ *
15
+ * const world = await generateWorld({
16
+ * adapter: reactorWorld('visko-orbis-stable'),
17
+ * prompt: 'A neon cyberpunk city at night',
18
+ * })
19
+ * ```
20
+ */
21
+ var ReactorWorldAdapter = class extends BaseWorldAdapter {
22
+ name = "reactor";
23
+ clientConfig;
24
+ apiKey;
25
+ constructor(model, config = {}) {
26
+ super(model, {});
27
+ this.apiKey = resolveReactorApiKey(config);
28
+ this.clientConfig = config;
29
+ }
30
+ async createWorld(options) {
31
+ const { logger, prompt, abortSignal } = options;
32
+ const modelSlug = REACTOR_WORLD_SLUGS[this.model];
33
+ const apiUrl = resolveReactorApiUrl(this.clientConfig);
34
+ logger.request(`activity=generateWorld provider=reactor model=${modelSlug}`, {
35
+ provider: "reactor",
36
+ model: modelSlug
37
+ });
38
+ try {
39
+ const token = await mintReactorSessionToken({
40
+ apiKey: this.apiKey,
41
+ apiUrl,
42
+ modelSlug,
43
+ fetchImpl: this.clientConfig.fetch,
44
+ abortSignal
45
+ });
46
+ const result = {
47
+ id: generateId("world"),
48
+ model: modelSlug,
49
+ token: token.jwt,
50
+ expiresAt: token.expires_at * 1e3,
51
+ prompt,
52
+ status: "ready"
53
+ };
54
+ logger.output(`activity=generateWorld provider=reactor model=${modelSlug}`, {
55
+ model: modelSlug,
56
+ status: result.status
57
+ });
58
+ return result;
59
+ } catch (error) {
60
+ logger.errors("reactor.createWorld failed", {
61
+ error,
62
+ source: "reactor.createWorld"
63
+ });
64
+ throw error;
65
+ }
66
+ }
67
+ };
68
+ /**
69
+ * Create a Reactor world adapter. Reads `REACTOR_API_KEY` when `apiKey` is
70
+ * omitted.
71
+ */
72
+ function reactorWorld(model, config) {
73
+ return new ReactorWorldAdapter(model, config ?? {});
74
+ }
75
+ /**
76
+ * Create a Reactor world adapter with an explicit API key.
77
+ */
78
+ function createReactorWorld(model, apiKey, config) {
79
+ return new ReactorWorldAdapter(model, {
80
+ ...config,
81
+ apiKey
82
+ });
83
+ }
84
+ //#endregion
85
+ export { ReactorWorldAdapter, createReactorWorld, reactorWorld };
86
+
87
+ //# sourceMappingURL=world.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world.js","names":[],"sources":["../../../src/adapters/world.ts"],"sourcesContent":["import { BaseWorldAdapter } from '@tanstack/ai/adapters'\nimport { generateId } from '@tanstack/ai-utils'\nimport {\n mintReactorSessionToken,\n resolveReactorApiKey,\n resolveReactorApiUrl,\n} from '../utils/client'\nimport { REACTOR_WORLD_SLUGS } from '../model-meta'\nimport type { ReactorClientConfig } from '../utils/client'\nimport type {\n WorldGenerationOptions,\n WorldGenerationResult,\n} from '@tanstack/ai'\nimport type {\n ReactorWorldModel,\n ReactorWorldProviderOptions,\n} from '../model-meta'\n\nexport type { ReactorClientConfig }\n\n/**\n * Reactor world adapter. Mints a session-scoped JWT so a browser can connect\n * with `@reactor-team/js-sdk`, set the prompt, and start the live stream.\n *\n * @example\n * ```ts\n * import { generateWorld } from '@tanstack/ai'\n * import { reactorWorld } from '@tanstack/ai-reactor'\n *\n * const world = await generateWorld({\n * adapter: reactorWorld('visko-orbis-stable'),\n * prompt: 'A neon cyberpunk city at night',\n * })\n * ```\n */\nexport class ReactorWorldAdapter<\n TModel extends ReactorWorldModel,\n> extends BaseWorldAdapter<TModel, ReactorWorldProviderOptions> {\n readonly name = 'reactor' as const\n private readonly clientConfig: ReactorClientConfig\n private readonly apiKey: string\n\n constructor(model: TModel, config: ReactorClientConfig = {}) {\n super(model, {})\n this.apiKey = resolveReactorApiKey(config)\n this.clientConfig = config\n }\n\n async createWorld(\n options: WorldGenerationOptions<ReactorWorldProviderOptions>,\n ): Promise<WorldGenerationResult> {\n const { logger, prompt, abortSignal } = options\n const modelSlug = REACTOR_WORLD_SLUGS[this.model]\n const apiUrl = resolveReactorApiUrl(this.clientConfig)\n\n logger.request(\n `activity=generateWorld provider=reactor model=${modelSlug}`,\n { provider: 'reactor', model: modelSlug },\n )\n\n try {\n const token = await mintReactorSessionToken({\n apiKey: this.apiKey,\n apiUrl,\n modelSlug,\n fetchImpl: this.clientConfig.fetch,\n abortSignal,\n })\n\n const result: WorldGenerationResult = {\n id: generateId('world'),\n model: modelSlug,\n token: token.jwt,\n expiresAt: token.expires_at * 1000,\n prompt,\n status: 'ready',\n }\n\n logger.output(\n `activity=generateWorld provider=reactor model=${modelSlug}`,\n { model: modelSlug, status: result.status },\n )\n\n return result\n } catch (error) {\n logger.errors('reactor.createWorld failed', {\n error,\n source: 'reactor.createWorld',\n })\n throw error\n }\n }\n}\n\n/**\n * Create a Reactor world adapter. Reads `REACTOR_API_KEY` when `apiKey` is\n * omitted.\n */\nexport function reactorWorld<TModel extends ReactorWorldModel>(\n model: TModel,\n config?: ReactorClientConfig,\n): ReactorWorldAdapter<TModel> {\n return new ReactorWorldAdapter(model, config ?? {})\n}\n\n/**\n * Create a Reactor world adapter with an explicit API key.\n */\nexport function createReactorWorld<TModel extends ReactorWorldModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<ReactorClientConfig, 'apiKey'>,\n): ReactorWorldAdapter<TModel> {\n return new ReactorWorldAdapter(model, { ...config, apiKey })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmCA,IAAa,sBAAb,cAEU,iBAAsD;CAC9D,OAAgB;CAChB;CACA;CAEA,YAAY,OAAe,SAA8B,CAAC,GAAG;EAC3D,MAAM,OAAO,CAAC,CAAC;EACf,KAAK,SAAS,qBAAqB,MAAM;EACzC,KAAK,eAAe;CACtB;CAEA,MAAM,YACJ,SACgC;EAChC,MAAM,EAAE,QAAQ,QAAQ,gBAAgB;EACxC,MAAM,YAAY,oBAAoB,KAAK;EAC3C,MAAM,SAAS,qBAAqB,KAAK,YAAY;EAErD,OAAO,QACL,iDAAiD,aACjD;GAAE,UAAU;GAAW,OAAO;EAAU,CAC1C;EAEA,IAAI;GACF,MAAM,QAAQ,MAAM,wBAAwB;IAC1C,QAAQ,KAAK;IACb;IACA;IACA,WAAW,KAAK,aAAa;IAC7B;GACF,CAAC;GAED,MAAM,SAAgC;IACpC,IAAI,WAAW,OAAO;IACtB,OAAO;IACP,OAAO,MAAM;IACb,WAAW,MAAM,aAAa;IAC9B;IACA,QAAQ;GACV;GAEA,OAAO,OACL,iDAAiD,aACjD;IAAE,OAAO;IAAW,QAAQ,OAAO;GAAO,CAC5C;GAEA,OAAO;EACT,SAAS,OAAO;GACd,OAAO,OAAO,8BAA8B;IAC1C;IACA,QAAQ;GACV,CAAC;GACD,MAAM;EACR;CACF;AACF;;;;;AAMA,SAAgB,aACd,OACA,QAC6B;CAC7B,OAAO,IAAI,oBAAoB,OAAO,UAAU,CAAC,CAAC;AACpD;;;;AAKA,SAAgB,mBACd,OACA,QACA,QAC6B;CAC7B,OAAO,IAAI,oBAAoB,OAAO;EAAE,GAAG;EAAQ;CAAO,CAAC;AAC7D"}
@@ -0,0 +1 @@
1
+ export declare const reactorByok: import('@tanstack/ai/byok').ByokProvider<"reactor">;
@@ -0,0 +1,11 @@
1
+ import { defineByokProvider } from "@tanstack/ai/byok";
2
+ //#region src/byok.ts
3
+ var reactorByok = defineByokProvider({
4
+ id: "reactor",
5
+ label: "Reactor",
6
+ env: "REACTOR_API_KEY"
7
+ });
8
+ //#endregion
9
+ export { reactorByok };
10
+
11
+ //# sourceMappingURL=byok.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"byok.js","names":[],"sources":["../../src/byok.ts"],"sourcesContent":["import { defineByokProvider } from '@tanstack/ai/byok'\n\nexport const reactorByok = defineByokProvider({\n id: 'reactor',\n label: 'Reactor',\n env: 'REACTOR_API_KEY',\n})\n"],"mappings":";;AAEA,IAAa,cAAc,mBAAmB;CAC5C,IAAI;CACJ,OAAO;CACP,KAAK;AACP,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { ReactorWorldAdapter, reactorWorld, createReactorWorld, } from './adapters/world.js';
2
+ export { ReactorVideoAdapter, reactorVideo, createReactorVideo, } from './adapters/video.js';
3
+ export type { ReactorClientConfig } from './adapters/world.js';
4
+ export { REACTOR_WORLD_MODELS, REACTOR_WORLD_SLUGS, isReactorWorldModel, REACTOR_VIDEO_MODELS, REACTOR_VIDEO_SLUGS, isReactorVideoModel, } from './model-meta.js';
5
+ export type { ReactorWorldModel, ReactorWorldSlug, ReactorWorldResolution, ReactorWorldProviderOptions, ReactorWorldModelProviderOptionsByName, ReactorVideoModel, ReactorVideoSlug, ReactorVideoProviderOptions, ReactorVideoModelProviderOptionsByName, } from './model-meta.js';
6
+ export { DEFAULT_REACTOR_API_URL, getReactorApiKeyFromEnv, mintReactorSessionToken, } from './utils/client.js';
@@ -0,0 +1,5 @@
1
+ import { DEFAULT_REACTOR_API_URL, getReactorApiKeyFromEnv, mintReactorSessionToken } from "./utils/client.js";
2
+ import { REACTOR_VIDEO_MODELS, REACTOR_VIDEO_SLUGS, REACTOR_WORLD_MODELS, REACTOR_WORLD_SLUGS, isReactorVideoModel, isReactorWorldModel } from "./model-meta.js";
3
+ import { ReactorWorldAdapter, createReactorWorld, reactorWorld } from "./adapters/world.js";
4
+ import { ReactorVideoAdapter, createReactorVideo, reactorVideo } from "./adapters/video.js";
5
+ export { DEFAULT_REACTOR_API_URL, REACTOR_VIDEO_MODELS, REACTOR_VIDEO_SLUGS, REACTOR_WORLD_MODELS, REACTOR_WORLD_SLUGS, ReactorVideoAdapter, ReactorWorldAdapter, createReactorVideo, createReactorWorld, getReactorApiKeyFromEnv, isReactorVideoModel, isReactorWorldModel, mintReactorSessionToken, reactorVideo, reactorWorld };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Reactor world-model ids and connect slugs.
3
+ *
4
+ * Ids are the short names passed to `reactorWorld('visko-orbis-stable')`.
5
+ * `REACTOR_WORLD_SLUGS` maps each id to the `modelName` the Reactor SDK
6
+ * connects with.
7
+ */
8
+ export declare const REACTOR_WORLD_MODELS: readonly ["visko-orbis-stable", "visko-orbis-dynamic", "happy-oyster-adventure", "happy-oyster-director", "lingbot-world-2", "lingbot", "helios"];
9
+ export type ReactorWorldModel = (typeof REACTOR_WORLD_MODELS)[number];
10
+ export declare function isReactorWorldModel(model: string): model is ReactorWorldModel;
11
+ export declare const REACTOR_WORLD_SLUGS: {
12
+ readonly 'visko-orbis-stable': "reactor/visko-orbis-stable";
13
+ readonly 'visko-orbis-dynamic': "reactor/visko-orbis-dynamic";
14
+ readonly 'happy-oyster-adventure': "reactor/happy-oyster-adventure";
15
+ readonly 'happy-oyster-director': "reactor/happy-oyster-director";
16
+ readonly 'lingbot-world-2': "reactor/lingbot-world-2";
17
+ readonly lingbot: "reactor/lingbot";
18
+ readonly helios: "reactor/helios";
19
+ };
20
+ export type ReactorWorldSlug = (typeof REACTOR_WORLD_SLUGS)[ReactorWorldModel];
21
+ export type ReactorWorldResolution = '1080p' | '2k' | '4k';
22
+ /**
23
+ * Browser session commands for Reactor world models (`set_resolution`,
24
+ * `set_seed`, `set_audio_enabled`, `set_audio_prompt`). `createWorld` does
25
+ * not send these. Keep them in client state and apply after connect.
26
+ */
27
+ export interface ReactorWorldProviderOptions {
28
+ /** Delivery resolution for Orbis. Ignored by models that do not expose it. */
29
+ resolution?: ReactorWorldResolution;
30
+ /** RNG seed for the next run. */
31
+ seed?: number;
32
+ /** When false, skip audio compute on the next start. */
33
+ audioEnabled?: boolean;
34
+ /** Sound description. Empty string generates audio from the picture alone. */
35
+ audioPrompt?: string;
36
+ }
37
+ export type ReactorWorldModelProviderOptionsByName = {
38
+ [M in ReactorWorldModel]: ReactorWorldProviderOptions;
39
+ };
40
+ /**
41
+ * Reactor video-model ids and connect slugs.
42
+ *
43
+ * Ids are the short names passed to `reactorVideo('helios')`.
44
+ * `REACTOR_VIDEO_SLUGS` maps each id to the `modelName` the Reactor SDK
45
+ * connects with.
46
+ *
47
+ * These models stream live video over WebRTC. Use them with `generateLiveVideo()`.
48
+ * They do not return a download URL. X2 and SANA-Streaming need a live source
49
+ * track, so they are not in this list.
50
+ */
51
+ export declare const REACTOR_VIDEO_MODELS: readonly ["helios", "fast-h3", "longlive-v2", "ltx2"];
52
+ export type ReactorVideoModel = (typeof REACTOR_VIDEO_MODELS)[number];
53
+ export declare function isReactorVideoModel(model: string): model is ReactorVideoModel;
54
+ export declare const REACTOR_VIDEO_SLUGS: {
55
+ readonly helios: "reactor/helios";
56
+ readonly 'fast-h3': "reactor/fast-h3";
57
+ readonly 'longlive-v2': "reactor/longlive-v2";
58
+ readonly ltx2: "reactor/ltx2";
59
+ };
60
+ export type ReactorVideoSlug = (typeof REACTOR_VIDEO_SLUGS)[ReactorVideoModel];
61
+ /**
62
+ * Browser session commands for Reactor video models. Same fields as world.
63
+ * `createLiveVideo` does not send these. Keep them in client state and apply after
64
+ * connect.
65
+ */
66
+ export type ReactorVideoProviderOptions = ReactorWorldProviderOptions;
67
+ export type ReactorVideoModelProviderOptionsByName = {
68
+ [M in ReactorVideoModel]: ReactorVideoProviderOptions;
69
+ };
@@ -0,0 +1,59 @@
1
+ //#region src/model-meta.ts
2
+ /**
3
+ * Reactor world-model ids and connect slugs.
4
+ *
5
+ * Ids are the short names passed to `reactorWorld('visko-orbis-stable')`.
6
+ * `REACTOR_WORLD_SLUGS` maps each id to the `modelName` the Reactor SDK
7
+ * connects with.
8
+ */
9
+ var REACTOR_WORLD_MODELS = [
10
+ "visko-orbis-stable",
11
+ "visko-orbis-dynamic",
12
+ "happy-oyster-adventure",
13
+ "happy-oyster-director",
14
+ "lingbot-world-2",
15
+ "lingbot",
16
+ "helios"
17
+ ];
18
+ function isReactorWorldModel(model) {
19
+ return REACTOR_WORLD_MODELS.includes(model);
20
+ }
21
+ var REACTOR_WORLD_SLUGS = {
22
+ "visko-orbis-stable": "reactor/visko-orbis-stable",
23
+ "visko-orbis-dynamic": "reactor/visko-orbis-dynamic",
24
+ "happy-oyster-adventure": "reactor/happy-oyster-adventure",
25
+ "happy-oyster-director": "reactor/happy-oyster-director",
26
+ "lingbot-world-2": "reactor/lingbot-world-2",
27
+ lingbot: "reactor/lingbot",
28
+ helios: "reactor/helios"
29
+ };
30
+ /**
31
+ * Reactor video-model ids and connect slugs.
32
+ *
33
+ * Ids are the short names passed to `reactorVideo('helios')`.
34
+ * `REACTOR_VIDEO_SLUGS` maps each id to the `modelName` the Reactor SDK
35
+ * connects with.
36
+ *
37
+ * These models stream live video over WebRTC. Use them with `generateLiveVideo()`.
38
+ * They do not return a download URL. X2 and SANA-Streaming need a live source
39
+ * track, so they are not in this list.
40
+ */
41
+ var REACTOR_VIDEO_MODELS = [
42
+ "helios",
43
+ "fast-h3",
44
+ "longlive-v2",
45
+ "ltx2"
46
+ ];
47
+ function isReactorVideoModel(model) {
48
+ return REACTOR_VIDEO_MODELS.includes(model);
49
+ }
50
+ var REACTOR_VIDEO_SLUGS = {
51
+ helios: "reactor/helios",
52
+ "fast-h3": "reactor/fast-h3",
53
+ "longlive-v2": "reactor/longlive-v2",
54
+ ltx2: "reactor/ltx2"
55
+ };
56
+ //#endregion
57
+ export { REACTOR_VIDEO_MODELS, REACTOR_VIDEO_SLUGS, REACTOR_WORLD_MODELS, REACTOR_WORLD_SLUGS, isReactorVideoModel, isReactorWorldModel };
58
+
59
+ //# sourceMappingURL=model-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-meta.js","names":[],"sources":["../../src/model-meta.ts"],"sourcesContent":["/**\n * Reactor world-model ids and connect slugs.\n *\n * Ids are the short names passed to `reactorWorld('visko-orbis-stable')`.\n * `REACTOR_WORLD_SLUGS` maps each id to the `modelName` the Reactor SDK\n * connects with.\n */\n\nexport const REACTOR_WORLD_MODELS = [\n 'visko-orbis-stable',\n 'visko-orbis-dynamic',\n 'happy-oyster-adventure',\n 'happy-oyster-director',\n 'lingbot-world-2',\n 'lingbot',\n 'helios',\n] as const\n\nexport type ReactorWorldModel = (typeof REACTOR_WORLD_MODELS)[number]\n\nexport function isReactorWorldModel(model: string): model is ReactorWorldModel {\n return (REACTOR_WORLD_MODELS as ReadonlyArray<string>).includes(model)\n}\n\nexport const REACTOR_WORLD_SLUGS = {\n 'visko-orbis-stable': 'reactor/visko-orbis-stable',\n 'visko-orbis-dynamic': 'reactor/visko-orbis-dynamic',\n 'happy-oyster-adventure': 'reactor/happy-oyster-adventure',\n 'happy-oyster-director': 'reactor/happy-oyster-director',\n 'lingbot-world-2': 'reactor/lingbot-world-2',\n lingbot: 'reactor/lingbot',\n helios: 'reactor/helios',\n} as const satisfies Record<ReactorWorldModel, string>\n\nexport type ReactorWorldSlug = (typeof REACTOR_WORLD_SLUGS)[ReactorWorldModel]\n\nexport type ReactorWorldResolution = '1080p' | '2k' | '4k'\n\n/**\n * Browser session commands for Reactor world models (`set_resolution`,\n * `set_seed`, `set_audio_enabled`, `set_audio_prompt`). `createWorld` does\n * not send these. Keep them in client state and apply after connect.\n */\nexport interface ReactorWorldProviderOptions {\n /** Delivery resolution for Orbis. Ignored by models that do not expose it. */\n resolution?: ReactorWorldResolution\n /** RNG seed for the next run. */\n seed?: number\n /** When false, skip audio compute on the next start. */\n audioEnabled?: boolean\n /** Sound description. Empty string generates audio from the picture alone. */\n audioPrompt?: string\n}\n\nexport type ReactorWorldModelProviderOptionsByName = {\n [M in ReactorWorldModel]: ReactorWorldProviderOptions\n}\n\n/**\n * Reactor video-model ids and connect slugs.\n *\n * Ids are the short names passed to `reactorVideo('helios')`.\n * `REACTOR_VIDEO_SLUGS` maps each id to the `modelName` the Reactor SDK\n * connects with.\n *\n * These models stream live video over WebRTC. Use them with `generateLiveVideo()`.\n * They do not return a download URL. X2 and SANA-Streaming need a live source\n * track, so they are not in this list.\n */\n\nexport const REACTOR_VIDEO_MODELS = [\n 'helios',\n 'fast-h3',\n 'longlive-v2',\n 'ltx2',\n] as const\n\nexport type ReactorVideoModel = (typeof REACTOR_VIDEO_MODELS)[number]\n\nexport function isReactorVideoModel(model: string): model is ReactorVideoModel {\n return (REACTOR_VIDEO_MODELS as ReadonlyArray<string>).includes(model)\n}\n\nexport const REACTOR_VIDEO_SLUGS = {\n helios: 'reactor/helios',\n 'fast-h3': 'reactor/fast-h3',\n 'longlive-v2': 'reactor/longlive-v2',\n ltx2: 'reactor/ltx2',\n} as const satisfies Record<ReactorVideoModel, string>\n\nexport type ReactorVideoSlug = (typeof REACTOR_VIDEO_SLUGS)[ReactorVideoModel]\n\n/**\n * Browser session commands for Reactor video models. Same fields as world.\n * `createLiveVideo` does not send these. Keep them in client state and apply after\n * connect.\n */\nexport type ReactorVideoProviderOptions = ReactorWorldProviderOptions\n\nexport type ReactorVideoModelProviderOptionsByName = {\n [M in ReactorVideoModel]: ReactorVideoProviderOptions\n}\n"],"mappings":";;;;;;;;AAQA,IAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,SAAgB,oBAAoB,OAA2C;CAC7E,OAAQ,qBAA+C,SAAS,KAAK;AACvE;AAEA,IAAa,sBAAsB;CACjC,sBAAsB;CACtB,uBAAuB;CACvB,0BAA0B;CAC1B,yBAAyB;CACzB,mBAAmB;CACnB,SAAS;CACT,QAAQ;AACV;;;;;;;;;;;;AAsCA,IAAa,uBAAuB;CAClC;CACA;CACA;CACA;AACF;AAIA,SAAgB,oBAAoB,OAA2C;CAC7E,OAAQ,qBAA+C,SAAS,KAAK;AACvE;AAEA,IAAa,sBAAsB;CACjC,QAAQ;CACR,WAAW;CACX,eAAe;CACf,MAAM;AACR"}
@@ -0,0 +1,20 @@
1
+ export declare const DEFAULT_REACTOR_API_URL = "https://api.reactor.inc";
2
+ export interface ReactorClientConfig {
3
+ apiKey?: string;
4
+ baseUrl?: string;
5
+ fetch?: typeof fetch;
6
+ }
7
+ export declare function getReactorApiKeyFromEnv(): string;
8
+ export declare function resolveReactorApiKey(config?: ReactorClientConfig): string;
9
+ export declare function resolveReactorApiUrl(config?: ReactorClientConfig): string;
10
+ export interface ReactorTokenResponse {
11
+ jwt: string;
12
+ expires_at: number;
13
+ }
14
+ export declare function mintReactorSessionToken(args: {
15
+ apiKey: string;
16
+ apiUrl: string;
17
+ modelSlug: string;
18
+ fetchImpl?: typeof fetch;
19
+ abortSignal?: AbortSignal;
20
+ }): Promise<ReactorTokenResponse>;
@@ -0,0 +1,43 @@
1
+ import { getApiKeyFromEnv } from "@tanstack/ai-utils";
2
+ //#region src/utils/client.ts
3
+ var DEFAULT_REACTOR_API_URL = "https://api.reactor.inc";
4
+ function getReactorApiKeyFromEnv() {
5
+ return getApiKeyFromEnv("REACTOR_API_KEY");
6
+ }
7
+ function resolveReactorApiKey(config) {
8
+ return config?.apiKey ?? getReactorApiKeyFromEnv();
9
+ }
10
+ function resolveReactorApiUrl(config) {
11
+ return (config?.baseUrl ?? "https://api.reactor.inc").replace(/\/+$/, "");
12
+ }
13
+ async function mintReactorSessionToken(args) {
14
+ const fetchImpl = args.fetchImpl ?? globalThis.fetch;
15
+ if (typeof fetchImpl !== "function") throw new Error("global fetch is not available. Pass `fetch` in the Reactor adapter config.");
16
+ const response = await fetchImpl(`${args.apiUrl}/tokens`, {
17
+ method: "POST",
18
+ headers: {
19
+ "Reactor-API-Key": args.apiKey,
20
+ "Content-Type": "application/json"
21
+ },
22
+ body: JSON.stringify({ authorization_details: [{
23
+ type: "session",
24
+ resources: { models: { match: [args.modelSlug] } }
25
+ }] }),
26
+ ...args.abortSignal ? { signal: args.abortSignal } : {}
27
+ });
28
+ if (!response.ok) {
29
+ const detail = await response.text().catch(() => "");
30
+ throw new Error(`Reactor token request failed (${response.status}${response.statusText ? ` ${response.statusText}` : ""})${detail ? `: ${detail}` : ""}`);
31
+ }
32
+ const body = await response.json();
33
+ if (typeof body.jwt !== "string" || body.jwt.length === 0) throw new Error("Reactor token response did not include a jwt.");
34
+ if (typeof body.expires_at !== "number" || !Number.isFinite(body.expires_at)) throw new Error("Reactor token response did not include expires_at.");
35
+ return {
36
+ jwt: body.jwt,
37
+ expires_at: body.expires_at
38
+ };
39
+ }
40
+ //#endregion
41
+ export { DEFAULT_REACTOR_API_URL, getReactorApiKeyFromEnv, mintReactorSessionToken, resolveReactorApiKey, resolveReactorApiUrl };
42
+
43
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/utils/client.ts"],"sourcesContent":["import { getApiKeyFromEnv } from '@tanstack/ai-utils'\n\nexport const DEFAULT_REACTOR_API_URL = 'https://api.reactor.inc'\n\nexport interface ReactorClientConfig {\n apiKey?: string\n baseUrl?: string\n fetch?: typeof fetch\n}\n\nexport function getReactorApiKeyFromEnv(): string {\n return getApiKeyFromEnv('REACTOR_API_KEY')\n}\n\nexport function resolveReactorApiKey(config?: ReactorClientConfig): string {\n return config?.apiKey ?? getReactorApiKeyFromEnv()\n}\n\nexport function resolveReactorApiUrl(config?: ReactorClientConfig): string {\n return (config?.baseUrl ?? DEFAULT_REACTOR_API_URL).replace(/\\/+$/, '')\n}\n\nexport interface ReactorTokenResponse {\n jwt: string\n expires_at: number\n}\n\nexport async function mintReactorSessionToken(args: {\n apiKey: string\n apiUrl: string\n modelSlug: string\n fetchImpl?: typeof fetch\n abortSignal?: AbortSignal\n}): Promise<ReactorTokenResponse> {\n const fetchImpl = args.fetchImpl ?? globalThis.fetch\n if (typeof fetchImpl !== 'function') {\n throw new Error(\n 'global fetch is not available. Pass `fetch` in the Reactor adapter config.',\n )\n }\n\n const response = await fetchImpl(`${args.apiUrl}/tokens`, {\n method: 'POST',\n headers: {\n 'Reactor-API-Key': args.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n authorization_details: [\n {\n type: 'session',\n resources: { models: { match: [args.modelSlug] } },\n },\n ],\n }),\n ...(args.abortSignal ? { signal: args.abortSignal } : {}),\n })\n\n if (!response.ok) {\n const detail = await response.text().catch(() => '')\n throw new Error(\n `Reactor token request failed (${response.status}${\n response.statusText ? ` ${response.statusText}` : ''\n })${detail ? `: ${detail}` : ''}`,\n )\n }\n\n const body = (await response.json()) as {\n jwt?: unknown\n expires_at?: unknown\n }\n\n if (typeof body.jwt !== 'string' || body.jwt.length === 0) {\n throw new Error('Reactor token response did not include a jwt.')\n }\n\n if (\n typeof body.expires_at !== 'number' ||\n !Number.isFinite(body.expires_at)\n ) {\n throw new Error('Reactor token response did not include expires_at.')\n }\n\n return { jwt: body.jwt, expires_at: body.expires_at }\n}\n"],"mappings":";;AAEA,IAAa,0BAA0B;AAQvC,SAAgB,0BAAkC;CAChD,OAAO,iBAAiB,iBAAiB;AAC3C;AAEA,SAAgB,qBAAqB,QAAsC;CACzE,OAAO,QAAQ,UAAU,wBAAwB;AACnD;AAEA,SAAgB,qBAAqB,QAAsC;CACzE,QAAQ,QAAQ,WAAA,0BAAA,CAAoC,QAAQ,QAAQ,EAAE;AACxE;AAOA,eAAsB,wBAAwB,MAMZ;CAChC,MAAM,YAAY,KAAK,aAAa,WAAW;CAC/C,IAAI,OAAO,cAAc,YACvB,MAAM,IAAI,MACR,4EACF;CAGF,MAAM,WAAW,MAAM,UAAU,GAAG,KAAK,OAAO,UAAU;EACxD,QAAQ;EACR,SAAS;GACP,mBAAmB,KAAK;GACxB,gBAAgB;EAClB;EACA,MAAM,KAAK,UAAU,EACnB,uBAAuB,CACrB;GACE,MAAM;GACN,WAAW,EAAE,QAAQ,EAAE,OAAO,CAAC,KAAK,SAAS,EAAE,EAAE;EACnD,CACF,EACF,CAAC;EACD,GAAI,KAAK,cAAc,EAAE,QAAQ,KAAK,YAAY,IAAI,CAAC;CACzD,CAAC;CAED,IAAI,CAAC,SAAS,IAAI;EAChB,MAAM,SAAS,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;EACnD,MAAM,IAAI,MACR,iCAAiC,SAAS,SACxC,SAAS,aAAa,IAAI,SAAS,eAAe,GACnD,GAAG,SAAS,KAAK,WAAW,IAC/B;CACF;CAEA,MAAM,OAAQ,MAAM,SAAS,KAAK;CAKlC,IAAI,OAAO,KAAK,QAAQ,YAAY,KAAK,IAAI,WAAW,GACtD,MAAM,IAAI,MAAM,+CAA+C;CAGjE,IACE,OAAO,KAAK,eAAe,YAC3B,CAAC,OAAO,SAAS,KAAK,UAAU,GAEhC,MAAM,IAAI,MAAM,oDAAoD;CAGtE,OAAO;EAAE,KAAK,KAAK;EAAK,YAAY,KAAK;CAAW;AACtD"}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@tanstack/ai-reactor",
3
+ "version": "0.2.0",
4
+ "description": "Reactor adapter for TanStack AI live world and video generation (Orbis, Happy Oyster, LingBot, Helios, FastH3).",
5
+ "author": "Tanner Linsley",
6
+ "license": "MIT",
7
+ "homepage": "https://tanstack.com/ai",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/TanStack/ai.git",
11
+ "directory": "packages/ai-reactor"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/TanStack/ai/issues"
15
+ },
16
+ "funding": {
17
+ "type": "github",
18
+ "url": "https://github.com/sponsors/tannerlinsley"
19
+ },
20
+ "type": "module",
21
+ "module": "./dist/esm/index.js",
22
+ "types": "./dist/esm/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/esm/index.d.ts",
26
+ "import": "./dist/esm/index.js"
27
+ },
28
+ "./byok": {
29
+ "types": "./dist/esm/byok.d.ts",
30
+ "import": "./dist/esm/byok.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "src"
36
+ ],
37
+ "keywords": [
38
+ "ai",
39
+ "ai-sdk",
40
+ "typescript",
41
+ "tanstack",
42
+ "reactor",
43
+ "adapter",
44
+ "world-generation",
45
+ "video-generation",
46
+ "world-models",
47
+ "orbis",
48
+ "realtime-video"
49
+ ],
50
+ "dependencies": {
51
+ "@tanstack/ai-utils": "^0.4.0"
52
+ },
53
+ "devDependencies": {
54
+ "@vitest/coverage-v8": "4.1.10",
55
+ "vite": "^8.2.1",
56
+ "@tanstack/ai": "0.54.0"
57
+ },
58
+ "peerDependencies": {
59
+ "@tanstack/ai": "^0.54.0"
60
+ },
61
+ "scripts": {
62
+ "build": "vite build",
63
+ "clean": "premove ./build ./dist",
64
+ "lint:fix": "oxlint src --type-aware --fix",
65
+ "test:build": "publint --strict",
66
+ "test:oxlint": "oxlint src --type-aware",
67
+ "test:lib": "vitest run",
68
+ "test:lib:dev": "pnpm test:lib --watch",
69
+ "test:types": "tsc"
70
+ }
71
+ }
@@ -0,0 +1,113 @@
1
+ import { BaseLiveVideoAdapter } from '@tanstack/ai/adapters'
2
+ import { generateId } from '@tanstack/ai-utils'
3
+ import {
4
+ mintReactorSessionToken,
5
+ resolveReactorApiKey,
6
+ resolveReactorApiUrl,
7
+ } from '../utils/client'
8
+ import { REACTOR_VIDEO_SLUGS } from '../model-meta'
9
+ import type { ReactorClientConfig } from '../utils/client'
10
+ import type {
11
+ LiveVideoGenerationOptions,
12
+ LiveVideoGenerationResult,
13
+ } from '@tanstack/ai'
14
+ import type {
15
+ ReactorVideoModel,
16
+ ReactorVideoProviderOptions,
17
+ } from '../model-meta'
18
+
19
+ /**
20
+ * Reactor live-video adapter. Mints a session-scoped JWT so a browser can
21
+ * connect with `@reactor-team/js-sdk`, set the prompt, and start the stream.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * import { generateLiveVideo } from '@tanstack/ai'
26
+ * import { reactorVideo } from '@tanstack/ai-reactor'
27
+ *
28
+ * const live = await generateLiveVideo({
29
+ * adapter: reactorVideo('helios'),
30
+ * prompt: 'A neon cyberpunk city at night',
31
+ * })
32
+ * ```
33
+ */
34
+ export class ReactorVideoAdapter<
35
+ TModel extends ReactorVideoModel,
36
+ > extends BaseLiveVideoAdapter<TModel, ReactorVideoProviderOptions> {
37
+ readonly name = 'reactor' as const
38
+ private readonly clientConfig: ReactorClientConfig
39
+ private readonly apiKey: string
40
+
41
+ constructor(model: TModel, config: ReactorClientConfig = {}) {
42
+ super(model, {})
43
+ this.apiKey = resolveReactorApiKey(config)
44
+ this.clientConfig = config
45
+ }
46
+
47
+ async createLiveVideo(
48
+ options: LiveVideoGenerationOptions<ReactorVideoProviderOptions>,
49
+ ): Promise<LiveVideoGenerationResult> {
50
+ const { logger, prompt, abortSignal } = options
51
+ const modelSlug = REACTOR_VIDEO_SLUGS[this.model]
52
+ const apiUrl = resolveReactorApiUrl(this.clientConfig)
53
+
54
+ logger.request(
55
+ `activity=generateLiveVideo provider=reactor model=${modelSlug}`,
56
+ { provider: 'reactor', model: modelSlug },
57
+ )
58
+
59
+ try {
60
+ const token = await mintReactorSessionToken({
61
+ apiKey: this.apiKey,
62
+ apiUrl,
63
+ modelSlug,
64
+ fetchImpl: this.clientConfig.fetch,
65
+ abortSignal,
66
+ })
67
+
68
+ const result: LiveVideoGenerationResult = {
69
+ id: generateId('liveVideo'),
70
+ model: modelSlug,
71
+ token: token.jwt,
72
+ expiresAt: token.expires_at * 1000,
73
+ prompt,
74
+ status: 'ready',
75
+ }
76
+
77
+ logger.output(
78
+ `activity=generateLiveVideo provider=reactor model=${modelSlug}`,
79
+ { model: modelSlug, status: result.status },
80
+ )
81
+
82
+ return result
83
+ } catch (error) {
84
+ logger.errors('reactor.createLiveVideo failed', {
85
+ error,
86
+ source: 'reactor.createLiveVideo',
87
+ })
88
+ throw error
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Create a Reactor live-video adapter. Reads `REACTOR_API_KEY` when `apiKey` is
95
+ * omitted.
96
+ */
97
+ export function reactorVideo<TModel extends ReactorVideoModel>(
98
+ model: TModel,
99
+ config?: ReactorClientConfig,
100
+ ): ReactorVideoAdapter<TModel> {
101
+ return new ReactorVideoAdapter(model, config ?? {})
102
+ }
103
+
104
+ /**
105
+ * Create a Reactor live-video adapter with an explicit API key.
106
+ */
107
+ export function createReactorVideo<TModel extends ReactorVideoModel>(
108
+ model: TModel,
109
+ apiKey: string,
110
+ config?: Omit<ReactorClientConfig, 'apiKey'>,
111
+ ): ReactorVideoAdapter<TModel> {
112
+ return new ReactorVideoAdapter(model, { ...config, apiKey })
113
+ }
@@ -0,0 +1,115 @@
1
+ import { BaseWorldAdapter } from '@tanstack/ai/adapters'
2
+ import { generateId } from '@tanstack/ai-utils'
3
+ import {
4
+ mintReactorSessionToken,
5
+ resolveReactorApiKey,
6
+ resolveReactorApiUrl,
7
+ } from '../utils/client'
8
+ import { REACTOR_WORLD_SLUGS } from '../model-meta'
9
+ import type { ReactorClientConfig } from '../utils/client'
10
+ import type {
11
+ WorldGenerationOptions,
12
+ WorldGenerationResult,
13
+ } from '@tanstack/ai'
14
+ import type {
15
+ ReactorWorldModel,
16
+ ReactorWorldProviderOptions,
17
+ } from '../model-meta'
18
+
19
+ export type { ReactorClientConfig }
20
+
21
+ /**
22
+ * Reactor world adapter. Mints a session-scoped JWT so a browser can connect
23
+ * with `@reactor-team/js-sdk`, set the prompt, and start the live stream.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { generateWorld } from '@tanstack/ai'
28
+ * import { reactorWorld } from '@tanstack/ai-reactor'
29
+ *
30
+ * const world = await generateWorld({
31
+ * adapter: reactorWorld('visko-orbis-stable'),
32
+ * prompt: 'A neon cyberpunk city at night',
33
+ * })
34
+ * ```
35
+ */
36
+ export class ReactorWorldAdapter<
37
+ TModel extends ReactorWorldModel,
38
+ > extends BaseWorldAdapter<TModel, ReactorWorldProviderOptions> {
39
+ readonly name = 'reactor' as const
40
+ private readonly clientConfig: ReactorClientConfig
41
+ private readonly apiKey: string
42
+
43
+ constructor(model: TModel, config: ReactorClientConfig = {}) {
44
+ super(model, {})
45
+ this.apiKey = resolveReactorApiKey(config)
46
+ this.clientConfig = config
47
+ }
48
+
49
+ async createWorld(
50
+ options: WorldGenerationOptions<ReactorWorldProviderOptions>,
51
+ ): Promise<WorldGenerationResult> {
52
+ const { logger, prompt, abortSignal } = options
53
+ const modelSlug = REACTOR_WORLD_SLUGS[this.model]
54
+ const apiUrl = resolveReactorApiUrl(this.clientConfig)
55
+
56
+ logger.request(
57
+ `activity=generateWorld provider=reactor model=${modelSlug}`,
58
+ { provider: 'reactor', model: modelSlug },
59
+ )
60
+
61
+ try {
62
+ const token = await mintReactorSessionToken({
63
+ apiKey: this.apiKey,
64
+ apiUrl,
65
+ modelSlug,
66
+ fetchImpl: this.clientConfig.fetch,
67
+ abortSignal,
68
+ })
69
+
70
+ const result: WorldGenerationResult = {
71
+ id: generateId('world'),
72
+ model: modelSlug,
73
+ token: token.jwt,
74
+ expiresAt: token.expires_at * 1000,
75
+ prompt,
76
+ status: 'ready',
77
+ }
78
+
79
+ logger.output(
80
+ `activity=generateWorld provider=reactor model=${modelSlug}`,
81
+ { model: modelSlug, status: result.status },
82
+ )
83
+
84
+ return result
85
+ } catch (error) {
86
+ logger.errors('reactor.createWorld failed', {
87
+ error,
88
+ source: 'reactor.createWorld',
89
+ })
90
+ throw error
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Create a Reactor world adapter. Reads `REACTOR_API_KEY` when `apiKey` is
97
+ * omitted.
98
+ */
99
+ export function reactorWorld<TModel extends ReactorWorldModel>(
100
+ model: TModel,
101
+ config?: ReactorClientConfig,
102
+ ): ReactorWorldAdapter<TModel> {
103
+ return new ReactorWorldAdapter(model, config ?? {})
104
+ }
105
+
106
+ /**
107
+ * Create a Reactor world adapter with an explicit API key.
108
+ */
109
+ export function createReactorWorld<TModel extends ReactorWorldModel>(
110
+ model: TModel,
111
+ apiKey: string,
112
+ config?: Omit<ReactorClientConfig, 'apiKey'>,
113
+ ): ReactorWorldAdapter<TModel> {
114
+ return new ReactorWorldAdapter(model, { ...config, apiKey })
115
+ }
package/src/byok.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { defineByokProvider } from '@tanstack/ai/byok'
2
+
3
+ export const reactorByok = defineByokProvider({
4
+ id: 'reactor',
5
+ label: 'Reactor',
6
+ env: 'REACTOR_API_KEY',
7
+ })
package/src/index.ts ADDED
@@ -0,0 +1,37 @@
1
+ export {
2
+ ReactorWorldAdapter,
3
+ reactorWorld,
4
+ createReactorWorld,
5
+ } from './adapters/world'
6
+ export {
7
+ ReactorVideoAdapter,
8
+ reactorVideo,
9
+ createReactorVideo,
10
+ } from './adapters/video'
11
+ export type { ReactorClientConfig } from './adapters/world'
12
+
13
+ export {
14
+ REACTOR_WORLD_MODELS,
15
+ REACTOR_WORLD_SLUGS,
16
+ isReactorWorldModel,
17
+ REACTOR_VIDEO_MODELS,
18
+ REACTOR_VIDEO_SLUGS,
19
+ isReactorVideoModel,
20
+ } from './model-meta'
21
+ export type {
22
+ ReactorWorldModel,
23
+ ReactorWorldSlug,
24
+ ReactorWorldResolution,
25
+ ReactorWorldProviderOptions,
26
+ ReactorWorldModelProviderOptionsByName,
27
+ ReactorVideoModel,
28
+ ReactorVideoSlug,
29
+ ReactorVideoProviderOptions,
30
+ ReactorVideoModelProviderOptionsByName,
31
+ } from './model-meta'
32
+
33
+ export {
34
+ DEFAULT_REACTOR_API_URL,
35
+ getReactorApiKeyFromEnv,
36
+ mintReactorSessionToken,
37
+ } from './utils/client'
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Reactor world-model ids and connect slugs.
3
+ *
4
+ * Ids are the short names passed to `reactorWorld('visko-orbis-stable')`.
5
+ * `REACTOR_WORLD_SLUGS` maps each id to the `modelName` the Reactor SDK
6
+ * connects with.
7
+ */
8
+
9
+ export const REACTOR_WORLD_MODELS = [
10
+ 'visko-orbis-stable',
11
+ 'visko-orbis-dynamic',
12
+ 'happy-oyster-adventure',
13
+ 'happy-oyster-director',
14
+ 'lingbot-world-2',
15
+ 'lingbot',
16
+ 'helios',
17
+ ] as const
18
+
19
+ export type ReactorWorldModel = (typeof REACTOR_WORLD_MODELS)[number]
20
+
21
+ export function isReactorWorldModel(model: string): model is ReactorWorldModel {
22
+ return (REACTOR_WORLD_MODELS as ReadonlyArray<string>).includes(model)
23
+ }
24
+
25
+ export const REACTOR_WORLD_SLUGS = {
26
+ 'visko-orbis-stable': 'reactor/visko-orbis-stable',
27
+ 'visko-orbis-dynamic': 'reactor/visko-orbis-dynamic',
28
+ 'happy-oyster-adventure': 'reactor/happy-oyster-adventure',
29
+ 'happy-oyster-director': 'reactor/happy-oyster-director',
30
+ 'lingbot-world-2': 'reactor/lingbot-world-2',
31
+ lingbot: 'reactor/lingbot',
32
+ helios: 'reactor/helios',
33
+ } as const satisfies Record<ReactorWorldModel, string>
34
+
35
+ export type ReactorWorldSlug = (typeof REACTOR_WORLD_SLUGS)[ReactorWorldModel]
36
+
37
+ export type ReactorWorldResolution = '1080p' | '2k' | '4k'
38
+
39
+ /**
40
+ * Browser session commands for Reactor world models (`set_resolution`,
41
+ * `set_seed`, `set_audio_enabled`, `set_audio_prompt`). `createWorld` does
42
+ * not send these. Keep them in client state and apply after connect.
43
+ */
44
+ export interface ReactorWorldProviderOptions {
45
+ /** Delivery resolution for Orbis. Ignored by models that do not expose it. */
46
+ resolution?: ReactorWorldResolution
47
+ /** RNG seed for the next run. */
48
+ seed?: number
49
+ /** When false, skip audio compute on the next start. */
50
+ audioEnabled?: boolean
51
+ /** Sound description. Empty string generates audio from the picture alone. */
52
+ audioPrompt?: string
53
+ }
54
+
55
+ export type ReactorWorldModelProviderOptionsByName = {
56
+ [M in ReactorWorldModel]: ReactorWorldProviderOptions
57
+ }
58
+
59
+ /**
60
+ * Reactor video-model ids and connect slugs.
61
+ *
62
+ * Ids are the short names passed to `reactorVideo('helios')`.
63
+ * `REACTOR_VIDEO_SLUGS` maps each id to the `modelName` the Reactor SDK
64
+ * connects with.
65
+ *
66
+ * These models stream live video over WebRTC. Use them with `generateLiveVideo()`.
67
+ * They do not return a download URL. X2 and SANA-Streaming need a live source
68
+ * track, so they are not in this list.
69
+ */
70
+
71
+ export const REACTOR_VIDEO_MODELS = [
72
+ 'helios',
73
+ 'fast-h3',
74
+ 'longlive-v2',
75
+ 'ltx2',
76
+ ] as const
77
+
78
+ export type ReactorVideoModel = (typeof REACTOR_VIDEO_MODELS)[number]
79
+
80
+ export function isReactorVideoModel(model: string): model is ReactorVideoModel {
81
+ return (REACTOR_VIDEO_MODELS as ReadonlyArray<string>).includes(model)
82
+ }
83
+
84
+ export const REACTOR_VIDEO_SLUGS = {
85
+ helios: 'reactor/helios',
86
+ 'fast-h3': 'reactor/fast-h3',
87
+ 'longlive-v2': 'reactor/longlive-v2',
88
+ ltx2: 'reactor/ltx2',
89
+ } as const satisfies Record<ReactorVideoModel, string>
90
+
91
+ export type ReactorVideoSlug = (typeof REACTOR_VIDEO_SLUGS)[ReactorVideoModel]
92
+
93
+ /**
94
+ * Browser session commands for Reactor video models. Same fields as world.
95
+ * `createLiveVideo` does not send these. Keep them in client state and apply after
96
+ * connect.
97
+ */
98
+ export type ReactorVideoProviderOptions = ReactorWorldProviderOptions
99
+
100
+ export type ReactorVideoModelProviderOptionsByName = {
101
+ [M in ReactorVideoModel]: ReactorVideoProviderOptions
102
+ }
@@ -0,0 +1,85 @@
1
+ import { getApiKeyFromEnv } from '@tanstack/ai-utils'
2
+
3
+ export const DEFAULT_REACTOR_API_URL = 'https://api.reactor.inc'
4
+
5
+ export interface ReactorClientConfig {
6
+ apiKey?: string
7
+ baseUrl?: string
8
+ fetch?: typeof fetch
9
+ }
10
+
11
+ export function getReactorApiKeyFromEnv(): string {
12
+ return getApiKeyFromEnv('REACTOR_API_KEY')
13
+ }
14
+
15
+ export function resolveReactorApiKey(config?: ReactorClientConfig): string {
16
+ return config?.apiKey ?? getReactorApiKeyFromEnv()
17
+ }
18
+
19
+ export function resolveReactorApiUrl(config?: ReactorClientConfig): string {
20
+ return (config?.baseUrl ?? DEFAULT_REACTOR_API_URL).replace(/\/+$/, '')
21
+ }
22
+
23
+ export interface ReactorTokenResponse {
24
+ jwt: string
25
+ expires_at: number
26
+ }
27
+
28
+ export async function mintReactorSessionToken(args: {
29
+ apiKey: string
30
+ apiUrl: string
31
+ modelSlug: string
32
+ fetchImpl?: typeof fetch
33
+ abortSignal?: AbortSignal
34
+ }): Promise<ReactorTokenResponse> {
35
+ const fetchImpl = args.fetchImpl ?? globalThis.fetch
36
+ if (typeof fetchImpl !== 'function') {
37
+ throw new Error(
38
+ 'global fetch is not available. Pass `fetch` in the Reactor adapter config.',
39
+ )
40
+ }
41
+
42
+ const response = await fetchImpl(`${args.apiUrl}/tokens`, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Reactor-API-Key': args.apiKey,
46
+ 'Content-Type': 'application/json',
47
+ },
48
+ body: JSON.stringify({
49
+ authorization_details: [
50
+ {
51
+ type: 'session',
52
+ resources: { models: { match: [args.modelSlug] } },
53
+ },
54
+ ],
55
+ }),
56
+ ...(args.abortSignal ? { signal: args.abortSignal } : {}),
57
+ })
58
+
59
+ if (!response.ok) {
60
+ const detail = await response.text().catch(() => '')
61
+ throw new Error(
62
+ `Reactor token request failed (${response.status}${
63
+ response.statusText ? ` ${response.statusText}` : ''
64
+ })${detail ? `: ${detail}` : ''}`,
65
+ )
66
+ }
67
+
68
+ const body = (await response.json()) as {
69
+ jwt?: unknown
70
+ expires_at?: unknown
71
+ }
72
+
73
+ if (typeof body.jwt !== 'string' || body.jwt.length === 0) {
74
+ throw new Error('Reactor token response did not include a jwt.')
75
+ }
76
+
77
+ if (
78
+ typeof body.expires_at !== 'number' ||
79
+ !Number.isFinite(body.expires_at)
80
+ ) {
81
+ throw new Error('Reactor token response did not include expires_at.')
82
+ }
83
+
84
+ return { jwt: body.jwt, expires_at: body.expires_at }
85
+ }