@tanstack/ai 0.6.1 → 0.6.3
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/dist/esm/activities/chat/messages.js +2 -2
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +7 -4
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.js +9 -0
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateImage/index.d.ts +19 -6
- package/dist/esm/activities/generateImage/index.js +11 -2
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +19 -6
- package/dist/esm/activities/generateSpeech/index.js +11 -2
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +30 -6
- package/dist/esm/activities/generateTranscription/index.js +13 -2
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +45 -7
- package/dist/esm/activities/generateVideo/index.js +90 -1
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.d.ts +14 -0
- package/dist/esm/activities/stream-generation-result.js +40 -0
- package/dist/esm/activities/stream-generation-result.js.map +1 -0
- package/dist/esm/activities/summarize/index.js +2 -17
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/package.json +1 -1
- package/src/activities/chat/messages.ts +5 -7
- package/src/activities/chat/stream/message-updaters.ts +6 -4
- package/src/activities/chat/tools/tool-calls.ts +14 -0
- package/src/activities/generateImage/index.ts +49 -7
- package/src/activities/generateSpeech/index.ts +41 -7
- package/src/activities/generateTranscription/index.ts +59 -9
- package/src/activities/generateVideo/index.ts +173 -6
- package/src/activities/stream-generation-result.ts +62 -0
- package/src/activities/summarize/index.ts +3 -22
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TranscriptionAdapter } from './adapter.js';
|
|
2
|
-
import { TranscriptionResult } from '../../types.js';
|
|
2
|
+
import { StreamChunk, TranscriptionResult } from '../../types.js';
|
|
3
3
|
/** The adapter kind this activity handles */
|
|
4
4
|
export declare const kind: "transcription";
|
|
5
5
|
/**
|
|
@@ -11,8 +11,9 @@ export type TranscriptionProviderOptions<TAdapter> = TAdapter extends Transcript
|
|
|
11
11
|
* The model is extracted from the adapter's model property.
|
|
12
12
|
*
|
|
13
13
|
* @template TAdapter - The transcription adapter type
|
|
14
|
+
* @template TStream - Whether to stream the output
|
|
14
15
|
*/
|
|
15
|
-
export interface TranscriptionActivityOptions<TAdapter extends TranscriptionAdapter<string, object
|
|
16
|
+
export interface TranscriptionActivityOptions<TAdapter extends TranscriptionAdapter<string, object>, TStream extends boolean = false> {
|
|
16
17
|
/** The transcription adapter to use (must be created with a model) */
|
|
17
18
|
adapter: TAdapter & {
|
|
18
19
|
kind: typeof kind;
|
|
@@ -27,9 +28,21 @@ export interface TranscriptionActivityOptions<TAdapter extends TranscriptionAdap
|
|
|
27
28
|
responseFormat?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt';
|
|
28
29
|
/** Provider-specific options for transcription */
|
|
29
30
|
modelOptions?: TranscriptionProviderOptions<TAdapter>;
|
|
31
|
+
/**
|
|
32
|
+
* Whether to stream the transcription result.
|
|
33
|
+
* When true, returns an AsyncIterable<StreamChunk> for streaming transport.
|
|
34
|
+
* When false or not provided, returns a Promise<TranscriptionResult>.
|
|
35
|
+
*
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
stream?: TStream;
|
|
30
39
|
}
|
|
31
|
-
/**
|
|
32
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Result type for the transcription activity.
|
|
42
|
+
* - If stream is true: AsyncIterable<StreamChunk>
|
|
43
|
+
* - Otherwise: Promise<TranscriptionResult>
|
|
44
|
+
*/
|
|
45
|
+
export type TranscriptionActivityResult<TStream extends boolean = false> = TStream extends true ? AsyncIterable<StreamChunk> : Promise<TranscriptionResult>;
|
|
33
46
|
/**
|
|
34
47
|
* Transcription activity - converts audio to text.
|
|
35
48
|
*
|
|
@@ -61,11 +74,22 @@ export type TranscriptionActivityResult = Promise<TranscriptionResult>;
|
|
|
61
74
|
* console.log(`[${segment.start}s - ${segment.end}s]: ${segment.text}`)
|
|
62
75
|
* })
|
|
63
76
|
* ```
|
|
77
|
+
*
|
|
78
|
+
* @example Streaming transcription result
|
|
79
|
+
* ```ts
|
|
80
|
+
* for await (const chunk of generateTranscription({
|
|
81
|
+
* adapter: openaiTranscription('whisper-1'),
|
|
82
|
+
* audio: audioFile,
|
|
83
|
+
* stream: true
|
|
84
|
+
* })) {
|
|
85
|
+
* console.log(chunk)
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
64
88
|
*/
|
|
65
|
-
export declare function generateTranscription<TAdapter extends TranscriptionAdapter<string, object
|
|
89
|
+
export declare function generateTranscription<TAdapter extends TranscriptionAdapter<string, object>, TStream extends boolean = false>(options: TranscriptionActivityOptions<TAdapter, TStream>): TranscriptionActivityResult<TStream>;
|
|
66
90
|
/**
|
|
67
91
|
* Create typed options for the generateTranscription() function without executing.
|
|
68
92
|
*/
|
|
69
|
-
export declare function createTranscriptionOptions<TAdapter extends TranscriptionAdapter<string, object
|
|
93
|
+
export declare function createTranscriptionOptions<TAdapter extends TranscriptionAdapter<string, object>, TStream extends boolean = false>(options: TranscriptionActivityOptions<TAdapter, TStream>): TranscriptionActivityOptions<TAdapter, TStream>;
|
|
70
94
|
export type { TranscriptionAdapter, TranscriptionAdapterConfig, AnyTranscriptionAdapter, } from './adapter.js';
|
|
71
95
|
export { BaseTranscriptionAdapter } from './adapter.js';
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { aiEventClient } from "../../event-client.js";
|
|
2
|
+
import { streamGenerationResult } from "../stream-generation-result.js";
|
|
2
3
|
const kind = "transcription";
|
|
3
4
|
function createId(prefix) {
|
|
4
5
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
function generateTranscription(options) {
|
|
8
|
+
if (options.stream) {
|
|
9
|
+
return streamGenerationResult(
|
|
10
|
+
() => runGenerateTranscription(options)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
return runGenerateTranscription(
|
|
14
|
+
options
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
async function runGenerateTranscription(options) {
|
|
18
|
+
const { adapter, stream: _stream, ...rest } = options;
|
|
8
19
|
const model = adapter.model;
|
|
9
20
|
const requestId = createId("transcription");
|
|
10
21
|
const startTime = Date.now();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/activities/generateTranscription/index.ts"],"sourcesContent":["/**\n * Transcription Activity\n *\n * Transcribes audio to text using speech-to-text models.\n * This is a self-contained module with implementation, types, and JSDoc.\n */\n\nimport { aiEventClient } from '../../event-client.js'\nimport type { TranscriptionAdapter } from './adapter'\nimport type { TranscriptionResult } from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'transcription' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract provider options from a TranscriptionAdapter via ~types.\n */\nexport type TranscriptionProviderOptions<TAdapter> =\n TAdapter extends TranscriptionAdapter<any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the transcription activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The transcription adapter type\n */\nexport interface TranscriptionActivityOptions<\n TAdapter extends TranscriptionAdapter<string, object>,\n> {\n /** The transcription adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** The audio data to transcribe - can be base64 string, File, Blob, or Buffer */\n audio: string | File | Blob | ArrayBuffer\n /** The language of the audio in ISO-639-1 format (e.g., 'en') */\n language?: string\n /** An optional prompt to guide the transcription */\n prompt?: string\n /** The format of the transcription output */\n responseFormat?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt'\n /** Provider-specific options for transcription */\n modelOptions?: TranscriptionProviderOptions<TAdapter>\n}\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/activities/generateTranscription/index.ts"],"sourcesContent":["/**\n * Transcription Activity\n *\n * Transcribes audio to text using speech-to-text models.\n * This is a self-contained module with implementation, types, and JSDoc.\n */\n\nimport { aiEventClient } from '../../event-client.js'\nimport { streamGenerationResult } from '../stream-generation-result.js'\nimport type { TranscriptionAdapter } from './adapter'\nimport type { StreamChunk, TranscriptionResult } from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'transcription' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract provider options from a TranscriptionAdapter via ~types.\n */\nexport type TranscriptionProviderOptions<TAdapter> =\n TAdapter extends TranscriptionAdapter<any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the transcription activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The transcription adapter type\n * @template TStream - Whether to stream the output\n */\nexport interface TranscriptionActivityOptions<\n TAdapter extends TranscriptionAdapter<string, object>,\n TStream extends boolean = false,\n> {\n /** The transcription adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** The audio data to transcribe - can be base64 string, File, Blob, or Buffer */\n audio: string | File | Blob | ArrayBuffer\n /** The language of the audio in ISO-639-1 format (e.g., 'en') */\n language?: string\n /** An optional prompt to guide the transcription */\n prompt?: string\n /** The format of the transcription output */\n responseFormat?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt'\n /** Provider-specific options for transcription */\n modelOptions?: TranscriptionProviderOptions<TAdapter>\n /**\n * Whether to stream the transcription result.\n * When true, returns an AsyncIterable<StreamChunk> for streaming transport.\n * When false or not provided, returns a Promise<TranscriptionResult>.\n *\n * @default false\n */\n stream?: TStream\n}\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n/**\n * Result type for the transcription activity.\n * - If stream is true: AsyncIterable<StreamChunk>\n * - Otherwise: Promise<TranscriptionResult>\n */\nexport type TranscriptionActivityResult<TStream extends boolean = false> =\n TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<TranscriptionResult>\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Transcription activity - converts audio to text.\n *\n * Uses AI speech-to-text models to transcribe audio content.\n *\n * @example Transcribe an audio file\n * ```ts\n * import { generateTranscription } from '@tanstack/ai'\n * import { openaiTranscription } from '@tanstack/ai-openai'\n *\n * const result = await generateTranscription({\n * adapter: openaiTranscription('whisper-1'),\n * audio: audioFile, // File, Blob, or base64 string\n * language: 'en'\n * })\n *\n * console.log(result.text)\n * ```\n *\n * @example With verbose output for timestamps\n * ```ts\n * const result = await generateTranscription({\n * adapter: openaiTranscription('whisper-1'),\n * audio: audioFile,\n * responseFormat: 'verbose_json'\n * })\n *\n * result.segments?.forEach(segment => {\n * console.log(`[${segment.start}s - ${segment.end}s]: ${segment.text}`)\n * })\n * ```\n *\n * @example Streaming transcription result\n * ```ts\n * for await (const chunk of generateTranscription({\n * adapter: openaiTranscription('whisper-1'),\n * audio: audioFile,\n * stream: true\n * })) {\n * console.log(chunk)\n * }\n * ```\n */\nexport function generateTranscription<\n TAdapter extends TranscriptionAdapter<string, object>,\n TStream extends boolean = false,\n>(\n options: TranscriptionActivityOptions<TAdapter, TStream>,\n): TranscriptionActivityResult<TStream> {\n if (options.stream) {\n return streamGenerationResult(() =>\n runGenerateTranscription(options),\n ) as TranscriptionActivityResult<TStream>\n }\n\n return runGenerateTranscription(\n options,\n ) as TranscriptionActivityResult<TStream>\n}\n\n/**\n * Run non-streaming transcription\n */\nasync function runGenerateTranscription<\n TAdapter extends TranscriptionAdapter<string, object>,\n>(\n options: TranscriptionActivityOptions<TAdapter, boolean>,\n): Promise<TranscriptionResult> {\n const { adapter, stream: _stream, ...rest } = options\n const model = adapter.model\n const requestId = createId('transcription')\n const startTime = Date.now()\n\n aiEventClient.emit('transcription:request:started', {\n requestId,\n provider: adapter.name,\n model,\n language: rest.language,\n prompt: rest.prompt,\n responseFormat: rest.responseFormat,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: startTime,\n })\n\n const result = await adapter.transcribe({ ...rest, model })\n const duration = Date.now() - startTime\n\n aiEventClient.emit('transcription:request:completed', {\n requestId,\n provider: adapter.name,\n model,\n text: result.text,\n language: result.language,\n duration,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: Date.now(),\n })\n\n return result\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the generateTranscription() function without executing.\n */\nexport function createTranscriptionOptions<\n TAdapter extends TranscriptionAdapter<string, object>,\n TStream extends boolean = false,\n>(\n options: TranscriptionActivityOptions<TAdapter, TStream>,\n): TranscriptionActivityOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n TranscriptionAdapter,\n TranscriptionAdapterConfig,\n AnyTranscriptionAdapter,\n} from './adapter'\nexport { BaseTranscriptionAdapter } from './adapter'\n"],"names":[],"mappings":";;AAiBO,MAAM,OAAO;AAiEpB,SAAS,SAAS,QAAwB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAiDO,SAAS,sBAId,SACsC;AACtC,MAAI,QAAQ,QAAQ;AAClB,WAAO;AAAA,MAAuB,MAC5B,yBAAyB,OAAO;AAAA,IAAA;AAAA,EAEpC;AAEA,SAAO;AAAA,IACL;AAAA,EAAA;AAEJ;AAKA,eAAe,yBAGb,SAC8B;AAC9B,QAAM,EAAE,SAAS,QAAQ,SAAS,GAAG,SAAS;AAC9C,QAAM,QAAQ,QAAQ;AACtB,QAAM,YAAY,SAAS,eAAe;AAC1C,QAAM,YAAY,KAAK,IAAA;AAEvB,gBAAc,KAAK,iCAAiC;AAAA,IAClD;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,UAAU,KAAK;AAAA,IACf,QAAQ,KAAK;AAAA,IACb,gBAAgB,KAAK;AAAA,IACrB,cAAc,KAAK;AAAA,IACnB,WAAW;AAAA,EAAA,CACZ;AAED,QAAM,SAAS,MAAM,QAAQ,WAAW,EAAE,GAAG,MAAM,OAAO;AAC1D,QAAM,WAAW,KAAK,IAAA,IAAQ;AAE9B,gBAAc,KAAK,mCAAmC;AAAA,IACpD;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,MAAM,OAAO;AAAA,IACb,UAAU,OAAO;AAAA,IACjB;AAAA,IACA,cAAc,KAAK;AAAA,IACnB,WAAW,KAAK,IAAA;AAAA,EAAI,CACrB;AAED,SAAO;AACT;AASO,SAAS,2BAId,SACiD;AACjD,SAAO;AACT;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VideoAdapter } from './adapter.js';
|
|
2
|
-
import { VideoJobResult, VideoStatusResult, VideoUrlResult } from '../../types.js';
|
|
2
|
+
import { StreamChunk, VideoJobResult, VideoStatusResult, VideoUrlResult } from '../../types.js';
|
|
3
3
|
/** The adapter kind this activity handles */
|
|
4
4
|
export declare const kind: "video";
|
|
5
5
|
/**
|
|
@@ -24,9 +24,12 @@ interface VideoActivityBaseOptions<TAdapter extends VideoAdapter<string, any, an
|
|
|
24
24
|
* Options for creating a new video generation job.
|
|
25
25
|
* The model is extracted from the adapter's model property.
|
|
26
26
|
*
|
|
27
|
+
* @template TAdapter - The video adapter type
|
|
28
|
+
* @template TStream - Whether to stream the output
|
|
29
|
+
*
|
|
27
30
|
* @experimental Video generation is an experimental feature and may change.
|
|
28
31
|
*/
|
|
29
|
-
export type VideoCreateOptions<TAdapter extends VideoAdapter<string, any, any, any
|
|
32
|
+
export type VideoCreateOptions<TAdapter extends VideoAdapter<string, any, any, any>, TStream extends boolean = false> = VideoActivityBaseOptions<TAdapter> & {
|
|
30
33
|
/** Request type - create a new job (default if not specified) */
|
|
31
34
|
request?: 'create';
|
|
32
35
|
/** Text description of the desired video */
|
|
@@ -35,6 +38,21 @@ export type VideoCreateOptions<TAdapter extends VideoAdapter<string, any, any, a
|
|
|
35
38
|
size?: VideoSizeForAdapter<TAdapter>;
|
|
36
39
|
/** Video duration in seconds */
|
|
37
40
|
duration?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Whether to stream the video generation lifecycle.
|
|
43
|
+
* When true, returns an AsyncIterable<StreamChunk> that handles the full
|
|
44
|
+
* job lifecycle: create job, poll for status, yield updates, and yield final result.
|
|
45
|
+
* When false or not provided, returns a Promise<VideoJobResult>.
|
|
46
|
+
*
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
stream?: TStream;
|
|
50
|
+
/** Polling interval in milliseconds (stream mode only). @default 2000 */
|
|
51
|
+
pollingInterval?: number;
|
|
52
|
+
/** Maximum time to wait before timing out in milliseconds (stream mode only). @default 600000 */
|
|
53
|
+
maxDuration?: number;
|
|
54
|
+
/** Custom run ID (stream mode only) */
|
|
55
|
+
runId?: string;
|
|
38
56
|
} & ({} extends VideoProviderOptions<TAdapter> ? {
|
|
39
57
|
/** Provider-specific options for video generation */ modelOptions?: VideoProviderOptions<TAdapter>;
|
|
40
58
|
} : {
|
|
@@ -68,19 +86,24 @@ export interface VideoUrlOptions<TAdapter extends VideoAdapter<string, any, any,
|
|
|
68
86
|
*
|
|
69
87
|
* @experimental Video generation is an experimental feature and may change.
|
|
70
88
|
*/
|
|
71
|
-
export type VideoActivityOptions<TAdapter extends VideoAdapter<string, any, any, any>, TRequest extends 'create' | 'status' | 'url' = 'create'> = TRequest extends 'status' ? VideoStatusOptions<TAdapter> : TRequest extends 'url' ? VideoUrlOptions<TAdapter> : VideoCreateOptions<TAdapter>;
|
|
89
|
+
export type VideoActivityOptions<TAdapter extends VideoAdapter<string, any, any, any>, TRequest extends 'create' | 'status' | 'url' = 'create', TStream extends boolean = false> = TRequest extends 'status' ? VideoStatusOptions<TAdapter> : TRequest extends 'url' ? VideoUrlOptions<TAdapter> : VideoCreateOptions<TAdapter, TStream>;
|
|
72
90
|
/**
|
|
73
|
-
* Result type for the video activity, based on request type.
|
|
91
|
+
* Result type for the video activity, based on request type and streaming.
|
|
92
|
+
* - If stream is true (create request): AsyncIterable<StreamChunk>
|
|
93
|
+
* - Otherwise: Promise<VideoJobResult | VideoStatusResult | VideoUrlResult>
|
|
74
94
|
*
|
|
75
95
|
* @experimental Video generation is an experimental feature and may change.
|
|
76
96
|
*/
|
|
77
|
-
export type VideoActivityResult<TRequest extends 'create' | 'status' | 'url' = 'create'> = TRequest extends 'status' ? Promise<VideoStatusResult> : TRequest extends 'url' ? Promise<VideoUrlResult> : Promise<VideoJobResult>;
|
|
97
|
+
export type VideoActivityResult<TRequest extends 'create' | 'status' | 'url' = 'create', TStream extends boolean = false> = TRequest extends 'status' ? Promise<VideoStatusResult> : TRequest extends 'url' ? Promise<VideoUrlResult> : TStream extends true ? AsyncIterable<StreamChunk> : Promise<VideoJobResult>;
|
|
78
98
|
/**
|
|
79
99
|
* Generate video - creates a video generation job from a text prompt.
|
|
80
100
|
*
|
|
81
101
|
* Uses AI video generation models to create videos based on natural language descriptions.
|
|
82
102
|
* Unlike image generation, video generation is asynchronous and requires polling for completion.
|
|
83
103
|
*
|
|
104
|
+
* When `stream: true` is passed, handles the full job lifecycle automatically:
|
|
105
|
+
* create job → poll for status → stream updates → yield final result.
|
|
106
|
+
*
|
|
84
107
|
* @experimental Video generation is an experimental feature and may change.
|
|
85
108
|
*
|
|
86
109
|
* @example Create a video generation job
|
|
@@ -96,8 +119,23 @@ export type VideoActivityResult<TRequest extends 'create' | 'status' | 'url' = '
|
|
|
96
119
|
*
|
|
97
120
|
* console.log('Job started:', jobId)
|
|
98
121
|
* ```
|
|
122
|
+
*
|
|
123
|
+
* @example Stream the full video generation lifecycle
|
|
124
|
+
* ```ts
|
|
125
|
+
* import { generateVideo, toServerSentEventsResponse } from '@tanstack/ai'
|
|
126
|
+
* import { openaiVideo } from '@tanstack/ai-openai'
|
|
127
|
+
*
|
|
128
|
+
* const stream = generateVideo({
|
|
129
|
+
* adapter: openaiVideo('sora-2'),
|
|
130
|
+
* prompt: 'A cat chasing a dog in a sunny park',
|
|
131
|
+
* stream: true,
|
|
132
|
+
* pollingInterval: 3000,
|
|
133
|
+
* })
|
|
134
|
+
*
|
|
135
|
+
* return toServerSentEventsResponse(stream)
|
|
136
|
+
* ```
|
|
99
137
|
*/
|
|
100
|
-
export declare function generateVideo<TAdapter extends VideoAdapter<string, any, any, any
|
|
138
|
+
export declare function generateVideo<TAdapter extends VideoAdapter<string, any, any, any>, TStream extends boolean = false>(options: VideoCreateOptions<TAdapter, TStream>): VideoActivityResult<'create', TStream>;
|
|
101
139
|
/**
|
|
102
140
|
* Get video job status - returns the current status, progress, and URL if available.
|
|
103
141
|
*
|
|
@@ -137,6 +175,6 @@ export declare function getVideoJobStatus<TAdapter extends VideoAdapter<string,
|
|
|
137
175
|
/**
|
|
138
176
|
* Create typed options for the generateVideo() function without executing.
|
|
139
177
|
*/
|
|
140
|
-
export declare function createVideoOptions<TAdapter extends VideoAdapter<string, any, any, any
|
|
178
|
+
export declare function createVideoOptions<TAdapter extends VideoAdapter<string, any, any, any>, TStream extends boolean = false>(options: VideoCreateOptions<TAdapter, TStream>): VideoCreateOptions<TAdapter, TStream>;
|
|
141
179
|
export type { VideoAdapter, VideoAdapterConfig, AnyVideoAdapter, } from './adapter.js';
|
|
142
180
|
export { BaseVideoAdapter } from './adapter.js';
|
|
@@ -3,7 +3,15 @@ const kind = "video";
|
|
|
3
3
|
function createId(prefix) {
|
|
4
4
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
5
5
|
}
|
|
6
|
-
|
|
6
|
+
function generateVideo(options) {
|
|
7
|
+
if (options.stream) {
|
|
8
|
+
return runStreamingVideoGeneration(
|
|
9
|
+
options
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return runCreateVideoJob(options);
|
|
13
|
+
}
|
|
14
|
+
async function runCreateVideoJob(options) {
|
|
7
15
|
const { adapter, prompt, size, duration, modelOptions } = options;
|
|
8
16
|
const model = adapter.model;
|
|
9
17
|
return adapter.createVideoJob({
|
|
@@ -14,6 +22,87 @@ async function generateVideo(options) {
|
|
|
14
22
|
modelOptions
|
|
15
23
|
});
|
|
16
24
|
}
|
|
25
|
+
function sleep(ms) {
|
|
26
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
27
|
+
}
|
|
28
|
+
async function* runStreamingVideoGeneration(options) {
|
|
29
|
+
const { adapter, prompt, size, duration, modelOptions } = options;
|
|
30
|
+
const model = adapter.model;
|
|
31
|
+
const runId = options.runId ?? createId("run");
|
|
32
|
+
const pollingInterval = options.pollingInterval ?? 2e3;
|
|
33
|
+
const maxDuration = options.maxDuration ?? 6e5;
|
|
34
|
+
yield {
|
|
35
|
+
type: "RUN_STARTED",
|
|
36
|
+
runId,
|
|
37
|
+
timestamp: Date.now()
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
const jobResult = await adapter.createVideoJob({
|
|
41
|
+
model,
|
|
42
|
+
prompt,
|
|
43
|
+
size,
|
|
44
|
+
duration,
|
|
45
|
+
modelOptions
|
|
46
|
+
});
|
|
47
|
+
yield {
|
|
48
|
+
type: "CUSTOM",
|
|
49
|
+
name: "video:job:created",
|
|
50
|
+
value: { jobId: jobResult.jobId },
|
|
51
|
+
timestamp: Date.now()
|
|
52
|
+
};
|
|
53
|
+
const startTime = Date.now();
|
|
54
|
+
while (Date.now() - startTime < maxDuration) {
|
|
55
|
+
await sleep(pollingInterval);
|
|
56
|
+
const statusResult = await adapter.getVideoStatus(jobResult.jobId);
|
|
57
|
+
yield {
|
|
58
|
+
type: "CUSTOM",
|
|
59
|
+
name: "video:status",
|
|
60
|
+
value: {
|
|
61
|
+
jobId: jobResult.jobId,
|
|
62
|
+
status: statusResult.status,
|
|
63
|
+
progress: statusResult.progress,
|
|
64
|
+
error: statusResult.error
|
|
65
|
+
},
|
|
66
|
+
timestamp: Date.now()
|
|
67
|
+
};
|
|
68
|
+
if (statusResult.status === "completed") {
|
|
69
|
+
const urlResult = await adapter.getVideoUrl(jobResult.jobId);
|
|
70
|
+
yield {
|
|
71
|
+
type: "CUSTOM",
|
|
72
|
+
name: "generation:result",
|
|
73
|
+
value: {
|
|
74
|
+
jobId: jobResult.jobId,
|
|
75
|
+
status: "completed",
|
|
76
|
+
url: urlResult.url,
|
|
77
|
+
expiresAt: urlResult.expiresAt
|
|
78
|
+
},
|
|
79
|
+
timestamp: Date.now()
|
|
80
|
+
};
|
|
81
|
+
yield {
|
|
82
|
+
type: "RUN_FINISHED",
|
|
83
|
+
runId,
|
|
84
|
+
finishReason: "stop",
|
|
85
|
+
timestamp: Date.now()
|
|
86
|
+
};
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (statusResult.status === "failed") {
|
|
90
|
+
throw new Error(statusResult.error || "Video generation failed");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw new Error("Video generation timed out");
|
|
94
|
+
} catch (error) {
|
|
95
|
+
yield {
|
|
96
|
+
type: "RUN_ERROR",
|
|
97
|
+
runId,
|
|
98
|
+
error: {
|
|
99
|
+
message: error.message || "Video generation failed",
|
|
100
|
+
code: error.code
|
|
101
|
+
},
|
|
102
|
+
timestamp: Date.now()
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
17
106
|
async function getVideoJobStatus(options) {
|
|
18
107
|
const { adapter, jobId } = options;
|
|
19
108
|
const requestId = createId("video-status");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/activities/generateVideo/index.ts"],"sourcesContent":["/**\n * Video Activity (Experimental)\n *\n * Generates videos from text prompts using a jobs/polling architecture.\n * This is a self-contained module with implementation, types, and JSDoc.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\n\nimport { aiEventClient } from '../../event-client.js'\nimport type { VideoAdapter } from './adapter'\nimport type {\n VideoJobResult,\n VideoStatusResult,\n VideoUrlResult,\n} from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'video' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract provider options from a VideoAdapter via ~types.\n */\nexport type VideoProviderOptions<TAdapter> =\n TAdapter extends VideoAdapter<any, any, any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n/**\n * Extract the size type for a VideoAdapter's model via ~types.\n */\nexport type VideoSizeForAdapter<TAdapter> =\n TAdapter extends VideoAdapter<infer TModel, any, any, infer TSizeMap>\n ? TModel extends keyof TSizeMap\n ? TSizeMap[TModel]\n : string\n : string\n\n// ===========================\n// Activity Options Types\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n// ===========================\n\n/**\n * Base options shared by all video activity operations.\n * The model is extracted from the adapter's model property.\n */\ninterface VideoActivityBaseOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> {\n /** The video adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n}\n\n/**\n * Options for creating a new video generation job.\n * The model is extracted from the adapter's model property.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoCreateOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> = VideoActivityBaseOptions<TAdapter> & {\n /** Request type - create a new job (default if not specified) */\n request?: 'create'\n /** Text description of the desired video */\n prompt: string\n /** Video size — format depends on the provider (e.g., \"16:9\", \"1280x720\") */\n size?: VideoSizeForAdapter<TAdapter>\n /** Video duration in seconds */\n duration?: number\n} & ({} extends VideoProviderOptions<TAdapter>\n ? {\n /** Provider-specific options for video generation */ modelOptions?: VideoProviderOptions<TAdapter>\n }\n : {\n /** Provider-specific options for video generation */ modelOptions: VideoProviderOptions<TAdapter>\n })\n\n/**\n * Options for polling the status of a video generation job.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface VideoStatusOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> extends VideoActivityBaseOptions<TAdapter> {\n /** Request type - get job status */\n request: 'status'\n /** The job ID to check status for */\n jobId: string\n}\n\n/**\n * Options for getting the URL of a completed video.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface VideoUrlOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> extends VideoActivityBaseOptions<TAdapter> {\n /** Request type - get video URL */\n request: 'url'\n /** The job ID to get URL for */\n jobId: string\n}\n\n/**\n * Union type for all video activity options.\n * Discriminated by the `request` field.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoActivityOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TRequest extends 'create' | 'status' | 'url' = 'create',\n> = TRequest extends 'status'\n ? VideoStatusOptions<TAdapter>\n : TRequest extends 'url'\n ? VideoUrlOptions<TAdapter>\n : VideoCreateOptions<TAdapter>\n\n// ===========================\n// Activity Result Types\n// ===========================\n\n/**\n * Result type for the video activity, based on request type.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoActivityResult<\n TRequest extends 'create' | 'status' | 'url' = 'create',\n> = TRequest extends 'status'\n ? Promise<VideoStatusResult>\n : TRequest extends 'url'\n ? Promise<VideoUrlResult>\n : Promise<VideoJobResult>\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Generate video - creates a video generation job from a text prompt.\n *\n * Uses AI video generation models to create videos based on natural language descriptions.\n * Unlike image generation, video generation is asynchronous and requires polling for completion.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @example Create a video generation job\n * ```ts\n * import { generateVideo } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * // Start a video generation job\n * const { jobId } = await generateVideo({\n * adapter: openaiVideo('sora-2'),\n * prompt: 'A cat chasing a dog in a sunny park'\n * })\n *\n * console.log('Job started:', jobId)\n * ```\n */\nexport async function generateVideo<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: VideoCreateOptions<TAdapter>): Promise<VideoJobResult> {\n const { adapter, prompt, size, duration, modelOptions } = options\n const model = adapter.model\n\n return adapter.createVideoJob({\n model,\n prompt,\n size,\n duration,\n modelOptions,\n })\n}\n\n/**\n * Get video job status - returns the current status, progress, and URL if available.\n *\n * This function combines status checking and URL retrieval. If the job is completed,\n * it will automatically fetch and include the video URL.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @example Check job status\n * ```ts\n * import { getVideoJobStatus } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * const result = await getVideoJobStatus({\n * adapter: openaiVideo('sora-2'),\n * jobId: 'job-123'\n * })\n *\n * console.log('Status:', result.status)\n * console.log('Progress:', result.progress)\n * if (result.url) {\n * console.log('Video URL:', result.url)\n * }\n * ```\n */\nexport async function getVideoJobStatus<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: {\n adapter: TAdapter & { kind: typeof kind }\n jobId: string\n}): Promise<{\n status: 'pending' | 'processing' | 'completed' | 'failed'\n progress?: number\n url?: string\n error?: string\n}> {\n const { adapter, jobId } = options\n const requestId = createId('video-status')\n const startTime = Date.now()\n\n aiEventClient.emit('video:request:started', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n timestamp: startTime,\n })\n\n // Get status first\n const statusResult = await adapter.getVideoStatus(jobId)\n\n // If completed, also get the URL\n if (statusResult.status === 'completed') {\n try {\n const urlResult = await adapter.getVideoUrl(jobId)\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n url: urlResult.url,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n url: urlResult.url,\n }\n } catch (error) {\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n error:\n error instanceof Error ? error.message : 'Failed to get video URL',\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n // If URL fetch fails, still return status\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n error:\n error instanceof Error ? error.message : 'Failed to get video URL',\n }\n }\n }\n\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n\n // Return status for non-completed jobs\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n }\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the generateVideo() function without executing.\n */\nexport function createVideoOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: VideoCreateOptions<TAdapter>): VideoCreateOptions<TAdapter> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n VideoAdapter,\n VideoAdapterConfig,\n AnyVideoAdapter,\n} from './adapter'\nexport { BaseVideoAdapter } from './adapter'\n"],"names":[],"mappings":";AAsBO,MAAM,OAAO;AA2BpB,SAAS,SAAS,QAAwB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AA6HA,eAAsB,cAEpB,SAAgE;AAChE,QAAM,EAAE,SAAS,QAAQ,MAAM,UAAU,iBAAiB;AAC1D,QAAM,QAAQ,QAAQ;AAEtB,SAAO,QAAQ,eAAe;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AACH;AA2BA,eAAsB,kBAEpB,SAQC;AACD,QAAM,EAAE,SAAS,MAAA,IAAU;AAC3B,QAAM,YAAY,SAAS,cAAc;AACzC,QAAM,YAAY,KAAK,IAAA;AAEvB,gBAAc,KAAK,yBAAyB;AAAA,IAC1C;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB,OAAO,QAAQ;AAAA,IACf,aAAa;AAAA,IACb;AAAA,IACA,WAAW;AAAA,EAAA,CACZ;AAGD,QAAM,eAAe,MAAM,QAAQ,eAAe,KAAK;AAGvD,MAAI,aAAa,WAAW,aAAa;AACvC,QAAI;AACF,YAAM,YAAY,MAAM,QAAQ,YAAY,KAAK;AACjD,oBAAc,KAAK,2BAA2B;AAAA,QAC5C;AAAA,QACA,UAAU,QAAQ;AAAA,QAClB,OAAO,QAAQ;AAAA,QACf,aAAa;AAAA,QACb;AAAA,QACA,QAAQ,aAAa;AAAA,QACrB,UAAU,aAAa;AAAA,QACvB,KAAK,UAAU;AAAA,QACf,UAAU,KAAK,IAAA,IAAQ;AAAA,QACvB,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB;AACD,aAAO;AAAA,QACL,QAAQ,aAAa;AAAA,QACrB,UAAU,aAAa;AAAA,QACvB,KAAK,UAAU;AAAA,MAAA;AAAA,IAEnB,SAAS,OAAO;AACd,oBAAc,KAAK,2BAA2B;AAAA,QAC5C;AAAA,QACA,UAAU,QAAQ;AAAA,QAClB,OAAO,QAAQ;AAAA,QACf,aAAa;AAAA,QACb;AAAA,QACA,QAAQ,aAAa;AAAA,QACrB,UAAU,aAAa;AAAA,QACvB,OACE,iBAAiB,QAAQ,MAAM,UAAU;AAAA,QAC3C,UAAU,KAAK,IAAA,IAAQ;AAAA,QACvB,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB;AAED,aAAO;AAAA,QACL,QAAQ,aAAa;AAAA,QACrB,UAAU,aAAa;AAAA,QACvB,OACE,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MAAA;AAAA,IAE/C;AAAA,EACF;AAEA,gBAAc,KAAK,2BAA2B;AAAA,IAC5C;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB,OAAO,QAAQ;AAAA,IACf,aAAa;AAAA,IACb;AAAA,IACA,QAAQ,aAAa;AAAA,IACrB,UAAU,aAAa;AAAA,IACvB,OAAO,aAAa;AAAA,IACpB,UAAU,KAAK,IAAA,IAAQ;AAAA,IACvB,WAAW,KAAK,IAAA;AAAA,EAAI,CACrB;AAGD,SAAO;AAAA,IACL,QAAQ,aAAa;AAAA,IACrB,UAAU,aAAa;AAAA,IACvB,OAAO,aAAa;AAAA,EAAA;AAExB;AASO,SAAS,mBAEd,SAAqE;AACrE,SAAO;AACT;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/activities/generateVideo/index.ts"],"sourcesContent":["/**\n * Video Activity (Experimental)\n *\n * Generates videos from text prompts using a jobs/polling architecture.\n * This is a self-contained module with implementation, types, and JSDoc.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\n\nimport { aiEventClient } from '../../event-client.js'\nimport type { VideoAdapter } from './adapter'\nimport type {\n StreamChunk,\n VideoJobResult,\n VideoStatusResult,\n VideoUrlResult,\n} from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'video' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract provider options from a VideoAdapter via ~types.\n */\nexport type VideoProviderOptions<TAdapter> =\n TAdapter extends VideoAdapter<any, any, any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n/**\n * Extract the size type for a VideoAdapter's model via ~types.\n */\nexport type VideoSizeForAdapter<TAdapter> =\n TAdapter extends VideoAdapter<infer TModel, any, any, infer TSizeMap>\n ? TModel extends keyof TSizeMap\n ? TSizeMap[TModel]\n : string\n : string\n\n// ===========================\n// Activity Options Types\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n// ===========================\n\n/**\n * Base options shared by all video activity operations.\n * The model is extracted from the adapter's model property.\n */\ninterface VideoActivityBaseOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> {\n /** The video adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n}\n\n/**\n * Options for creating a new video generation job.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The video adapter type\n * @template TStream - Whether to stream the output\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoCreateOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TStream extends boolean = false,\n> = VideoActivityBaseOptions<TAdapter> & {\n /** Request type - create a new job (default if not specified) */\n request?: 'create'\n /** Text description of the desired video */\n prompt: string\n /** Video size — format depends on the provider (e.g., \"16:9\", \"1280x720\") */\n size?: VideoSizeForAdapter<TAdapter>\n /** Video duration in seconds */\n duration?: number\n /**\n * Whether to stream the video generation lifecycle.\n * When true, returns an AsyncIterable<StreamChunk> that handles the full\n * job lifecycle: create job, poll for status, yield updates, and yield final result.\n * When false or not provided, returns a Promise<VideoJobResult>.\n *\n * @default false\n */\n stream?: TStream\n /** Polling interval in milliseconds (stream mode only). @default 2000 */\n pollingInterval?: number\n /** Maximum time to wait before timing out in milliseconds (stream mode only). @default 600000 */\n maxDuration?: number\n /** Custom run ID (stream mode only) */\n runId?: string\n} & ({} extends VideoProviderOptions<TAdapter>\n ? {\n /** Provider-specific options for video generation */ modelOptions?: VideoProviderOptions<TAdapter>\n }\n : {\n /** Provider-specific options for video generation */ modelOptions: VideoProviderOptions<TAdapter>\n })\n\n/**\n * Options for polling the status of a video generation job.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface VideoStatusOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> extends VideoActivityBaseOptions<TAdapter> {\n /** Request type - get job status */\n request: 'status'\n /** The job ID to check status for */\n jobId: string\n}\n\n/**\n * Options for getting the URL of a completed video.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface VideoUrlOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n> extends VideoActivityBaseOptions<TAdapter> {\n /** Request type - get video URL */\n request: 'url'\n /** The job ID to get URL for */\n jobId: string\n}\n\n/**\n * Union type for all video activity options.\n * Discriminated by the `request` field.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoActivityOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TRequest extends 'create' | 'status' | 'url' = 'create',\n TStream extends boolean = false,\n> = TRequest extends 'status'\n ? VideoStatusOptions<TAdapter>\n : TRequest extends 'url'\n ? VideoUrlOptions<TAdapter>\n : VideoCreateOptions<TAdapter, TStream>\n\n// ===========================\n// Activity Result Types\n// ===========================\n\n/**\n * Result type for the video activity, based on request type and streaming.\n * - If stream is true (create request): AsyncIterable<StreamChunk>\n * - Otherwise: Promise<VideoJobResult | VideoStatusResult | VideoUrlResult>\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type VideoActivityResult<\n TRequest extends 'create' | 'status' | 'url' = 'create',\n TStream extends boolean = false,\n> = TRequest extends 'status'\n ? Promise<VideoStatusResult>\n : TRequest extends 'url'\n ? Promise<VideoUrlResult>\n : TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<VideoJobResult>\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Generate video - creates a video generation job from a text prompt.\n *\n * Uses AI video generation models to create videos based on natural language descriptions.\n * Unlike image generation, video generation is asynchronous and requires polling for completion.\n *\n * When `stream: true` is passed, handles the full job lifecycle automatically:\n * create job → poll for status → stream updates → yield final result.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @example Create a video generation job\n * ```ts\n * import { generateVideo } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * // Start a video generation job\n * const { jobId } = await generateVideo({\n * adapter: openaiVideo('sora-2'),\n * prompt: 'A cat chasing a dog in a sunny park'\n * })\n *\n * console.log('Job started:', jobId)\n * ```\n *\n * @example Stream the full video generation lifecycle\n * ```ts\n * import { generateVideo, toServerSentEventsResponse } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * const stream = generateVideo({\n * adapter: openaiVideo('sora-2'),\n * prompt: 'A cat chasing a dog in a sunny park',\n * stream: true,\n * pollingInterval: 3000,\n * })\n *\n * return toServerSentEventsResponse(stream)\n * ```\n */\nexport function generateVideo<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TStream extends boolean = false,\n>(\n options: VideoCreateOptions<TAdapter, TStream>,\n): VideoActivityResult<'create', TStream> {\n if (options.stream) {\n return runStreamingVideoGeneration(\n options as VideoCreateOptions<TAdapter, true>,\n ) as VideoActivityResult<'create', TStream>\n }\n\n return runCreateVideoJob(options) as VideoActivityResult<'create', TStream>\n}\n\n/**\n * Internal implementation of non-streaming video job creation.\n */\nasync function runCreateVideoJob<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: VideoCreateOptions<TAdapter, boolean>): Promise<VideoJobResult> {\n const { adapter, prompt, size, duration, modelOptions } = options\n const model = adapter.model\n\n return adapter.createVideoJob({\n model,\n prompt,\n size,\n duration,\n modelOptions,\n })\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Internal streaming implementation for video generation.\n * Handles the full job lifecycle: create job → poll for status → stream updates → yield final result.\n */\nasync function* runStreamingVideoGeneration<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: VideoCreateOptions<TAdapter, true>): AsyncIterable<StreamChunk> {\n const { adapter, prompt, size, duration, modelOptions } = options\n const model = adapter.model\n const runId = options.runId ?? createId('run')\n const pollingInterval = options.pollingInterval ?? 2000\n const maxDuration = options.maxDuration ?? 600_000\n\n yield {\n type: 'RUN_STARTED',\n runId,\n timestamp: Date.now(),\n }\n\n try {\n // Create the video generation job\n const jobResult = await adapter.createVideoJob({\n model,\n prompt,\n size,\n duration,\n modelOptions,\n })\n\n yield {\n type: 'CUSTOM',\n name: 'video:job:created',\n value: { jobId: jobResult.jobId },\n timestamp: Date.now(),\n }\n\n // Poll for completion\n const startTime = Date.now()\n while (Date.now() - startTime < maxDuration) {\n await sleep(pollingInterval)\n\n const statusResult = await adapter.getVideoStatus(jobResult.jobId)\n\n yield {\n type: 'CUSTOM',\n name: 'video:status',\n value: {\n jobId: jobResult.jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n },\n timestamp: Date.now(),\n }\n\n if (statusResult.status === 'completed') {\n const urlResult = await adapter.getVideoUrl(jobResult.jobId)\n\n yield {\n type: 'CUSTOM',\n name: 'generation:result',\n value: {\n jobId: jobResult.jobId,\n status: 'completed',\n url: urlResult.url,\n expiresAt: urlResult.expiresAt,\n },\n timestamp: Date.now(),\n }\n\n yield {\n type: 'RUN_FINISHED',\n runId,\n finishReason: 'stop',\n timestamp: Date.now(),\n }\n return\n }\n\n if (statusResult.status === 'failed') {\n throw new Error(statusResult.error || 'Video generation failed')\n }\n }\n\n throw new Error('Video generation timed out')\n } catch (error: any) {\n yield {\n type: 'RUN_ERROR',\n runId,\n error: {\n message: error.message || 'Video generation failed',\n code: error.code,\n },\n timestamp: Date.now(),\n }\n }\n}\n\n/**\n * Get video job status - returns the current status, progress, and URL if available.\n *\n * This function combines status checking and URL retrieval. If the job is completed,\n * it will automatically fetch and include the video URL.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @example Check job status\n * ```ts\n * import { getVideoJobStatus } from '@tanstack/ai'\n * import { openaiVideo } from '@tanstack/ai-openai'\n *\n * const result = await getVideoJobStatus({\n * adapter: openaiVideo('sora-2'),\n * jobId: 'job-123'\n * })\n *\n * console.log('Status:', result.status)\n * console.log('Progress:', result.progress)\n * if (result.url) {\n * console.log('Video URL:', result.url)\n * }\n * ```\n */\nexport async function getVideoJobStatus<\n TAdapter extends VideoAdapter<string, any, any, any>,\n>(options: {\n adapter: TAdapter & { kind: typeof kind }\n jobId: string\n}): Promise<{\n status: 'pending' | 'processing' | 'completed' | 'failed'\n progress?: number\n url?: string\n error?: string\n}> {\n const { adapter, jobId } = options\n const requestId = createId('video-status')\n const startTime = Date.now()\n\n aiEventClient.emit('video:request:started', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n timestamp: startTime,\n })\n\n // Get status first\n const statusResult = await adapter.getVideoStatus(jobId)\n\n // If completed, also get the URL\n if (statusResult.status === 'completed') {\n try {\n const urlResult = await adapter.getVideoUrl(jobId)\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n url: urlResult.url,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n url: urlResult.url,\n }\n } catch (error) {\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n error:\n error instanceof Error ? error.message : 'Failed to get video URL',\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n // If URL fetch fails, still return status\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n error:\n error instanceof Error ? error.message : 'Failed to get video URL',\n }\n }\n }\n\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n\n // Return status for non-completed jobs\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n error: statusResult.error,\n }\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the generateVideo() function without executing.\n */\nexport function createVideoOptions<\n TAdapter extends VideoAdapter<string, any, any, any>,\n TStream extends boolean = false,\n>(\n options: VideoCreateOptions<TAdapter, TStream>,\n): VideoCreateOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n VideoAdapter,\n VideoAdapterConfig,\n AnyVideoAdapter,\n} from './adapter'\nexport { BaseVideoAdapter } from './adapter'\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StreamChunk } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wrap a one-shot generation result as a StreamChunk async iterable.
|
|
4
|
+
*
|
|
5
|
+
* This allows non-streaming activities (image, speech, transcription, summarize)
|
|
6
|
+
* to be sent over the same streaming transport as chat.
|
|
7
|
+
*
|
|
8
|
+
* @param generator - An async function that performs the generation and returns the result
|
|
9
|
+
* @param options - Optional configuration (runId)
|
|
10
|
+
* @returns An AsyncIterable of StreamChunks with RUN_STARTED, CUSTOM(generation:result), and RUN_FINISHED events
|
|
11
|
+
*/
|
|
12
|
+
export declare function streamGenerationResult<TResult>(generator: () => Promise<TResult>, options?: {
|
|
13
|
+
runId?: string;
|
|
14
|
+
}): AsyncIterable<StreamChunk>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function createId(prefix) {
|
|
2
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
3
|
+
}
|
|
4
|
+
async function* streamGenerationResult(generator, options) {
|
|
5
|
+
const runId = createId("run");
|
|
6
|
+
yield {
|
|
7
|
+
type: "RUN_STARTED",
|
|
8
|
+
runId,
|
|
9
|
+
timestamp: Date.now()
|
|
10
|
+
};
|
|
11
|
+
try {
|
|
12
|
+
const result = await generator();
|
|
13
|
+
yield {
|
|
14
|
+
type: "CUSTOM",
|
|
15
|
+
name: "generation:result",
|
|
16
|
+
value: result,
|
|
17
|
+
timestamp: Date.now()
|
|
18
|
+
};
|
|
19
|
+
yield {
|
|
20
|
+
type: "RUN_FINISHED",
|
|
21
|
+
runId,
|
|
22
|
+
finishReason: "stop",
|
|
23
|
+
timestamp: Date.now()
|
|
24
|
+
};
|
|
25
|
+
} catch (error) {
|
|
26
|
+
yield {
|
|
27
|
+
type: "RUN_ERROR",
|
|
28
|
+
runId,
|
|
29
|
+
error: {
|
|
30
|
+
message: error.message || "Generation failed",
|
|
31
|
+
code: error.code
|
|
32
|
+
},
|
|
33
|
+
timestamp: Date.now()
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
streamGenerationResult
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=stream-generation-result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-generation-result.js","sources":["../../../src/activities/stream-generation-result.ts"],"sourcesContent":["/**\n * Internal helper for wrapping one-shot generation results as StreamChunk\n * async iterables. NOT exported from the package — used only by activity\n * implementations to support `stream: true`.\n */\n\nimport type { StreamChunk } from '../types'\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n/**\n * Wrap a one-shot generation result as a StreamChunk async iterable.\n *\n * This allows non-streaming activities (image, speech, transcription, summarize)\n * to be sent over the same streaming transport as chat.\n *\n * @param generator - An async function that performs the generation and returns the result\n * @param options - Optional configuration (runId)\n * @returns An AsyncIterable of StreamChunks with RUN_STARTED, CUSTOM(generation:result), and RUN_FINISHED events\n */\nexport async function* streamGenerationResult<TResult>(\n generator: () => Promise<TResult>,\n options?: { runId?: string },\n): AsyncIterable<StreamChunk> {\n const runId = options?.runId ?? createId('run')\n\n yield {\n type: 'RUN_STARTED',\n runId,\n timestamp: Date.now(),\n }\n\n try {\n const result = await generator()\n\n yield {\n type: 'CUSTOM',\n name: 'generation:result',\n value: result as unknown,\n timestamp: Date.now(),\n }\n\n yield {\n type: 'RUN_FINISHED',\n runId,\n finishReason: 'stop',\n timestamp: Date.now(),\n }\n } catch (error: any) {\n yield {\n type: 'RUN_ERROR',\n runId,\n error: {\n message: error.message || 'Generation failed',\n code: error.code,\n },\n timestamp: Date.now(),\n }\n }\n}\n"],"names":[],"mappings":"AAQA,SAAS,SAAS,QAAwB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAYA,gBAAuB,uBACrB,WACA,SAC4B;AAC5B,QAAM,QAA0B,SAAS,KAAK;AAE9C,QAAM;AAAA,IACJ,MAAM;AAAA,IACN;AAAA,IACA,WAAW,KAAK,IAAA;AAAA,EAAI;AAGtB,MAAI;AACF,UAAM,SAAS,MAAM,UAAA;AAErB,UAAM;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,WAAW,KAAK,IAAA;AAAA,IAAI;AAGtB,UAAM;AAAA,MACJ,MAAM;AAAA,MACN;AAAA,MACA,cAAc;AAAA,MACd,WAAW,KAAK,IAAA;AAAA,IAAI;AAAA,EAExB,SAAS,OAAY;AACnB,UAAM;AAAA,MACJ,MAAM;AAAA,MACN;AAAA,MACA,OAAO;AAAA,QACL,SAAS,MAAM,WAAW;AAAA,QAC1B,MAAM,MAAM;AAAA,MAAA;AAAA,MAEd,WAAW,KAAK,IAAA;AAAA,IAAI;AAAA,EAExB;AACF;"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { aiEventClient } from "../../event-client.js";
|
|
2
|
+
import { streamGenerationResult } from "../stream-generation-result.js";
|
|
2
3
|
const kind = "summarize";
|
|
3
4
|
function createId(prefix) {
|
|
4
5
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
@@ -62,23 +63,7 @@ async function* runStreamingSummarize(options) {
|
|
|
62
63
|
yield* adapter.summarizeStream(summarizeOptions);
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
yield {
|
|
67
|
-
type: "TEXT_MESSAGE_CONTENT",
|
|
68
|
-
messageId: result.id,
|
|
69
|
-
model: result.model,
|
|
70
|
-
timestamp: Date.now(),
|
|
71
|
-
delta: result.summary,
|
|
72
|
-
content: result.summary
|
|
73
|
-
};
|
|
74
|
-
yield {
|
|
75
|
-
type: "RUN_FINISHED",
|
|
76
|
-
runId: result.id,
|
|
77
|
-
model: result.model,
|
|
78
|
-
timestamp: Date.now(),
|
|
79
|
-
finishReason: "stop",
|
|
80
|
-
usage: result.usage
|
|
81
|
-
};
|
|
66
|
+
yield* streamGenerationResult(() => adapter.summarize(summarizeOptions));
|
|
82
67
|
}
|
|
83
68
|
function createSummarizeOptions(options) {
|
|
84
69
|
return options;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/activities/summarize/index.ts"],"sourcesContent":["/**\n * Summarize Activity\n *\n * Generates summaries from text input.\n * This is a self-contained module with implementation, types, and JSDoc.\n */\n\nimport { aiEventClient } from '../../event-client.js'\nimport type { SummarizeAdapter } from './adapter'\nimport type {\n StreamChunk,\n SummarizationOptions,\n SummarizationResult,\n} from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'summarize' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/** Extract provider options from a SummarizeAdapter via ~types */\nexport type SummarizeProviderOptions<TAdapter> =\n TAdapter extends SummarizeAdapter<any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the summarize activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The summarize adapter type\n * @template TStream - Whether to stream the output\n */\nexport interface SummarizeActivityOptions<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n> {\n /** The summarize adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** The text to summarize */\n text: string\n /** Maximum length of the summary (in words or characters, provider-dependent) */\n maxLength?: number\n /** Style of summary to generate */\n style?: 'bullet-points' | 'paragraph' | 'concise'\n /** Topics or aspects to focus on in the summary */\n focus?: Array<string>\n /** Provider-specific options */\n modelOptions?: SummarizeProviderOptions<TAdapter>\n /**\n * Whether to stream the summarization result.\n * When true, returns an AsyncIterable<StreamChunk> for streaming output.\n * When false or not provided, returns a Promise<SummarizationResult>.\n *\n * @default false\n */\n stream?: TStream\n}\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n/**\n * Result type for the summarize activity.\n * - If stream is true: AsyncIterable<StreamChunk>\n * - Otherwise: Promise<SummarizationResult>\n */\nexport type SummarizeActivityResult<TStream extends boolean> =\n TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<SummarizationResult>\n\n// ===========================\n// Helper Functions\n// ===========================\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Summarize activity - generates summaries from text.\n *\n * Supports both streaming and non-streaming modes.\n *\n * @example Basic summarization\n * ```ts\n * import { summarize } from '@tanstack/ai'\n * import { openaiSummarize } from '@tanstack/ai-openai'\n *\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...'\n * })\n *\n * console.log(result.summary)\n * ```\n *\n * @example Summarization with style\n * ```ts\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...',\n * style: 'bullet-points',\n * maxLength: 100\n * })\n * ```\n *\n * @example Focused summarization\n * ```ts\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long technical document...',\n * focus: ['key findings', 'methodology']\n * })\n * ```\n *\n * @example Streaming summarization\n * ```ts\n * for await (const chunk of summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...',\n * stream: true\n * })) {\n * if (chunk.type === 'content') {\n * process.stdout.write(chunk.delta)\n * }\n * }\n * ```\n */\nexport function summarize<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n>(\n options: SummarizeActivityOptions<TAdapter, TStream>,\n): SummarizeActivityResult<TStream> {\n const { stream } = options\n\n if (stream) {\n return runStreamingSummarize(\n options as unknown as SummarizeActivityOptions<\n SummarizeAdapter<string, object>,\n true\n >,\n ) as SummarizeActivityResult<TStream>\n }\n\n return runSummarize(\n options as unknown as SummarizeActivityOptions<\n SummarizeAdapter<string, object>,\n false\n >,\n ) as SummarizeActivityResult<TStream>\n}\n\n/**\n * Run non-streaming summarization\n */\nasync function runSummarize(\n options: SummarizeActivityOptions<SummarizeAdapter<string, object>, false>,\n): Promise<SummarizationResult> {\n const { adapter, text, maxLength, style, focus } = options\n const model = adapter.model\n const requestId = createId('summarize')\n const inputLength = text.length\n const startTime = Date.now()\n\n aiEventClient.emit('summarize:request:started', {\n requestId,\n provider: adapter.name,\n model,\n inputLength,\n timestamp: startTime,\n })\n\n const summarizeOptions: SummarizationOptions = {\n model,\n text,\n maxLength,\n style,\n focus,\n }\n\n const result = await adapter.summarize(summarizeOptions)\n\n const duration = Date.now() - startTime\n const outputLength = result.summary.length\n\n aiEventClient.emit('summarize:request:completed', {\n requestId,\n provider: adapter.name,\n model,\n inputLength,\n outputLength,\n duration,\n timestamp: Date.now(),\n })\n\n return result\n}\n\n/**\n * Run streaming summarization\n * Uses the adapter's native streaming if available, otherwise falls back\n * to non-streaming and yields the result as a single chunk.\n */\nasync function* runStreamingSummarize(\n options: SummarizeActivityOptions<SummarizeAdapter<string, object>, true>,\n): AsyncIterable<StreamChunk> {\n const { adapter, text, maxLength, style, focus } = options\n const model = adapter.model\n\n const summarizeOptions: SummarizationOptions = {\n model,\n text,\n maxLength,\n style,\n focus,\n }\n\n // Use real streaming if the adapter supports it\n if (adapter.summarizeStream) {\n yield* adapter.summarizeStream(summarizeOptions)\n return\n }\n\n // Fall back to non-streaming
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/activities/summarize/index.ts"],"sourcesContent":["/**\n * Summarize Activity\n *\n * Generates summaries from text input.\n * This is a self-contained module with implementation, types, and JSDoc.\n */\n\nimport { aiEventClient } from '../../event-client.js'\nimport { streamGenerationResult } from '../stream-generation-result.js'\nimport type { SummarizeAdapter } from './adapter'\nimport type {\n StreamChunk,\n SummarizationOptions,\n SummarizationResult,\n} from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'summarize' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/** Extract provider options from a SummarizeAdapter via ~types */\nexport type SummarizeProviderOptions<TAdapter> =\n TAdapter extends SummarizeAdapter<any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the summarize activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The summarize adapter type\n * @template TStream - Whether to stream the output\n */\nexport interface SummarizeActivityOptions<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n> {\n /** The summarize adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** The text to summarize */\n text: string\n /** Maximum length of the summary (in words or characters, provider-dependent) */\n maxLength?: number\n /** Style of summary to generate */\n style?: 'bullet-points' | 'paragraph' | 'concise'\n /** Topics or aspects to focus on in the summary */\n focus?: Array<string>\n /** Provider-specific options */\n modelOptions?: SummarizeProviderOptions<TAdapter>\n /**\n * Whether to stream the summarization result.\n * When true, returns an AsyncIterable<StreamChunk> for streaming output.\n * When false or not provided, returns a Promise<SummarizationResult>.\n *\n * @default false\n */\n stream?: TStream\n}\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n/**\n * Result type for the summarize activity.\n * - If stream is true: AsyncIterable<StreamChunk>\n * - Otherwise: Promise<SummarizationResult>\n */\nexport type SummarizeActivityResult<TStream extends boolean> =\n TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<SummarizationResult>\n\n// ===========================\n// Helper Functions\n// ===========================\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Summarize activity - generates summaries from text.\n *\n * Supports both streaming and non-streaming modes.\n *\n * @example Basic summarization\n * ```ts\n * import { summarize } from '@tanstack/ai'\n * import { openaiSummarize } from '@tanstack/ai-openai'\n *\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...'\n * })\n *\n * console.log(result.summary)\n * ```\n *\n * @example Summarization with style\n * ```ts\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...',\n * style: 'bullet-points',\n * maxLength: 100\n * })\n * ```\n *\n * @example Focused summarization\n * ```ts\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long technical document...',\n * focus: ['key findings', 'methodology']\n * })\n * ```\n *\n * @example Streaming summarization\n * ```ts\n * for await (const chunk of summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...',\n * stream: true\n * })) {\n * if (chunk.type === 'content') {\n * process.stdout.write(chunk.delta)\n * }\n * }\n * ```\n */\nexport function summarize<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n>(\n options: SummarizeActivityOptions<TAdapter, TStream>,\n): SummarizeActivityResult<TStream> {\n const { stream } = options\n\n if (stream) {\n return runStreamingSummarize(\n options as unknown as SummarizeActivityOptions<\n SummarizeAdapter<string, object>,\n true\n >,\n ) as SummarizeActivityResult<TStream>\n }\n\n return runSummarize(\n options as unknown as SummarizeActivityOptions<\n SummarizeAdapter<string, object>,\n false\n >,\n ) as SummarizeActivityResult<TStream>\n}\n\n/**\n * Run non-streaming summarization\n */\nasync function runSummarize(\n options: SummarizeActivityOptions<SummarizeAdapter<string, object>, false>,\n): Promise<SummarizationResult> {\n const { adapter, text, maxLength, style, focus } = options\n const model = adapter.model\n const requestId = createId('summarize')\n const inputLength = text.length\n const startTime = Date.now()\n\n aiEventClient.emit('summarize:request:started', {\n requestId,\n provider: adapter.name,\n model,\n inputLength,\n timestamp: startTime,\n })\n\n const summarizeOptions: SummarizationOptions = {\n model,\n text,\n maxLength,\n style,\n focus,\n }\n\n const result = await adapter.summarize(summarizeOptions)\n\n const duration = Date.now() - startTime\n const outputLength = result.summary.length\n\n aiEventClient.emit('summarize:request:completed', {\n requestId,\n provider: adapter.name,\n model,\n inputLength,\n outputLength,\n duration,\n timestamp: Date.now(),\n })\n\n return result\n}\n\n/**\n * Run streaming summarization\n * Uses the adapter's native streaming if available, otherwise falls back\n * to non-streaming and yields the result as a single chunk.\n */\nasync function* runStreamingSummarize(\n options: SummarizeActivityOptions<SummarizeAdapter<string, object>, true>,\n): AsyncIterable<StreamChunk> {\n const { adapter, text, maxLength, style, focus } = options\n const model = adapter.model\n\n const summarizeOptions: SummarizationOptions = {\n model,\n text,\n maxLength,\n style,\n focus,\n }\n\n // Use real streaming if the adapter supports it\n if (adapter.summarizeStream) {\n yield* adapter.summarizeStream(summarizeOptions)\n return\n }\n\n // Fall back to non-streaming — wrap result with streamGenerationResult\n yield* streamGenerationResult(() => adapter.summarize(summarizeOptions))\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the summarize() function without executing.\n */\nexport function createSummarizeOptions<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n>(\n options: SummarizeActivityOptions<TAdapter, TStream>,\n): SummarizeActivityOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n SummarizeAdapter,\n SummarizeAdapterConfig,\n AnySummarizeAdapter,\n} from './adapter'\nexport { BaseSummarizeAdapter } from './adapter'\n"],"names":[],"mappings":";;AAqBO,MAAM,OAAO;AAmEpB,SAAS,SAAS,QAAwB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAwDO,SAAS,UAId,SACkC;AAClC,QAAM,EAAE,WAAW;AAEnB,MAAI,QAAQ;AACV,WAAO;AAAA,MACL;AAAA,IAAA;AAAA,EAKJ;AAEA,SAAO;AAAA,IACL;AAAA,EAAA;AAKJ;AAKA,eAAe,aACb,SAC8B;AAC9B,QAAM,EAAE,SAAS,MAAM,WAAW,OAAO,UAAU;AACnD,QAAM,QAAQ,QAAQ;AACtB,QAAM,YAAY,SAAS,WAAW;AACtC,QAAM,cAAc,KAAK;AACzB,QAAM,YAAY,KAAK,IAAA;AAEvB,gBAAc,KAAK,6BAA6B;AAAA,IAC9C;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EAAA,CACZ;AAED,QAAM,mBAAyC;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAGF,QAAM,SAAS,MAAM,QAAQ,UAAU,gBAAgB;AAEvD,QAAM,WAAW,KAAK,IAAA,IAAQ;AAC9B,QAAM,eAAe,OAAO,QAAQ;AAEpC,gBAAc,KAAK,+BAA+B;AAAA,IAChD;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,KAAK,IAAA;AAAA,EAAI,CACrB;AAED,SAAO;AACT;AAOA,gBAAgB,sBACd,SAC4B;AAC5B,QAAM,EAAE,SAAS,MAAM,WAAW,OAAO,UAAU;AACnD,QAAM,QAAQ,QAAQ;AAEtB,QAAM,mBAAyC;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAIF,MAAI,QAAQ,iBAAiB;AAC3B,WAAO,QAAQ,gBAAgB,gBAAgB;AAC/C;AAAA,EACF;AAGA,SAAO,uBAAuB,MAAM,QAAQ,UAAU,gBAAgB,CAAC;AACzE;AASO,SAAS,uBAId,SAC6C;AAC7C,SAAO;AACT;"}
|
package/package.json
CHANGED
|
@@ -243,12 +243,9 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
243
243
|
for (const part of uiMessage.parts) {
|
|
244
244
|
if (part.type !== 'tool-call') continue
|
|
245
245
|
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
!part.approval &&
|
|
250
|
-
!emittedToolResultIds.has(part.id)
|
|
251
|
-
) {
|
|
246
|
+
// Output takes priority — if the tool has already produced a result,
|
|
247
|
+
// emit the concrete output regardless of approval metadata.
|
|
248
|
+
if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {
|
|
252
249
|
messageList.push({
|
|
253
250
|
role: 'tool',
|
|
254
251
|
content: JSON.stringify(part.output),
|
|
@@ -257,8 +254,9 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
257
254
|
emittedToolResultIds.add(part.id)
|
|
258
255
|
}
|
|
259
256
|
|
|
260
|
-
// Approval response
|
|
257
|
+
// Approval response without output — emit approval status for iteration tracking
|
|
261
258
|
if (
|
|
259
|
+
part.output === undefined &&
|
|
262
260
|
part.state === 'approval-responded' &&
|
|
263
261
|
part.approval?.approved !== undefined &&
|
|
264
262
|
!emittedToolResultIds.has(part.id)
|
|
@@ -63,8 +63,7 @@ export function updateToolCallPart(
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const parts = [...msg.parts]
|
|
66
|
-
|
|
67
|
-
const existingPartIndex = parts.findIndex(
|
|
66
|
+
const existing = parts.find(
|
|
68
67
|
(p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCall.id,
|
|
69
68
|
)
|
|
70
69
|
|
|
@@ -74,11 +73,14 @@ export function updateToolCallPart(
|
|
|
74
73
|
name: toolCall.name,
|
|
75
74
|
arguments: toolCall.arguments,
|
|
76
75
|
state: toolCall.state,
|
|
76
|
+
// Carry forward approval and output from the existing part
|
|
77
|
+
...(existing?.approval && { approval: { ...existing.approval } }),
|
|
78
|
+
...(existing?.output !== undefined && { output: existing.output }),
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
if (
|
|
81
|
+
if (existing) {
|
|
80
82
|
// Update existing tool call
|
|
81
|
-
parts[
|
|
83
|
+
parts[parts.indexOf(existing)] = toolCallPart
|
|
82
84
|
} else {
|
|
83
85
|
// Add new tool call at the end (preserve natural streaming order)
|
|
84
86
|
parts.push(toolCallPart)
|