@tanstack/ai 0.28.0 → 0.29.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/dist/esm/activities/chat/index.js +10 -10
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +1 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +6 -0
- package/dist/esm/activities/chat/stream/processor.js +18 -3
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +2 -1
- package/dist/esm/activities/generateVideo/index.js +12 -2
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/logger/console-logger.d.ts +18 -0
- package/dist/esm/logger/console-logger.js +64 -8
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/types.d.ts +4 -4
- package/dist/esm/realtime/index.d.ts +1 -3
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/types.d.ts +7 -1
- package/package.json +2 -2
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -2
- package/skills/ai-core/media-generation/SKILL.md +29 -1
- package/src/activities/chat/index.ts +14 -13
- package/src/activities/chat/messages.ts +1 -0
- package/src/activities/chat/stream/message-updaters.ts +1 -1
- package/src/activities/chat/stream/processor.ts +30 -3
- package/src/activities/generateVideo/index.ts +12 -0
- package/src/logger/console-logger.ts +112 -17
- package/src/logger/types.ts +4 -4
- package/src/realtime/index.ts +1 -3
- package/src/types.ts +7 -0
|
@@ -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 '@tanstack/ai-event-client'\nimport { toRunErrorPayload } from '../error-payload'\nimport { resolveDebugOption } from '../../logger/resolve'\nimport type { InternalLogger } from '../../logger/internal-logger'\nimport type { DebugOption } from '../../logger/types'\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 /**\n * Enable debug logging. Pass `true` to enable all categories, `false` to\n * silence everything including errors, or a `DebugConfig` object for granular\n * control and/or a custom `Logger`.\n */\n debug?: DebugOption\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 const logger: InternalLogger = resolveDebugOption(options.debug)\n const providerName =\n (adapter as { name?: string; provider?: string }).provider ??\n (adapter as { name?: string }).name ??\n 'unknown'\n\n logger.request(`activity=generateVideo provider=${providerName}`, {\n provider: providerName,\n model,\n })\n\n try {\n const result = await adapter.createVideoJob({\n model,\n prompt,\n size,\n duration,\n modelOptions,\n logger,\n })\n logger.output(`activity=generateVideo jobId=${result.jobId}`, {\n jobId: result.jobId,\n model: result.model,\n })\n return result\n } catch (error) {\n logger.errors('generateVideo activity failed', {\n error,\n source: 'generateVideo',\n })\n throw error\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 const logger: InternalLogger = resolveDebugOption(options.debug)\n const providerName =\n (adapter as { name?: string; provider?: string }).provider ??\n (adapter as { name?: string }).name ??\n 'unknown'\n\n const threadId = createId('thread')\n\n yield {\n type: 'RUN_STARTED',\n runId,\n threadId,\n timestamp: Date.now(),\n } as StreamChunk\n\n logger.request(\n `activity=generateVideo provider=${providerName} stream=true`,\n {\n provider: providerName,\n model,\n },\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 logger,\n })\n\n yield {\n type: 'CUSTOM',\n name: 'video:job:created',\n value: { jobId: jobResult.jobId },\n timestamp: Date.now(),\n } as StreamChunk\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 } as StreamChunk\n\n if (statusResult.status === 'completed') {\n const urlResult = await adapter.getVideoUrl(jobResult.jobId)\n\n logger.output(\n `activity=generateVideo jobId=${jobResult.jobId} status=completed`,\n {\n jobId: jobResult.jobId,\n url: urlResult.url,\n },\n )\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 } as StreamChunk\n\n yield {\n type: 'RUN_FINISHED',\n runId,\n threadId,\n finishReason: 'stop',\n timestamp: Date.now(),\n } as StreamChunk\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: unknown) {\n const payload = toRunErrorPayload(error, 'Video generation failed')\n logger.errors('generateVideo activity failed', {\n message: payload.message,\n code: payload.code,\n source: 'generateVideo',\n })\n yield {\n type: 'RUN_ERROR',\n runId,\n threadId,\n message: payload.message,\n code: payload.code,\n error: payload,\n timestamp: Date.now(),\n } as StreamChunk\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 const errorMessage =\n error instanceof Error ? error.message : 'Failed to get video URL'\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: 'failed',\n progress: statusResult.progress,\n error: errorMessage,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n // Provider reported completed but result fetch failed — treat as failed\n return {\n status: 'failed' as const,\n progress: statusResult.progress,\n error: errorMessage,\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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"}
|
|
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 '@tanstack/ai-event-client'\nimport { toRunErrorPayload } from '../error-payload'\nimport { resolveDebugOption } from '../../logger/resolve'\nimport type { InternalLogger } from '../../logger/internal-logger'\nimport type { DebugOption } from '../../logger/types'\nimport type { VideoAdapter } from './adapter'\nimport type {\n StreamChunk,\n TokenUsage,\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 /**\n * Enable debug logging. Pass `true` to enable all categories, `false` to\n * silence everything including errors, or a `DebugConfig` object for granular\n * control and/or a custom `Logger`.\n */\n debug?: DebugOption\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 const logger: InternalLogger = resolveDebugOption(options.debug)\n const providerName =\n (adapter as { name?: string; provider?: string }).provider ??\n (adapter as { name?: string }).name ??\n 'unknown'\n\n logger.request(`activity=generateVideo provider=${providerName}`, {\n provider: providerName,\n model,\n })\n\n try {\n const result = await adapter.createVideoJob({\n model,\n prompt,\n size,\n duration,\n modelOptions,\n logger,\n })\n logger.output(`activity=generateVideo jobId=${result.jobId}`, {\n jobId: result.jobId,\n model: result.model,\n })\n return result\n } catch (error) {\n logger.errors('generateVideo activity failed', {\n error,\n source: 'generateVideo',\n })\n throw error\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 const logger: InternalLogger = resolveDebugOption(options.debug)\n const providerName =\n (adapter as { name?: string; provider?: string }).provider ??\n (adapter as { name?: string }).name ??\n 'unknown'\n\n const threadId = createId('thread')\n\n yield {\n type: 'RUN_STARTED',\n runId,\n threadId,\n timestamp: Date.now(),\n } as StreamChunk\n\n logger.request(\n `activity=generateVideo provider=${providerName} stream=true`,\n {\n provider: providerName,\n model,\n },\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 logger,\n })\n\n yield {\n type: 'CUSTOM',\n name: 'video:job:created',\n value: { jobId: jobResult.jobId },\n timestamp: Date.now(),\n } as StreamChunk\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 } as StreamChunk\n\n if (statusResult.status === 'completed') {\n const urlResult = await adapter.getVideoUrl(jobResult.jobId)\n\n logger.output(\n `activity=generateVideo jobId=${jobResult.jobId} status=completed`,\n {\n jobId: jobResult.jobId,\n url: urlResult.url,\n },\n )\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 ...(urlResult.usage ? { usage: urlResult.usage } : {}),\n },\n timestamp: Date.now(),\n } as StreamChunk\n\n yield {\n type: 'RUN_FINISHED',\n runId,\n threadId,\n finishReason: 'stop',\n timestamp: Date.now(),\n } as StreamChunk\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: unknown) {\n const payload = toRunErrorPayload(error, 'Video generation failed')\n logger.errors('generateVideo activity failed', {\n message: payload.message,\n code: payload.code,\n source: 'generateVideo',\n })\n yield {\n type: 'RUN_ERROR',\n runId,\n threadId,\n message: payload.message,\n code: payload.code,\n error: payload,\n timestamp: Date.now(),\n } as StreamChunk\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 usage?: TokenUsage\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 if (urlResult.usage) {\n aiEventClient.emit('video:usage', {\n requestId,\n model: adapter.model,\n usage: urlResult.usage,\n timestamp: Date.now(),\n })\n }\n return {\n status: statusResult.status,\n progress: statusResult.progress,\n url: urlResult.url,\n ...(urlResult.usage ? { usage: urlResult.usage } : {}),\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'Failed to get video URL'\n aiEventClient.emit('video:request:completed', {\n requestId,\n provider: adapter.name,\n model: adapter.model,\n requestType: 'status',\n jobId,\n status: 'failed',\n progress: statusResult.progress,\n error: errorMessage,\n duration: Date.now() - startTime,\n timestamp: Date.now(),\n })\n // Provider reported completed but result fetch failed — treat as failed\n return {\n status: 'failed' as const,\n progress: statusResult.progress,\n error: errorMessage,\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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"}
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import { Logger } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default `Logger` implementation that routes each level to the matching
|
|
4
|
+
* `console` method:
|
|
5
|
+
*
|
|
6
|
+
* - `debug` → `console.debug`
|
|
7
|
+
* - `info` → `console.info`
|
|
8
|
+
* - `warn` → `console.warn`
|
|
9
|
+
* - `error` → `console.error`
|
|
10
|
+
*
|
|
11
|
+
* When a `meta` object is supplied it is rendered with the strategy that
|
|
12
|
+
* actually surfaces it on the current runtime (see {@link MetaStrategy}):
|
|
13
|
+
* depth-unlimited `console.dir` on Node, circular-safe JSON on Cloudflare
|
|
14
|
+
* Workers, and an extra console argument everywhere else.
|
|
15
|
+
*
|
|
16
|
+
* This is the logger used when `debug` is enabled on any activity and no
|
|
17
|
+
* custom `logger` is supplied via `debug: { logger }`.
|
|
18
|
+
*/
|
|
2
19
|
export declare class ConsoleLogger implements Logger {
|
|
3
20
|
/** Log a debug-level message; forwards to `console.debug`. */
|
|
4
21
|
debug(message: string, meta?: Record<string, unknown>): void;
|
|
@@ -8,4 +25,5 @@ export declare class ConsoleLogger implements Logger {
|
|
|
8
25
|
warn(message: string, meta?: Record<string, unknown>): void;
|
|
9
26
|
/** Log an error-level message; forwards to `console.error`. */
|
|
10
27
|
error(message: string, meta?: Record<string, unknown>): void;
|
|
28
|
+
private emit;
|
|
11
29
|
}
|
|
@@ -1,24 +1,80 @@
|
|
|
1
1
|
const DIR_OPTIONS = { depth: null, colors: true };
|
|
2
|
+
function resolveMetaStrategy() {
|
|
3
|
+
try {
|
|
4
|
+
if (globalThis.navigator?.userAgent === "Cloudflare-Workers") return "json";
|
|
5
|
+
} catch {
|
|
6
|
+
}
|
|
7
|
+
if (typeof process !== "undefined" && // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a partial process global (bundler shims) may lack versions
|
|
8
|
+
typeof process.versions?.node === "string") {
|
|
9
|
+
return "dir";
|
|
10
|
+
}
|
|
11
|
+
return "arg";
|
|
12
|
+
}
|
|
13
|
+
function stringifyMetaSafely(value) {
|
|
14
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
15
|
+
try {
|
|
16
|
+
return JSON.stringify(
|
|
17
|
+
value,
|
|
18
|
+
(_key, entry) => {
|
|
19
|
+
if (typeof entry === "bigint") return entry.toString();
|
|
20
|
+
if (entry instanceof Error) {
|
|
21
|
+
return {
|
|
22
|
+
name: entry.name,
|
|
23
|
+
message: entry.message,
|
|
24
|
+
stack: entry.stack
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (typeof entry === "object" && entry !== null) {
|
|
28
|
+
if (seen.has(entry)) return "[Circular]";
|
|
29
|
+
seen.add(entry);
|
|
30
|
+
}
|
|
31
|
+
return entry;
|
|
32
|
+
},
|
|
33
|
+
2
|
|
34
|
+
);
|
|
35
|
+
} catch {
|
|
36
|
+
try {
|
|
37
|
+
return String(value);
|
|
38
|
+
} catch {
|
|
39
|
+
return "[Unserializable meta]";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
2
43
|
class ConsoleLogger {
|
|
3
44
|
/** Log a debug-level message; forwards to `console.debug`. */
|
|
4
45
|
debug(message, meta) {
|
|
5
|
-
|
|
6
|
-
if (meta !== void 0) console.dir(meta, DIR_OPTIONS);
|
|
46
|
+
this.emit("debug", message, meta);
|
|
7
47
|
}
|
|
8
48
|
/** Log an info-level message; forwards to `console.info`. */
|
|
9
49
|
info(message, meta) {
|
|
10
|
-
|
|
11
|
-
if (meta !== void 0) console.dir(meta, DIR_OPTIONS);
|
|
50
|
+
this.emit("info", message, meta);
|
|
12
51
|
}
|
|
13
52
|
/** Log a warning-level message; forwards to `console.warn`. */
|
|
14
53
|
warn(message, meta) {
|
|
15
|
-
|
|
16
|
-
if (meta !== void 0) console.dir(meta, DIR_OPTIONS);
|
|
54
|
+
this.emit("warn", message, meta);
|
|
17
55
|
}
|
|
18
56
|
/** Log an error-level message; forwards to `console.error`. */
|
|
19
57
|
error(message, meta) {
|
|
20
|
-
|
|
21
|
-
|
|
58
|
+
this.emit("error", message, meta);
|
|
59
|
+
}
|
|
60
|
+
emit(level, message, meta) {
|
|
61
|
+
if (meta === void 0) {
|
|
62
|
+
console[level](message);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
switch (resolveMetaStrategy()) {
|
|
66
|
+
case "dir":
|
|
67
|
+
console[level](message);
|
|
68
|
+
console.dir(meta, DIR_OPTIONS);
|
|
69
|
+
break;
|
|
70
|
+
case "json":
|
|
71
|
+
console[level](`${message}
|
|
72
|
+
${stringifyMetaSafely(meta)}`);
|
|
73
|
+
break;
|
|
74
|
+
case "arg":
|
|
75
|
+
console[level](message, meta);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
22
78
|
}
|
|
23
79
|
}
|
|
24
80
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console-logger.js","sources":["../../../src/logger/console-logger.ts"],"sourcesContent":["import type { Logger } from './types'\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"console-logger.js","sources":["../../../src/logger/console-logger.ts"],"sourcesContent":["import type { Logger } from './types'\n\n/**\n * `util.inspect` options used with `console.dir` on Node so deeply nested\n * structures (e.g. provider chunk payloads with `usage`, `output`,\n * `reasoning`, `tools`) render in full instead of truncating to\n * `[Object]` / `[Array]`.\n */\nconst DIR_OPTIONS = { depth: null, colors: true } as const\n\n/**\n * How `meta` should be rendered on the current runtime:\n *\n * - `dir` — Node. `console.dir(meta, { depth: null, colors: true })` gives a\n * depth-unlimited, colored inspect dump.\n * - `json` — Cloudflare Workers / workerd. workerd never forwards\n * `console.dir` output to the terminal (with or without options), and its\n * own inspect of extra console arguments truncates nested objects, so the\n * payload is appended as circular-safe pretty-printed JSON instead.\n * - `arg` — everything else (browsers, Deno, Bun). `meta` is passed as an\n * extra console argument: devtools keep collapsible object trees and the\n * runtime's inspect handles circular references natively.\n */\ntype MetaStrategy = 'dir' | 'json' | 'arg'\n\nfunction resolveMetaStrategy(): MetaStrategy {\n // workerd must be detected before the Node check: under the `nodejs_compat`\n // flag it emulates `process.versions.node`, but still drops `console.dir`.\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- navigator is missing on Node < 21 despite the DOM lib typing it as always present\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') return 'json'\n } catch {\n // A locked-down runtime with a throwing `userAgent` getter is not workerd;\n // fall through to the remaining checks rather than crash the log call.\n }\n if (\n typeof process !== 'undefined' &&\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a partial process global (bundler shims) may lack versions\n typeof process.versions?.node === 'string'\n ) {\n return 'dir'\n }\n return 'arg'\n}\n\n/**\n * `JSON.stringify` hardened for debug payloads: circular references collapse\n * to `\"[Circular]\"`, `Error` instances expand to `name`/`message`/`stack`\n * (they would otherwise stringify to `{}`), and `bigint` values become\n * strings (they would otherwise throw). Never throws — falls back to\n * `String(value)` and, if even that coercion throws, a placeholder.\n */\nfunction stringifyMetaSafely(value: unknown): string {\n const seen = new WeakSet<object>()\n try {\n return JSON.stringify(\n value,\n (_key, entry: unknown) => {\n if (typeof entry === 'bigint') return entry.toString()\n if (entry instanceof Error) {\n return {\n name: entry.name,\n message: entry.message,\n stack: entry.stack,\n }\n }\n if (typeof entry === 'object' && entry !== null) {\n if (seen.has(entry)) return '[Circular]'\n seen.add(entry)\n }\n return entry\n },\n 2,\n )\n } catch {\n try {\n return String(value)\n } catch {\n return '[Unserializable meta]'\n }\n }\n}\n\n/**\n * Default `Logger` implementation that routes each level to the matching\n * `console` method:\n *\n * - `debug` → `console.debug`\n * - `info` → `console.info`\n * - `warn` → `console.warn`\n * - `error` → `console.error`\n *\n * When a `meta` object is supplied it is rendered with the strategy that\n * actually surfaces it on the current runtime (see {@link MetaStrategy}):\n * depth-unlimited `console.dir` on Node, circular-safe JSON on Cloudflare\n * Workers, and an extra console argument everywhere else.\n *\n * This is the logger used when `debug` is enabled on any activity and no\n * custom `logger` is supplied via `debug: { logger }`.\n */\nexport class ConsoleLogger implements Logger {\n /** Log a debug-level message; forwards to `console.debug`. */\n debug(message: string, meta?: Record<string, unknown>): void {\n this.emit('debug', message, meta)\n }\n\n /** Log an info-level message; forwards to `console.info`. */\n info(message: string, meta?: Record<string, unknown>): void {\n this.emit('info', message, meta)\n }\n\n /** Log a warning-level message; forwards to `console.warn`. */\n warn(message: string, meta?: Record<string, unknown>): void {\n this.emit('warn', message, meta)\n }\n\n /** Log an error-level message; forwards to `console.error`. */\n error(message: string, meta?: Record<string, unknown>): void {\n this.emit('error', message, meta)\n }\n\n private emit(\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n meta?: Record<string, unknown>,\n ): void {\n if (meta === undefined) {\n console[level](message)\n return\n }\n switch (resolveMetaStrategy()) {\n case 'dir':\n console[level](message)\n console.dir(meta, DIR_OPTIONS)\n break\n case 'json':\n console[level](`${message}\\n${stringifyMetaSafely(meta)}`)\n break\n case 'arg':\n console[level](message, meta)\n break\n }\n }\n}\n"],"names":[],"mappings":"AAQA,MAAM,cAAc,EAAE,OAAO,MAAM,QAAQ,KAAA;AAiB3C,SAAS,sBAAoC;AAG3C,MAAI;AAEF,QAAI,WAAW,WAAW,cAAc,qBAAsB,QAAO;AAAA,EACvE,QAAQ;AAAA,EAGR;AACA,MACE,OAAO,YAAY;AAAA,EAEnB,OAAO,QAAQ,UAAU,SAAS,UAClC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AASA,SAAS,oBAAoB,OAAwB;AACnD,QAAM,2BAAW,QAAA;AACjB,MAAI;AACF,WAAO,KAAK;AAAA,MACV;AAAA,MACA,CAAC,MAAM,UAAmB;AACxB,YAAI,OAAO,UAAU,SAAU,QAAO,MAAM,SAAA;AAC5C,YAAI,iBAAiB,OAAO;AAC1B,iBAAO;AAAA,YACL,MAAM,MAAM;AAAA,YACZ,SAAS,MAAM;AAAA,YACf,OAAO,MAAM;AAAA,UAAA;AAAA,QAEjB;AACA,YAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,cAAI,KAAK,IAAI,KAAK,EAAG,QAAO;AAC5B,eAAK,IAAI,KAAK;AAAA,QAChB;AACA,eAAO;AAAA,MACT;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ,QAAQ;AACN,QAAI;AACF,aAAO,OAAO,KAAK;AAAA,IACrB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAmBO,MAAM,cAAgC;AAAA;AAAA,EAE3C,MAAM,SAAiB,MAAsC;AAC3D,SAAK,KAAK,SAAS,SAAS,IAAI;AAAA,EAClC;AAAA;AAAA,EAGA,KAAK,SAAiB,MAAsC;AAC1D,SAAK,KAAK,QAAQ,SAAS,IAAI;AAAA,EACjC;AAAA;AAAA,EAGA,KAAK,SAAiB,MAAsC;AAC1D,SAAK,KAAK,QAAQ,SAAS,IAAI;AAAA,EACjC;AAAA;AAAA,EAGA,MAAM,SAAiB,MAAsC;AAC3D,SAAK,KAAK,SAAS,SAAS,IAAI;AAAA,EAClC;AAAA,EAEQ,KACN,OACA,SACA,MACM;AACN,QAAI,SAAS,QAAW;AACtB,cAAQ,KAAK,EAAE,OAAO;AACtB;AAAA,IACF;AACA,YAAQ,uBAAoB;AAAA,MAC1B,KAAK;AACH,gBAAQ,KAAK,EAAE,OAAO;AACtB,gBAAQ,IAAI,MAAM,WAAW;AAC7B;AAAA,MACF,KAAK;AACH,gBAAQ,KAAK,EAAE,GAAG,OAAO;AAAA,EAAK,oBAAoB,IAAI,CAAC,EAAE;AACzD;AAAA,MACF,KAAK;AACH,gBAAQ,KAAK,EAAE,SAAS,IAAI;AAC5B;AAAA,IAAA;AAAA,EAEN;AACF;"}
|
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
export interface Logger {
|
|
5
5
|
/**
|
|
6
6
|
* Called for chunk-level diagnostic output (raw provider chunks, per-chunk output, agent-loop iteration markers).
|
|
7
|
-
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record;
|
|
7
|
+
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record; the default `ConsoleLogger` renders it with a runtime-appropriate strategy (depth-unlimited `console.dir` on Node, JSON appended to the message on Cloudflare Workers, a second `console.<level>` argument elsewhere).
|
|
8
8
|
*/
|
|
9
9
|
debug: (message: string, meta?: Record<string, unknown>) => void;
|
|
10
10
|
/**
|
|
11
11
|
* Called for notable informational events (outgoing requests, tool invocations, middleware transitions).
|
|
12
|
-
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record;
|
|
12
|
+
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record; the default `ConsoleLogger` renders it with a runtime-appropriate strategy (depth-unlimited `console.dir` on Node, JSON appended to the message on Cloudflare Workers, a second `console.<level>` argument elsewhere).
|
|
13
13
|
*/
|
|
14
14
|
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
15
15
|
/**
|
|
16
16
|
* Called for notable warnings that don't halt execution (deprecations, recoverable anomalies).
|
|
17
|
-
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record;
|
|
17
|
+
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record; the default `ConsoleLogger` renders it with a runtime-appropriate strategy (depth-unlimited `console.dir` on Node, JSON appended to the message on Cloudflare Workers, a second `console.<level>` argument elsewhere).
|
|
18
18
|
*/
|
|
19
19
|
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
20
20
|
/**
|
|
21
21
|
* Called for caught exceptions throughout the pipeline.
|
|
22
|
-
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record;
|
|
22
|
+
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record; the default `ConsoleLogger` renders it with a runtime-appropriate strategy (depth-unlimited `console.dir` on Node, JSON appended to the message on Cloudflare Workers, a second `console.<level>` argument elsewhere).
|
|
23
23
|
*/
|
|
24
24
|
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
25
25
|
}
|
|
@@ -19,9 +19,7 @@ export type * from './types.js';
|
|
|
19
19
|
* .handler(async () => {
|
|
20
20
|
* return realtimeToken({
|
|
21
21
|
* adapter: openaiRealtimeToken({
|
|
22
|
-
* model: 'gpt-
|
|
23
|
-
* voice: 'alloy',
|
|
24
|
-
* instructions: 'You are a helpful assistant...',
|
|
22
|
+
* model: 'gpt-realtime',
|
|
25
23
|
* }),
|
|
26
24
|
* })
|
|
27
25
|
* })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/realtime/index.ts"],"sourcesContent":["import type { RealtimeToken, RealtimeTokenOptions } from './types'\n\n// Re-export all types\nexport type * from './types'\n\n/**\n * Generate a realtime token using the provided adapter.\n *\n * This function is used on the server to generate ephemeral tokens\n * that clients can use to establish realtime connections.\n *\n * @param options - Token generation options including the adapter\n * @returns Promise resolving to a RealtimeToken\n *\n * @example\n * ```typescript\n * import { realtimeToken } from '@tanstack/ai'\n * import { openaiRealtimeToken } from '@tanstack/ai-openai'\n *\n * // Server function (TanStack Start example)\n * export const getRealtimeToken = createServerFn()\n * .handler(async () => {\n * return realtimeToken({\n * adapter: openaiRealtimeToken({\n * model: 'gpt-
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/realtime/index.ts"],"sourcesContent":["import type { RealtimeToken, RealtimeTokenOptions } from './types'\n\n// Re-export all types\nexport type * from './types'\n\n/**\n * Generate a realtime token using the provided adapter.\n *\n * This function is used on the server to generate ephemeral tokens\n * that clients can use to establish realtime connections.\n *\n * @param options - Token generation options including the adapter\n * @returns Promise resolving to a RealtimeToken\n *\n * @example\n * ```typescript\n * import { realtimeToken } from '@tanstack/ai'\n * import { openaiRealtimeToken } from '@tanstack/ai-openai'\n *\n * // Server function (TanStack Start example)\n * export const getRealtimeToken = createServerFn()\n * .handler(async () => {\n * return realtimeToken({\n * adapter: openaiRealtimeToken({\n * model: 'gpt-realtime',\n * }),\n * })\n * })\n * ```\n */\nexport async function realtimeToken(\n options: RealtimeTokenOptions,\n): Promise<RealtimeToken> {\n const { adapter } = options\n return adapter.generateToken()\n}\n"],"names":[],"mappings":"AA8BA,eAAsB,cACpB,SACwB;AACxB,QAAM,EAAE,YAAY;AACpB,SAAO,QAAQ,cAAA;AACjB;"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { BaseEvent as AGUIBaseEvent, CustomEvent as AGUICustomEvent, MessagesSna
|
|
|
6
6
|
/**
|
|
7
7
|
* Tool call states - track the lifecycle of a tool call
|
|
8
8
|
*/
|
|
9
|
-
export type ToolCallState = 'awaiting-input' | 'input-streaming' | 'input-complete' | 'approval-requested' | 'approval-responded' | 'complete';
|
|
9
|
+
export type ToolCallState = 'awaiting-input' | 'input-streaming' | 'input-complete' | 'approval-requested' | 'approval-responded' | 'complete' | 'error';
|
|
10
10
|
/**
|
|
11
11
|
* Tool result states - track the lifecycle of a tool result
|
|
12
12
|
*/
|
|
@@ -1348,6 +1348,12 @@ export interface VideoUrlResult {
|
|
|
1348
1348
|
url: string;
|
|
1349
1349
|
/** When the URL expires, if applicable */
|
|
1350
1350
|
expiresAt?: Date;
|
|
1351
|
+
/**
|
|
1352
|
+
* Usage information for the completed generation, when the adapter can report
|
|
1353
|
+
* it. For usage-based providers (e.g. fal) this carries `unitsBilled` — the
|
|
1354
|
+
* real billed quantity — so consumers can compute exact cost.
|
|
1355
|
+
*/
|
|
1356
|
+
usage?: TokenUsage;
|
|
1351
1357
|
}
|
|
1352
1358
|
/**
|
|
1353
1359
|
* Options for text-to-speech generation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@ag-ui/core": "^0.0.52",
|
|
69
69
|
"@standard-schema/spec": "^1.1.0",
|
|
70
70
|
"partial-json": "^0.1.7",
|
|
71
|
-
"@tanstack/ai-event-client": "0.
|
|
71
|
+
"@tanstack/ai-event-client": "0.6.0"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"@opentelemetry/api": ">=1.9.0"
|
|
@@ -27,13 +27,13 @@ import { geminiImage } from '@tanstack/ai-gemini'
|
|
|
27
27
|
| Model | Max Input | Max Output | Notes |
|
|
28
28
|
| ------------------------------- | --------- | ---------- | ---------------------------- |
|
|
29
29
|
| `gemini-3.1-pro-preview` | 1M | 65K | Latest flagship, thinking |
|
|
30
|
-
| `gemini-3-pro-preview` | 1M | 65K | Previous flagship |
|
|
31
30
|
| `gemini-3-flash-preview` | 1M | 65K | Fast, thinking, multimodal |
|
|
31
|
+
| `gemini-3.1-flash-lite` | 1M | 65K | Budget GA, thinking |
|
|
32
32
|
| `gemini-3.1-flash-lite-preview` | 1M | 65K | Budget, still capable |
|
|
33
33
|
| `gemini-2.5-pro` | 1M | 65K | Stable release, all features |
|
|
34
34
|
| `gemini-2.5-flash` | 1M | 65K | Fast stable release |
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Most Gemini text models accept `text`, `image`, `audio`, `video`, and `document` input; `gemini-2.5-flash` accepts all of these except `document`.
|
|
37
37
|
|
|
38
38
|
## Provider-Specific modelOptions
|
|
39
39
|
|
|
@@ -343,10 +343,38 @@ const { generate, result, jobId, videoStatus, isLoading } = useGenerateVideo({
|
|
|
343
343
|
console.log(`${status.status} (${status.progress}%)`),
|
|
344
344
|
})
|
|
345
345
|
|
|
346
|
-
// videoStatus: { jobId, status, progress?, url?, error? }
|
|
346
|
+
// videoStatus: { jobId, status, progress?, url?, error?, usage? }
|
|
347
347
|
// result (on completion): { url }
|
|
348
348
|
```
|
|
349
349
|
|
|
350
|
+
### 6. Cost tracking (fal billable units)
|
|
351
|
+
|
|
352
|
+
fal bills media generation by usage-based units, not tokens. Every fal media
|
|
353
|
+
adapter (`falImage`, `falAudio`, `falSpeech`, `falTranscription`, `falVideo`)
|
|
354
|
+
surfaces the real billed quantity on the result as `usage.unitsBilled`, read
|
|
355
|
+
from fal's `x-fal-billable-units` response header — no `fetch` interceptor
|
|
356
|
+
needed. It rides on the canonical `TokenUsage` shape (token fields are `0` for
|
|
357
|
+
media), mirroring how duration-billed transcription surfaces `durationSeconds`.
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
import { generateImage } from '@tanstack/ai'
|
|
361
|
+
import { falImage } from '@tanstack/ai-fal'
|
|
362
|
+
|
|
363
|
+
const result = await generateImage({
|
|
364
|
+
adapter: falImage('fal-ai/flux/dev'),
|
|
365
|
+
prompt: 'a serene mountain lake',
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
// usage.unitsBilled is the priced quantity. Multiply by the endpoint unit
|
|
369
|
+
// price (GET https://api.fal.ai/v1/models/pricing?endpoint_id=…) for exact cost.
|
|
370
|
+
if (result.usage?.unitsBilled != null) {
|
|
371
|
+
const cost = result.usage.unitsBilled * unitPrice
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
For video, the units arrive with the completed result: `getVideoJobStatus()`
|
|
376
|
+
returns `usage` and emits a `video:usage` devtools event when fal reports it.
|
|
377
|
+
|
|
350
378
|
---
|
|
351
379
|
|
|
352
380
|
## Common Hook API
|
|
@@ -1678,8 +1678,9 @@ class TextEngine<
|
|
|
1678
1678
|
const wireContent =
|
|
1679
1679
|
typeof content === 'string' ? content : JSON.stringify(content)
|
|
1680
1680
|
|
|
1681
|
-
//
|
|
1682
|
-
//
|
|
1681
|
+
// argsMap is set only on continuation re-executions, where the adapter
|
|
1682
|
+
// never streamed these calls. Otherwise it already emitted END, so a
|
|
1683
|
+
// second one here would be an orphan that fails verifyEvents (#519).
|
|
1683
1684
|
if (argsMap) {
|
|
1684
1685
|
chunks.push({
|
|
1685
1686
|
type: 'TOOL_CALL_START',
|
|
@@ -1699,18 +1700,18 @@ class TextEngine<
|
|
|
1699
1700
|
delta: args,
|
|
1700
1701
|
args,
|
|
1701
1702
|
} as StreamChunk)
|
|
1702
|
-
}
|
|
1703
1703
|
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1704
|
+
chunks.push({
|
|
1705
|
+
type: 'TOOL_CALL_END',
|
|
1706
|
+
timestamp: Date.now(),
|
|
1707
|
+
model: finishEvent.model,
|
|
1708
|
+
toolCallId: result.toolCallId,
|
|
1709
|
+
toolCallName: result.toolName,
|
|
1710
|
+
toolName: result.toolName,
|
|
1711
|
+
result: wireContent,
|
|
1712
|
+
...(result.state !== undefined && { state: result.state }),
|
|
1713
|
+
} as StreamChunk)
|
|
1714
|
+
}
|
|
1714
1715
|
|
|
1715
1716
|
// AG-UI spec TOOL_CALL_RESULT event (content is string-only per spec)
|
|
1716
1717
|
chunks.push({
|
|
@@ -226,7 +226,7 @@ export function updateToolCallWithOutput(
|
|
|
226
226
|
parts[index] = {
|
|
227
227
|
...toolCallPart,
|
|
228
228
|
output: errorText ? { error: errorText } : output,
|
|
229
|
-
state: state ?? (errorText ? '
|
|
229
|
+
state: state ?? (errorText ? 'error' : 'complete'),
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
@@ -317,7 +317,7 @@ export class StreamProcessor {
|
|
|
317
317
|
this.messages,
|
|
318
318
|
toolCallId,
|
|
319
319
|
output,
|
|
320
|
-
error ? '
|
|
320
|
+
error ? 'error' : undefined,
|
|
321
321
|
error,
|
|
322
322
|
)
|
|
323
323
|
|
|
@@ -1184,7 +1184,7 @@ export class StreamProcessor {
|
|
|
1184
1184
|
this.messages,
|
|
1185
1185
|
chunk.toolCallId,
|
|
1186
1186
|
output,
|
|
1187
|
-
chunk.state === 'output-error' ? '
|
|
1187
|
+
chunk.state === 'output-error' ? 'error' : undefined,
|
|
1188
1188
|
)
|
|
1189
1189
|
|
|
1190
1190
|
// Step 2: Create/update the tool-result part (for LLM conversation history)
|
|
@@ -1240,7 +1240,7 @@ export class StreamProcessor {
|
|
|
1240
1240
|
this.messages,
|
|
1241
1241
|
chunk.toolCallId,
|
|
1242
1242
|
output,
|
|
1243
|
-
chunk.state === 'output-error' ? '
|
|
1243
|
+
chunk.state === 'output-error' ? 'error' : undefined,
|
|
1244
1244
|
)
|
|
1245
1245
|
|
|
1246
1246
|
// Step 2: Create/update the tool-result part
|
|
@@ -1690,11 +1690,22 @@ export class StreamProcessor {
|
|
|
1690
1690
|
_index: number,
|
|
1691
1691
|
toolCall: InternalToolCallState,
|
|
1692
1692
|
): void {
|
|
1693
|
+
// Finalize the internal bookkeeping: the call's input arguments ARE
|
|
1694
|
+
// complete regardless of whether execution later failed, so the call still
|
|
1695
|
+
// counts as a completed tool call in getCompletedToolCalls()/getState().
|
|
1693
1696
|
toolCall.state = 'input-complete'
|
|
1694
1697
|
|
|
1695
1698
|
// Try final parse
|
|
1696
1699
|
toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments)
|
|
1697
1700
|
|
|
1701
|
+
// Don't downgrade the rendered part of a call that already reached the
|
|
1702
|
+
// terminal 'error' state (e.g. an output-error TOOL_CALL_RESULT arrived
|
|
1703
|
+
// without a preceding TOOL_CALL_END). The RUN_FINISHED / finalizeStream
|
|
1704
|
+
// safety net must not clobber a failed call back to 'input-complete'.
|
|
1705
|
+
if (this.isToolCallPartErrored(toolCall.id)) {
|
|
1706
|
+
return
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1698
1709
|
// Update UIMessage
|
|
1699
1710
|
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
1700
1711
|
id: toolCall.id,
|
|
@@ -1714,6 +1725,22 @@ export class StreamProcessor {
|
|
|
1714
1725
|
)
|
|
1715
1726
|
}
|
|
1716
1727
|
|
|
1728
|
+
/**
|
|
1729
|
+
* Whether the rendered tool-call part for the given id has reached the
|
|
1730
|
+
* terminal 'error' state. Used to prevent the completion safety net from
|
|
1731
|
+
* downgrading a failed call back to 'input-complete'.
|
|
1732
|
+
*/
|
|
1733
|
+
private isToolCallPartErrored(toolCallId: string): boolean {
|
|
1734
|
+
return this.messages.some((msg) =>
|
|
1735
|
+
msg.parts.some(
|
|
1736
|
+
(part) =>
|
|
1737
|
+
part.type === 'tool-call' &&
|
|
1738
|
+
part.id === toolCallId &&
|
|
1739
|
+
part.state === 'error',
|
|
1740
|
+
),
|
|
1741
|
+
)
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1717
1744
|
/**
|
|
1718
1745
|
* Emit pending text update for a specific message.
|
|
1719
1746
|
*
|
|
@@ -15,6 +15,7 @@ import type { DebugOption } from '../../logger/types'
|
|
|
15
15
|
import type { VideoAdapter } from './adapter'
|
|
16
16
|
import type {
|
|
17
17
|
StreamChunk,
|
|
18
|
+
TokenUsage,
|
|
18
19
|
VideoJobResult,
|
|
19
20
|
VideoStatusResult,
|
|
20
21
|
VideoUrlResult,
|
|
@@ -380,6 +381,7 @@ async function* runStreamingVideoGeneration<
|
|
|
380
381
|
status: 'completed',
|
|
381
382
|
url: urlResult.url,
|
|
382
383
|
expiresAt: urlResult.expiresAt,
|
|
384
|
+
...(urlResult.usage ? { usage: urlResult.usage } : {}),
|
|
383
385
|
},
|
|
384
386
|
timestamp: Date.now(),
|
|
385
387
|
} as StreamChunk
|
|
@@ -454,6 +456,7 @@ export async function getVideoJobStatus<
|
|
|
454
456
|
progress?: number
|
|
455
457
|
url?: string
|
|
456
458
|
error?: string
|
|
459
|
+
usage?: TokenUsage
|
|
457
460
|
}> {
|
|
458
461
|
const { adapter, jobId } = options
|
|
459
462
|
const requestId = createId('video-status')
|
|
@@ -487,10 +490,19 @@ export async function getVideoJobStatus<
|
|
|
487
490
|
duration: Date.now() - startTime,
|
|
488
491
|
timestamp: Date.now(),
|
|
489
492
|
})
|
|
493
|
+
if (urlResult.usage) {
|
|
494
|
+
aiEventClient.emit('video:usage', {
|
|
495
|
+
requestId,
|
|
496
|
+
model: adapter.model,
|
|
497
|
+
usage: urlResult.usage,
|
|
498
|
+
timestamp: Date.now(),
|
|
499
|
+
})
|
|
500
|
+
}
|
|
490
501
|
return {
|
|
491
502
|
status: statusResult.status,
|
|
492
503
|
progress: statusResult.progress,
|
|
493
504
|
url: urlResult.url,
|
|
505
|
+
...(urlResult.usage ? { usage: urlResult.usage } : {}),
|
|
494
506
|
}
|
|
495
507
|
} catch (error) {
|
|
496
508
|
const errorMessage =
|