egaki 0.5.0 → 0.7.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/CHANGELOG.md +188 -0
- package/dist/cli/cache-utils.d.ts +33 -0
- package/dist/cli/cache-utils.d.ts.map +1 -0
- package/dist/cli/cache-utils.js +157 -0
- package/dist/cli/cached-generate.d.ts +105 -0
- package/dist/cli/cached-generate.d.ts.map +1 -0
- package/dist/cli/cached-generate.js +208 -0
- package/dist/cli/cached-generate.test.d.ts +11 -0
- package/dist/cli/cached-generate.test.d.ts.map +1 -0
- package/dist/cli/cached-generate.test.js +431 -0
- package/dist/cli/cartesia-provider.d.ts +16 -0
- package/dist/cli/cartesia-provider.d.ts.map +1 -0
- package/dist/cli/cartesia-provider.js +184 -0
- package/dist/cli/chatgpt-auth.d.ts.map +1 -0
- package/dist/cli/cli.d.ts +4 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/cli.js +1329 -0
- package/dist/cli/clone-generate.d.ts +26 -0
- package/dist/cli/clone-generate.d.ts.map +1 -0
- package/dist/cli/clone-generate.js +148 -0
- package/dist/{credentials.d.ts → cli/credentials.d.ts} +11 -2
- package/dist/cli/credentials.d.ts.map +1 -0
- package/dist/{credentials.js → cli/credentials.js} +88 -5
- package/dist/cli/elevenlabs-speech-provider.d.ts +26 -0
- package/dist/cli/elevenlabs-speech-provider.d.ts.map +1 -0
- package/dist/cli/elevenlabs-speech-provider.js +199 -0
- package/dist/cli/generate.d.ts +140 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +604 -0
- package/dist/cli/generate.test.d.ts +2 -0
- package/dist/cli/generate.test.d.ts.map +1 -0
- package/dist/cli/generate.test.js +131 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/{login.js → cli/login.js} +45 -4
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +7 -0
- package/dist/cli/model-catalog.d.ts +141 -0
- package/dist/cli/model-catalog.d.ts.map +1 -0
- package/dist/cli/model-catalog.js +2135 -0
- package/dist/{models.d.ts → cli/models.d.ts} +16 -9
- package/dist/cli/models.d.ts.map +1 -0
- package/dist/cli/models.js +313 -0
- package/dist/cli/open-browser.d.ts.map +1 -0
- package/dist/cli/openai-speech-provider.d.ts +3 -0
- package/dist/cli/openai-speech-provider.d.ts.map +1 -0
- package/dist/cli/openai-speech-provider.js +28 -0
- package/dist/cli/separation-generate.d.ts +34 -0
- package/dist/cli/separation-generate.d.ts.map +1 -0
- package/dist/cli/separation-generate.js +117 -0
- package/dist/cli/speech-catalog.d.ts +35 -0
- package/dist/cli/speech-catalog.d.ts.map +1 -0
- package/dist/cli/speech-catalog.js +195 -0
- package/dist/cli/speech-generate.d.ts +86 -0
- package/dist/cli/speech-generate.d.ts.map +1 -0
- package/dist/cli/speech-generate.js +156 -0
- package/dist/cli/speech-models.d.ts +8 -0
- package/dist/cli/speech-models.d.ts.map +1 -0
- package/dist/cli/speech-models.js +109 -0
- package/dist/cli/speech-provider.test.d.ts +2 -0
- package/dist/cli/speech-provider.test.d.ts.map +1 -0
- package/dist/cli/speech-provider.test.js +197 -0
- package/dist/cli/subscription.d.ts.map +1 -0
- package/dist/cli/transcription-catalog.d.ts +35 -0
- package/dist/cli/transcription-catalog.d.ts.map +1 -0
- package/dist/cli/transcription-catalog.js +192 -0
- package/dist/cli/transcription-generate.d.ts +62 -0
- package/dist/cli/transcription-generate.d.ts.map +1 -0
- package/dist/cli/transcription-generate.js +144 -0
- package/dist/cli/transcription-models.d.ts +9 -0
- package/dist/cli/transcription-models.d.ts.map +1 -0
- package/dist/cli/transcription-models.js +100 -0
- package/dist/cli/transcription-providers.d.ts +22 -0
- package/dist/cli/transcription-providers.d.ts.map +1 -0
- package/dist/cli/transcription-providers.js +290 -0
- package/dist/cli/video-model-catalog.d.ts +2 -0
- package/dist/cli/video-model-catalog.d.ts.map +1 -0
- package/dist/cli/video-model-catalog.js +5 -0
- package/dist/cli/xai-auth.d.ts +29 -0
- package/dist/cli/xai-auth.d.ts.map +1 -0
- package/dist/cli/xai-auth.js +325 -0
- package/dist/vite/angled-screen.d.ts +57 -0
- package/dist/vite/angled-screen.d.ts.map +1 -0
- package/dist/vite/angled-screen.js +115 -0
- package/dist/vite/app.d.ts +51 -0
- package/dist/vite/app.d.ts.map +1 -0
- package/dist/vite/app.js +150 -0
- package/dist/vite/bands-shader.d.ts +47 -0
- package/dist/vite/bands-shader.d.ts.map +1 -0
- package/dist/vite/bands-shader.js +267 -0
- package/dist/vite/code-block.d.ts +107 -0
- package/dist/vite/code-block.d.ts.map +1 -0
- package/dist/vite/code-block.js +641 -0
- package/dist/vite/components.d.ts +36 -0
- package/dist/vite/components.d.ts.map +1 -0
- package/dist/vite/components.js +113 -0
- package/dist/vite/dispersion-rings-shader.d.ts +44 -0
- package/dist/vite/dispersion-rings-shader.d.ts.map +1 -0
- package/dist/vite/dispersion-rings-shader.js +282 -0
- package/dist/vite/easing-curves.d.ts +129 -0
- package/dist/vite/easing-curves.d.ts.map +1 -0
- package/dist/vite/easing-curves.js +584 -0
- package/dist/vite/easing-curves.test.d.ts +2 -0
- package/dist/vite/easing-curves.test.d.ts.map +1 -0
- package/dist/vite/easing-curves.test.js +200 -0
- package/dist/vite/generated-media-client.d.ts +35 -0
- package/dist/vite/generated-media-client.d.ts.map +1 -0
- package/dist/vite/generated-media-client.js +76 -0
- package/dist/vite/keyframes.d.ts +129 -0
- package/dist/vite/keyframes.d.ts.map +1 -0
- package/dist/vite/keyframes.js +194 -0
- package/dist/vite/layout-editor.d.ts +45 -0
- package/dist/vite/layout-editor.d.ts.map +1 -0
- package/dist/vite/layout-editor.js +587 -0
- package/dist/vite/layout-transition.d.ts +136 -0
- package/dist/vite/layout-transition.d.ts.map +1 -0
- package/dist/vite/layout-transition.js +581 -0
- package/dist/vite/liquid-gradient-shader.d.ts +62 -0
- package/dist/vite/liquid-gradient-shader.d.ts.map +1 -0
- package/dist/vite/liquid-gradient-shader.js +413 -0
- package/dist/vite/mdx-client.d.ts +15 -0
- package/dist/vite/mdx-client.d.ts.map +1 -0
- package/dist/vite/mdx-client.js +265 -0
- package/dist/vite/mdx-parse.d.ts +102 -0
- package/dist/vite/mdx-parse.d.ts.map +1 -0
- package/dist/vite/mdx-parse.js +251 -0
- package/dist/vite/mdx-provided-components.d.ts +18 -0
- package/dist/vite/mdx-provided-components.d.ts.map +1 -0
- package/dist/vite/mdx-provided-components.js +14 -0
- package/dist/vite/mdx-video.d.ts +327 -0
- package/dist/vite/mdx-video.d.ts.map +1 -0
- package/dist/vite/mdx-video.js +596 -0
- package/dist/vite/mdx-video.test.d.ts +9 -0
- package/dist/vite/mdx-video.test.d.ts.map +1 -0
- package/dist/vite/mdx-video.test.js +1945 -0
- package/dist/vite/media-components.d.ts +45 -0
- package/dist/vite/media-components.d.ts.map +1 -0
- package/dist/vite/media-components.js +355 -0
- package/dist/vite/media-duration-store.d.ts +66 -0
- package/dist/vite/media-duration-store.d.ts.map +1 -0
- package/dist/vite/media-duration-store.js +204 -0
- package/dist/vite/player-page.d.ts +35 -0
- package/dist/vite/player-page.d.ts.map +1 -0
- package/dist/vite/player-page.js +720 -0
- package/dist/vite/render-client.d.ts +18 -0
- package/dist/vite/render-client.d.ts.map +1 -0
- package/dist/vite/render-client.js +67 -0
- package/dist/vite/sdk.d.ts +221 -0
- package/dist/vite/sdk.d.ts.map +1 -0
- package/dist/vite/sdk.js +355 -0
- package/dist/vite/server-components.d.ts +55 -0
- package/dist/vite/server-components.d.ts.map +1 -0
- package/dist/vite/server-components.js +156 -0
- package/dist/vite/server-mdx.d.ts +74 -0
- package/dist/vite/server-mdx.d.ts.map +1 -0
- package/dist/vite/server-mdx.js +259 -0
- package/dist/vite/shader-renderer.d.ts +48 -0
- package/dist/vite/shader-renderer.d.ts.map +1 -0
- package/dist/vite/shader-renderer.js +411 -0
- package/dist/vite/store-hooks.d.ts +12 -0
- package/dist/vite/store-hooks.d.ts.map +1 -0
- package/dist/vite/store-hooks.js +93 -0
- package/dist/vite/store.d.ts +75 -0
- package/dist/vite/store.d.ts.map +1 -0
- package/dist/vite/store.js +52 -0
- package/dist/vite/store.test.d.ts +6 -0
- package/dist/vite/store.test.d.ts.map +1 -0
- package/dist/vite/store.test.js +172 -0
- package/dist/vite/tweakpane-hook.d.ts +94 -0
- package/dist/vite/tweakpane-hook.d.ts.map +1 -0
- package/dist/vite/tweakpane-hook.js +450 -0
- package/dist/vite/vite-plugin.d.ts +28 -0
- package/dist/vite/vite-plugin.d.ts.map +1 -0
- package/dist/vite/vite-plugin.js +538 -0
- package/dist/vite/wave-gradient-shader.d.ts +41 -0
- package/dist/vite/wave-gradient-shader.d.ts.map +1 -0
- package/dist/vite/wave-gradient-shader.js +207 -0
- package/package.json +103 -25
- package/skills/egaki/SKILL.md +162 -1
- package/src/cli/cache-utils.ts +169 -0
- package/src/cli/cached-generate.test.ts +531 -0
- package/src/cli/cached-generate.ts +348 -0
- package/src/cli/cartesia-provider.ts +249 -0
- package/src/cli/cli.ts +1818 -0
- package/src/cli/clone-generate.ts +202 -0
- package/src/{credentials.ts → cli/credentials.ts} +90 -7
- package/src/cli/elevenlabs-speech-provider.ts +243 -0
- package/src/cli/generate.test.ts +166 -0
- package/src/cli/generate.ts +912 -0
- package/src/{login.ts → cli/login.ts} +48 -3
- package/src/cli/main.ts +8 -0
- package/src/cli/model-catalog.ts +2336 -0
- package/src/cli/models.ts +408 -0
- package/src/cli/openai-speech-provider.ts +34 -0
- package/src/cli/separation-generate.ts +187 -0
- package/src/cli/speech-catalog.ts +253 -0
- package/src/cli/speech-generate.ts +275 -0
- package/src/cli/speech-models.ts +146 -0
- package/src/cli/speech-provider.test.ts +226 -0
- package/src/cli/transcription-catalog.ts +250 -0
- package/src/cli/transcription-generate.ts +214 -0
- package/src/cli/transcription-models.ts +136 -0
- package/src/cli/transcription-providers.ts +427 -0
- package/src/cli/video-model-catalog.ts +14 -0
- package/src/cli/xai-auth.ts +396 -0
- package/src/vite/angled-screen.tsx +214 -0
- package/src/vite/app.tsx +197 -0
- package/src/vite/bands-shader.tsx +319 -0
- package/src/vite/code-block.tsx +939 -0
- package/src/vite/components.tsx +278 -0
- package/src/vite/dispersion-rings-shader.tsx +331 -0
- package/src/vite/easing-curves.test.ts +232 -0
- package/src/vite/easing-curves.ts +743 -0
- package/src/vite/generated-media-client.tsx +121 -0
- package/src/vite/keyframes.tsx +347 -0
- package/src/vite/layout-editor.tsx +709 -0
- package/src/vite/layout-transition.tsx +706 -0
- package/src/vite/liquid-gradient-shader.tsx +474 -0
- package/src/vite/mdx-client.tsx +420 -0
- package/src/vite/mdx-parse.ts +343 -0
- package/src/vite/mdx-provided-components.ts +19 -0
- package/src/vite/mdx-video.test.tsx +2219 -0
- package/src/vite/mdx-video.tsx +976 -0
- package/src/vite/media-components.tsx +473 -0
- package/src/vite/media-duration-store.ts +225 -0
- package/src/vite/motion-timing.ts +194 -0
- package/src/vite/player-page.tsx +1107 -0
- package/src/vite/render-client.ts +84 -0
- package/src/vite/sdk.ts +598 -0
- package/src/vite/server-components.tsx +241 -0
- package/src/vite/server-mdx.ts +285 -0
- package/src/vite/shader-renderer.tsx +535 -0
- package/src/vite/store-hooks.ts +117 -0
- package/src/vite/store.test.ts +202 -0
- package/src/vite/store.ts +115 -0
- package/src/vite/styles.css +21 -0
- package/src/vite/tweakpane-hook.tsx +593 -0
- package/src/vite/virtual-modules.d.ts +30 -0
- package/src/vite/vite-plugin.ts +584 -0
- package/src/vite/wave-gradient-shader.tsx +253 -0
- package/README.md +0 -266
- package/dist/antigravity-auth.d.ts +0 -15
- package/dist/antigravity-auth.d.ts.map +0 -1
- package/dist/antigravity-auth.js +0 -544
- package/dist/antigravity-image.d.ts +0 -11
- package/dist/antigravity-image.d.ts.map +0 -1
- package/dist/antigravity-image.js +0 -285
- package/dist/chatgpt-auth.d.ts.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -968
- package/dist/credentials.d.ts.map +0 -1
- package/dist/gateway-model-candidates.generated.d.ts +0 -11
- package/dist/gateway-model-candidates.generated.d.ts.map +0 -1
- package/dist/gateway-model-candidates.generated.js +0 -64
- package/dist/login.d.ts.map +0 -1
- package/dist/model-catalog.d.ts +0 -42
- package/dist/model-catalog.d.ts.map +0 -1
- package/dist/model-catalog.js +0 -1001
- package/dist/models.d.ts.map +0 -1
- package/dist/models.js +0 -246
- package/dist/open-browser.d.ts.map +0 -1
- package/dist/subscription.d.ts.map +0 -1
- package/dist/video-model-catalog.d.ts +0 -52
- package/dist/video-model-catalog.d.ts.map +0 -1
- package/dist/video-model-catalog.js +0 -306
- package/src/cli.ts +0 -1378
- package/src/model-catalog.ts +0 -1077
- package/src/models.ts +0 -309
- package/src/video-model-catalog.ts +0 -363
- /package/dist/{chatgpt-auth.d.ts → cli/chatgpt-auth.d.ts} +0 -0
- /package/dist/{chatgpt-auth.js → cli/chatgpt-auth.js} +0 -0
- /package/dist/{login.d.ts → cli/login.d.ts} +0 -0
- /package/dist/{open-browser.d.ts → cli/open-browser.d.ts} +0 -0
- /package/dist/{open-browser.js → cli/open-browser.js} +0 -0
- /package/dist/{subscription.d.ts → cli/subscription.d.ts} +0 -0
- /package/dist/{subscription.js → cli/subscription.js} +0 -0
- /package/src/{chatgpt-auth.ts → cli/chatgpt-auth.ts} +0 -0
- /package/src/{open-browser.ts → cli/open-browser.ts} +0 -0
- /package/src/{subscription.ts → cli/subscription.ts} +0 -0
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
// Programmatic API for egaki image and video generation.
|
|
2
|
+
// Returns Error | Result (errore style) instead of process.exit/console.error.
|
|
3
|
+
//
|
|
4
|
+
// Two variants for each function:
|
|
5
|
+
// - `generateImage(opts)` — cached, writes to public/generated/, returns { src: string }
|
|
6
|
+
// - `generateImageUncached(opts)` — raw, returns bytes in GenerateImageResult
|
|
7
|
+
//
|
|
8
|
+
// The CLI uses *Uncached variants. The cached variants are for the Vite MDX
|
|
9
|
+
// video framework and .server.tsx components.
|
|
10
|
+
//
|
|
11
|
+
// Usage:
|
|
12
|
+
// import { generateImage, generateImageUncached } from 'egaki/generate'
|
|
13
|
+
// const cached = await generateImage({ prompt: 'a sunset' })
|
|
14
|
+
// if (!(cached instanceof Error)) cached.src // '/generated/image/sunset-a1b2c3d4.png'
|
|
15
|
+
// const raw = await generateImageUncached({ prompt: 'a sunset' })
|
|
16
|
+
// if (!(raw instanceof Error)) raw.images[0].uint8Array // raw bytes
|
|
17
|
+
import {
|
|
18
|
+
generateImage as aiGenerateImage,
|
|
19
|
+
generateText,
|
|
20
|
+
experimental_generateVideo as aiGenerateVideo,
|
|
21
|
+
} from 'ai'
|
|
22
|
+
import type { XaiImageModelOptions, XaiVideoModelOptions } from '@ai-sdk/xai'
|
|
23
|
+
import type { GoogleImageModelOptions, GoogleLanguageModelOptions, GoogleVideoModelOptions } from '@ai-sdk/google'
|
|
24
|
+
import type { GoogleVertexImageModelOptions, GoogleVertexVideoModelOptions } from '@ai-sdk/google-vertex'
|
|
25
|
+
import type { FalImageModelOptions, FalVideoModelOptions } from '@ai-sdk/fal'
|
|
26
|
+
import type { ByteDanceVideoProviderOptions } from '@ai-sdk/bytedance'
|
|
27
|
+
import { createParser, type EventSourceMessage } from 'eventsource-parser'
|
|
28
|
+
import {
|
|
29
|
+
injectCredentialsToEnv,
|
|
30
|
+
getChatGptAuth,
|
|
31
|
+
saveChatGptAuth,
|
|
32
|
+
} from './credentials.js'
|
|
33
|
+
import {
|
|
34
|
+
getModelConfig,
|
|
35
|
+
createImageModel,
|
|
36
|
+
createTextModel,
|
|
37
|
+
createVideoModel,
|
|
38
|
+
shouldUseResponsesApi,
|
|
39
|
+
DEFAULT_MODEL,
|
|
40
|
+
DEFAULT_VIDEO_MODEL,
|
|
41
|
+
ValidationError,
|
|
42
|
+
} from './models.js'
|
|
43
|
+
|
|
44
|
+
export { ValidationError }
|
|
45
|
+
import { getValidChatGptAuth } from './chatgpt-auth.js'
|
|
46
|
+
|
|
47
|
+
// ─── autocomplete-friendly union types ───────────────────────────────────────
|
|
48
|
+
// `(string & {})` lets users pass arbitrary strings (new models, custom
|
|
49
|
+
// endpoints) while still providing autocomplete for known values.
|
|
50
|
+
|
|
51
|
+
/** Image model IDs from the catalog. Accepts arbitrary strings too. */
|
|
52
|
+
export type ImageModelId =
|
|
53
|
+
| 'imagen-4.0-generate-001' | 'imagen-4.0-ultra-generate-001' | 'imagen-4.0-fast-generate-001'
|
|
54
|
+
| 'gemini-2.0-flash-exp-image-generation' | 'gemini-2.5-flash-image' | 'gemini-3-pro-image-preview' | 'gemini-3.1-flash-image-preview'
|
|
55
|
+
| 'vertex/imagen-4.0-generate-001' | 'vertex/imagen-4.0-ultra-generate-001' | 'vertex/imagen-4.0-fast-generate-001'
|
|
56
|
+
| 'vertex/gemini-2.5-flash-image' | 'vertex/gemini-3-pro-image-preview' | 'vertex/gemini-3.1-flash-image-preview'
|
|
57
|
+
| 'flux-kontext-max' | 'flux-kontext-pro' | 'flux-pro-1.0-fill' | 'flux-pro-1.1' | 'flux-pro-1.1-ultra'
|
|
58
|
+
| 'flux-2-pro' | 'flux-2-max' | 'flux-2-flex' | 'flux-2-klein-9b' | 'flux-2-klein-4b'
|
|
59
|
+
| 'recraft-v2' | 'recraft-v3' | 'recraft-v4' | 'recraft-v4-pro' | 'recraft-v4.1' | 'recraft-v4.1-pro' | 'recraft-v4.1-utility' | 'recraft-v4.1-utility-pro'
|
|
60
|
+
| 'seedream-5.0-lite' | 'seedream-4.5' | 'seedream-4.0'
|
|
61
|
+
| 'grok-imagine-image' | 'grok-imagine-image-quality'
|
|
62
|
+
| 'dall-e-2' | 'dall-e-3' | 'gpt-image-1' | 'gpt-image-1-mini' | 'gpt-image-1.5' | 'gpt-image-2' | 'chatgpt-image-latest'
|
|
63
|
+
| (string & {})
|
|
64
|
+
|
|
65
|
+
/** Video model IDs from the catalog. Accepts arbitrary strings too. */
|
|
66
|
+
export type VideoModelId =
|
|
67
|
+
| 'veo-3.1-generate-001' | 'veo-3.1-fast-generate-001' | 'veo-3.0-generate-001' | 'veo-3.0-fast-generate-001'
|
|
68
|
+
| 'vertex/veo-3.1-generate-001' | 'vertex/veo-3.1-fast-generate-001'
|
|
69
|
+
| 'grok-imagine-video' | 'grok-imagine-video-1.5'
|
|
70
|
+
| 'kling-v3.0-t2v' | 'kling-v3.0-i2v' | 'kling-v3.0-motion-control' | 'kling-v2.6-t2v' | 'kling-v2.6-i2v' | 'kling-v2.6-motion-control'
|
|
71
|
+
| 'kling-v2.5-turbo-t2v' | 'kling-v2.5-turbo-i2v'
|
|
72
|
+
| 'wan-v2.6-t2v' | 'wan-v2.6-i2v' | 'wan-v2.6-i2v-flash' | 'wan-v2.6-r2v' | 'wan-v2.6-r2v-flash' | 'wan-v2.5-t2v-preview'
|
|
73
|
+
| 'seedance-2.0' | 'seedance-2.0-fast' | 'seedance-v1.5-pro' | 'seedance-v1.0-pro' | 'seedance-v1.0-pro-fast' | 'seedance-v1.0-lite-t2v' | 'seedance-v1.0-lite-i2v'
|
|
74
|
+
| 'luma-ray-2' | 'minimax-video' | 'hunyuan-video'
|
|
75
|
+
| (string & {})
|
|
76
|
+
|
|
77
|
+
/** Common aspect ratios across providers. */
|
|
78
|
+
export type AspectRatio =
|
|
79
|
+
| '1:1' | '3:4' | '4:3' | '9:16' | '16:9' | '2:3' | '3:2' | '4:5' | '5:4' | '9:21' | '21:9'
|
|
80
|
+
| (string & {})
|
|
81
|
+
|
|
82
|
+
/** Image quality presets (varies by provider). */
|
|
83
|
+
export type ImageQuality = 'auto' | 'low' | 'medium' | 'high' | 'standard' | (string & {})
|
|
84
|
+
|
|
85
|
+
/** Image output formats. */
|
|
86
|
+
export type ImageOutputFormat = 'png' | 'jpeg' | 'webp' | (string & {})
|
|
87
|
+
|
|
88
|
+
// ─── public types ────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
export type GeneratedFile = {
|
|
91
|
+
uint8Array: Uint8Array
|
|
92
|
+
mediaType: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface GenerateImageOptions {
|
|
96
|
+
prompt: string
|
|
97
|
+
/** Model ID. Defaults to DEFAULT_MODEL if omitted. */
|
|
98
|
+
model?: ImageModelId
|
|
99
|
+
count?: number
|
|
100
|
+
aspectRatio?: AspectRatio
|
|
101
|
+
seed?: number
|
|
102
|
+
inputImages?: Uint8Array[]
|
|
103
|
+
maskImage?: Uint8Array
|
|
104
|
+
allowPeople?: boolean
|
|
105
|
+
quality?: ImageQuality
|
|
106
|
+
resolution?: string
|
|
107
|
+
outputFormat?: ImageOutputFormat
|
|
108
|
+
negativePrompt?: string
|
|
109
|
+
/** Only for text-model image generation (Gemini). */
|
|
110
|
+
imageSize?: '1K' | '2K' | '4K'
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface GenerateImageResult {
|
|
114
|
+
images: GeneratedFile[]
|
|
115
|
+
model: string
|
|
116
|
+
cost: number | null
|
|
117
|
+
usage?: { inputTokens?: number; outputTokens?: number }
|
|
118
|
+
warnings?: unknown[]
|
|
119
|
+
/** Text response from text-model path (Gemini). */
|
|
120
|
+
text?: string
|
|
121
|
+
/** Revised prompt from ChatGPT Responses API path. */
|
|
122
|
+
revisedPrompt?: string | null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface GenerateVideoOptions {
|
|
126
|
+
prompt: string
|
|
127
|
+
/** Model ID. Defaults to DEFAULT_VIDEO_MODEL if omitted. */
|
|
128
|
+
model?: VideoModelId
|
|
129
|
+
count?: number
|
|
130
|
+
aspectRatio?: AspectRatio
|
|
131
|
+
resolution?: string
|
|
132
|
+
duration?: number
|
|
133
|
+
fps?: number
|
|
134
|
+
seed?: number
|
|
135
|
+
inputImage?: Uint8Array
|
|
136
|
+
mode?: 'edit-video' | 'extend-video' | 'reference-to-video'
|
|
137
|
+
/** URL for video editing/extension input. Must be a URL (not a local path). */
|
|
138
|
+
videoUrl?: string
|
|
139
|
+
/** Reference image URLs for r2v generation. Must be URLs (not local paths). */
|
|
140
|
+
referenceImages?: string[]
|
|
141
|
+
negativePrompt?: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface GenerateVideoResult {
|
|
145
|
+
videos: GeneratedFile[]
|
|
146
|
+
model: string
|
|
147
|
+
cost: number | null
|
|
148
|
+
warnings?: unknown[]
|
|
149
|
+
/** Raw responses from the AI SDK (provider-specific). */
|
|
150
|
+
responses?: unknown[]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── OpenAI provider options type (not exported by @ai-sdk/openai) ───────────
|
|
154
|
+
|
|
155
|
+
type OpenAIImageProviderOptions = {
|
|
156
|
+
quality?: 'standard' | 'low' | 'medium' | 'high' | 'auto'
|
|
157
|
+
outputFormat?: 'png' | 'jpeg' | 'webp'
|
|
158
|
+
outputCompression?: number
|
|
159
|
+
size?: `${number}x${number}`
|
|
160
|
+
partialImages?: number | null
|
|
161
|
+
background?: 'auto' | 'opaque' | 'transparent'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ─── type-safe provider option builders ──────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
type ProviderOptionsResult = Record<string, Record<string, string | number | boolean | string[] | null | undefined | Record<string, string | number | boolean | null | undefined>>>
|
|
167
|
+
|
|
168
|
+
export function buildImageProviderOptions(
|
|
169
|
+
provider: string,
|
|
170
|
+
opts: {
|
|
171
|
+
aspectRatio?: `${number}:${number}`
|
|
172
|
+
allowPeople: boolean
|
|
173
|
+
quality?: string
|
|
174
|
+
resolution?: string
|
|
175
|
+
outputFormat?: string
|
|
176
|
+
negativePrompt?: string
|
|
177
|
+
},
|
|
178
|
+
): ProviderOptionsResult {
|
|
179
|
+
switch (provider) {
|
|
180
|
+
case 'google': {
|
|
181
|
+
const googleOpts = {
|
|
182
|
+
...(opts.allowPeople ? { personGeneration: 'allow_all' as const } : {}),
|
|
183
|
+
...(opts.aspectRatio ? { aspectRatio: opts.aspectRatio as GoogleImageModelOptions['aspectRatio'] } : {}),
|
|
184
|
+
} satisfies GoogleImageModelOptions
|
|
185
|
+
return { google: googleOpts }
|
|
186
|
+
}
|
|
187
|
+
case 'vertex': {
|
|
188
|
+
const vertexOpts = {
|
|
189
|
+
...(opts.allowPeople ? { personGeneration: 'allow_all' as const } : {}),
|
|
190
|
+
...(opts.negativePrompt ? { negativePrompt: opts.negativePrompt } : {}),
|
|
191
|
+
} satisfies GoogleVertexImageModelOptions
|
|
192
|
+
return { vertex: vertexOpts }
|
|
193
|
+
}
|
|
194
|
+
case 'xai': {
|
|
195
|
+
const xaiOpts = {
|
|
196
|
+
...(opts.quality ? { quality: opts.quality as XaiImageModelOptions['quality'] } : {}),
|
|
197
|
+
...(opts.resolution ? { resolution: opts.resolution as XaiImageModelOptions['resolution'] } : {}),
|
|
198
|
+
...(opts.outputFormat ? { output_format: opts.outputFormat } : {}),
|
|
199
|
+
} satisfies XaiImageModelOptions
|
|
200
|
+
return { xai: xaiOpts }
|
|
201
|
+
}
|
|
202
|
+
case 'openai': {
|
|
203
|
+
const openaiOpts = {
|
|
204
|
+
...(opts.quality ? { quality: opts.quality as OpenAIImageProviderOptions['quality'] } : {}),
|
|
205
|
+
...(opts.outputFormat ? { outputFormat: opts.outputFormat as OpenAIImageProviderOptions['outputFormat'] } : {}),
|
|
206
|
+
} satisfies OpenAIImageProviderOptions
|
|
207
|
+
return { openai: openaiOpts }
|
|
208
|
+
}
|
|
209
|
+
case 'fal': {
|
|
210
|
+
const falOpts = {
|
|
211
|
+
...(opts.outputFormat ? { outputFormat: opts.outputFormat } : {}),
|
|
212
|
+
...(opts.negativePrompt ? { negativePrompt: opts.negativePrompt } : {}),
|
|
213
|
+
} satisfies FalImageModelOptions
|
|
214
|
+
return { fal: falOpts }
|
|
215
|
+
}
|
|
216
|
+
default:
|
|
217
|
+
return {}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function buildVideoProviderOptions(
|
|
222
|
+
provider: string,
|
|
223
|
+
opts: {
|
|
224
|
+
resolution?: string
|
|
225
|
+
mode?: string
|
|
226
|
+
videoUrl?: string
|
|
227
|
+
referenceImages?: string[]
|
|
228
|
+
negativePrompt?: string
|
|
229
|
+
model: string
|
|
230
|
+
},
|
|
231
|
+
): ProviderOptionsResult | undefined {
|
|
232
|
+
switch (provider) {
|
|
233
|
+
case 'xai': {
|
|
234
|
+
const base = {
|
|
235
|
+
...(opts.resolution ? { resolution: opts.resolution as '480p' | '720p' } : {}),
|
|
236
|
+
}
|
|
237
|
+
if (opts.mode === 'edit-video' && opts.videoUrl) {
|
|
238
|
+
const xaiOpts = { ...base, mode: 'edit-video' as const, videoUrl: opts.videoUrl } satisfies XaiVideoModelOptions
|
|
239
|
+
return { xai: xaiOpts }
|
|
240
|
+
}
|
|
241
|
+
if (opts.mode === 'extend-video' && opts.videoUrl) {
|
|
242
|
+
const xaiOpts = { ...base, mode: 'extend-video' as const, videoUrl: opts.videoUrl } satisfies XaiVideoModelOptions
|
|
243
|
+
return { xai: xaiOpts }
|
|
244
|
+
}
|
|
245
|
+
if (opts.mode === 'reference-to-video' && opts.referenceImages) {
|
|
246
|
+
const xaiOpts = { ...base, mode: 'reference-to-video' as const, referenceImageUrls: opts.referenceImages } satisfies XaiVideoModelOptions
|
|
247
|
+
return { xai: xaiOpts }
|
|
248
|
+
}
|
|
249
|
+
if (Object.keys(base).length === 0) return undefined
|
|
250
|
+
const xaiOpts = { ...base } satisfies XaiVideoModelOptions
|
|
251
|
+
return { xai: xaiOpts }
|
|
252
|
+
}
|
|
253
|
+
case 'bytedance': {
|
|
254
|
+
const bytedanceOpts = {
|
|
255
|
+
...(opts.resolution ? { resolution: opts.resolution } : {}),
|
|
256
|
+
...(opts.referenceImages ? { referenceImages: opts.referenceImages } : {}),
|
|
257
|
+
} satisfies ByteDanceVideoProviderOptions
|
|
258
|
+
if (Object.keys(bytedanceOpts).length === 0) return undefined
|
|
259
|
+
return { bytedance: bytedanceOpts }
|
|
260
|
+
}
|
|
261
|
+
case 'fal': {
|
|
262
|
+
const falOpts = {
|
|
263
|
+
...(opts.resolution ? { resolution: opts.resolution } : {}),
|
|
264
|
+
...(opts.negativePrompt ? { negativePrompt: opts.negativePrompt } : {}),
|
|
265
|
+
} satisfies FalVideoModelOptions
|
|
266
|
+
if (Object.keys(falOpts).length === 0) return undefined
|
|
267
|
+
return { fal: falOpts }
|
|
268
|
+
}
|
|
269
|
+
case 'google': {
|
|
270
|
+
const googleOpts = {
|
|
271
|
+
...(opts.negativePrompt ? { negativePrompt: opts.negativePrompt } : {}),
|
|
272
|
+
} satisfies GoogleVideoModelOptions
|
|
273
|
+
if (Object.keys(googleOpts).length === 0) return undefined
|
|
274
|
+
return { google: googleOpts }
|
|
275
|
+
}
|
|
276
|
+
case 'vertex': {
|
|
277
|
+
const vertexOpts = {
|
|
278
|
+
...(opts.negativePrompt ? { negativePrompt: opts.negativePrompt } : {}),
|
|
279
|
+
} satisfies GoogleVertexVideoModelOptions
|
|
280
|
+
if (Object.keys(vertexOpts).length === 0) return undefined
|
|
281
|
+
return { vertex: vertexOpts }
|
|
282
|
+
}
|
|
283
|
+
default:
|
|
284
|
+
return undefined
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ─── cost calculation ────────────────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
export function calculateCost(
|
|
291
|
+
cost: {
|
|
292
|
+
type: 'per-image'
|
|
293
|
+
perImage: number
|
|
294
|
+
} | {
|
|
295
|
+
type: 'per-token'
|
|
296
|
+
inputPerM: number
|
|
297
|
+
outputPerM: number
|
|
298
|
+
} | {
|
|
299
|
+
type: 'per-video-second'
|
|
300
|
+
defaultDurationSec: number
|
|
301
|
+
tiers: Array<{ resolution?: string; costPerSecond: number; hasVideoInput?: boolean }>
|
|
302
|
+
} | {
|
|
303
|
+
type: 'unknown'
|
|
304
|
+
},
|
|
305
|
+
usage: {
|
|
306
|
+
inputTokens?: number
|
|
307
|
+
outputTokens?: number
|
|
308
|
+
imagesGenerated?: number
|
|
309
|
+
videosGenerated?: number
|
|
310
|
+
durationSeconds?: number
|
|
311
|
+
resolution?: string
|
|
312
|
+
hasVideoInput?: boolean
|
|
313
|
+
},
|
|
314
|
+
count: number = 1,
|
|
315
|
+
): number | null {
|
|
316
|
+
if (cost.type === 'per-image') {
|
|
317
|
+
return cost.perImage * count
|
|
318
|
+
}
|
|
319
|
+
if (cost.type === 'per-token' && usage.inputTokens != null && usage.outputTokens != null) {
|
|
320
|
+
return (
|
|
321
|
+
(usage.inputTokens * cost.inputPerM + usage.outputTokens * cost.outputPerM) / 1_000_000
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
if (cost.type === 'per-video-second') {
|
|
325
|
+
const durationSec = usage.durationSeconds ?? cost.defaultDurationSec
|
|
326
|
+
const resolution = normalizeResolutionKey(usage.resolution)
|
|
327
|
+
const hasVideoInput = usage.hasVideoInput ?? false
|
|
328
|
+
const tier =
|
|
329
|
+
cost.tiers.find((t) =>
|
|
330
|
+
normalizeResolutionKey(t.resolution) === resolution &&
|
|
331
|
+
(t.hasVideoInput == null || t.hasVideoInput === hasVideoInput),
|
|
332
|
+
) ??
|
|
333
|
+
cost.tiers.find((t) => normalizeResolutionKey(t.resolution) === resolution) ??
|
|
334
|
+
cost.tiers[0]
|
|
335
|
+
if (!tier) {
|
|
336
|
+
return null
|
|
337
|
+
}
|
|
338
|
+
return tier.costPerSecond * durationSec * count
|
|
339
|
+
}
|
|
340
|
+
return null
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function normalizeResolutionKey(input?: string): string | undefined {
|
|
344
|
+
if (!input) return undefined
|
|
345
|
+
const normalized = input.trim().toLowerCase()
|
|
346
|
+
if (normalized === '1920x1080') return '1080p'
|
|
347
|
+
if (normalized === '1280x720') return '720p'
|
|
348
|
+
if (normalized === '854x480' || normalized === '848x480') return '480p'
|
|
349
|
+
return normalized
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ─── image byte helpers ──────────────────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
function inferImageMediaType(image: Uint8Array): string {
|
|
355
|
+
if (image.length >= 8 && image[0] === 0x89 && image[1] === 0x50 && image[2] === 0x4e && image[3] === 0x47) {
|
|
356
|
+
return 'image/png'
|
|
357
|
+
}
|
|
358
|
+
if (image.length >= 3 && image[0] === 0xff && image[1] === 0xd8 && image[2] === 0xff) {
|
|
359
|
+
return 'image/jpeg'
|
|
360
|
+
}
|
|
361
|
+
if (
|
|
362
|
+
image.length >= 12 &&
|
|
363
|
+
image[0] === 0x52 && image[1] === 0x49 && image[2] === 0x46 && image[3] === 0x46 &&
|
|
364
|
+
image[8] === 0x57 && image[9] === 0x45 && image[10] === 0x42 && image[11] === 0x50
|
|
365
|
+
) {
|
|
366
|
+
return 'image/webp'
|
|
367
|
+
}
|
|
368
|
+
if (
|
|
369
|
+
image.length >= 6 &&
|
|
370
|
+
image[0] === 0x47 && image[1] === 0x49 && image[2] === 0x46 && image[3] === 0x38
|
|
371
|
+
) {
|
|
372
|
+
return 'image/gif'
|
|
373
|
+
}
|
|
374
|
+
return 'image/png'
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function imageBytesToDataUrl(image: Uint8Array): string {
|
|
378
|
+
const mediaType = inferImageMediaType(image)
|
|
379
|
+
return `data:${mediaType};base64,${Buffer.from(image).toString('base64')}`
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function chatGptImageSizeFromAspectRatio(
|
|
383
|
+
aspectRatio: `${number}:${number}`,
|
|
384
|
+
): '1024x1024' | '1536x1024' | '1024x1536' | undefined {
|
|
385
|
+
switch (aspectRatio) {
|
|
386
|
+
case '1:1':
|
|
387
|
+
return '1024x1024'
|
|
388
|
+
case '3:2':
|
|
389
|
+
return '1536x1024'
|
|
390
|
+
case '2:3':
|
|
391
|
+
return '1024x1536'
|
|
392
|
+
default:
|
|
393
|
+
return undefined
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function isAspectRatio(input: string): input is `${number}:${number}` {
|
|
398
|
+
return /^\d+:\d+$/.test(input)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Convert any thrown value into an Error instance. */
|
|
402
|
+
function toError(err: unknown): Error {
|
|
403
|
+
if (err instanceof Error) return err
|
|
404
|
+
return new Error(String(err))
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ─── generateImage ───────────────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Generate images from a text prompt. Auto-detects which generation strategy
|
|
411
|
+
* to use (image API, text model, or ChatGPT Responses API) based on model ID.
|
|
412
|
+
* Shares credentials and subscription with the CLI.
|
|
413
|
+
*/
|
|
414
|
+
export async function generateImageUncached(opts: GenerateImageOptions): Promise<Error | GenerateImageResult> {
|
|
415
|
+
injectCredentialsToEnv()
|
|
416
|
+
|
|
417
|
+
const model = opts.model ?? DEFAULT_MODEL
|
|
418
|
+
const count = opts.count ?? 1
|
|
419
|
+
const config = getModelConfig(model)
|
|
420
|
+
if (config instanceof Error) return config
|
|
421
|
+
|
|
422
|
+
const parsedAspectRatio = opts.aspectRatio && isAspectRatio(opts.aspectRatio)
|
|
423
|
+
? opts.aspectRatio
|
|
424
|
+
: undefined
|
|
425
|
+
const inputImages = opts.inputImages ?? []
|
|
426
|
+
|
|
427
|
+
// ChatGPT OAuth + OpenAI image models → Responses API path
|
|
428
|
+
const useResponsesApi = config.strategy === 'image' && shouldUseResponsesApi(model)
|
|
429
|
+
|
|
430
|
+
if (useResponsesApi) {
|
|
431
|
+
return generateWithResponsesApi({
|
|
432
|
+
prompt: opts.prompt,
|
|
433
|
+
model,
|
|
434
|
+
count,
|
|
435
|
+
aspectRatio: parsedAspectRatio,
|
|
436
|
+
seed: opts.seed,
|
|
437
|
+
maskImage: opts.maskImage,
|
|
438
|
+
inputImages,
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (config.strategy === 'image') {
|
|
443
|
+
return generateWithImageModel({
|
|
444
|
+
prompt: opts.prompt,
|
|
445
|
+
model,
|
|
446
|
+
count,
|
|
447
|
+
aspectRatio: parsedAspectRatio,
|
|
448
|
+
seed: opts.seed,
|
|
449
|
+
inputImages,
|
|
450
|
+
maskImage: opts.maskImage,
|
|
451
|
+
allowPeople: opts.allowPeople ?? false,
|
|
452
|
+
quality: opts.quality,
|
|
453
|
+
resolution: opts.resolution,
|
|
454
|
+
outputFormat: opts.outputFormat,
|
|
455
|
+
negativePrompt: opts.negativePrompt,
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Text model path (Gemini multimodal)
|
|
460
|
+
return generateWithTextModelPath({
|
|
461
|
+
prompt: opts.prompt,
|
|
462
|
+
model,
|
|
463
|
+
inputImages,
|
|
464
|
+
imageSize: opts.imageSize,
|
|
465
|
+
aspectRatio: opts.aspectRatio,
|
|
466
|
+
})
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ─── generateVideo ───────────────────────────────────────────────────────────
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Generate videos from a text prompt (or image+text for i2v models).
|
|
473
|
+
* Shares credentials and subscription with the CLI.
|
|
474
|
+
*/
|
|
475
|
+
export async function generateVideoUncached(opts: GenerateVideoOptions): Promise<Error | GenerateVideoResult> {
|
|
476
|
+
injectCredentialsToEnv()
|
|
477
|
+
|
|
478
|
+
const model = opts.model ?? DEFAULT_VIDEO_MODEL
|
|
479
|
+
const count = opts.count ?? 1
|
|
480
|
+
const config = getModelConfig(model)
|
|
481
|
+
if (config instanceof Error) return config
|
|
482
|
+
|
|
483
|
+
if (config.strategy !== 'video') {
|
|
484
|
+
return new ValidationError(`Model ${model} is not a video model`)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const isVideoInputMode = opts.mode === 'edit-video' || opts.mode === 'extend-video'
|
|
488
|
+
|
|
489
|
+
// Validate mode-specific required inputs
|
|
490
|
+
if (isVideoInputMode && !opts.videoUrl) {
|
|
491
|
+
return new ValidationError(`videoUrl is required with mode ${opts.mode}`)
|
|
492
|
+
}
|
|
493
|
+
if (opts.mode === 'reference-to-video' && (!opts.referenceImages || opts.referenceImages.length === 0)) {
|
|
494
|
+
return new ValidationError('referenceImages is required with mode reference-to-video')
|
|
495
|
+
}
|
|
496
|
+
if (opts.referenceImages?.length && !config.providerOptions?.some((opt) => opt.flag === 'reference-images')) {
|
|
497
|
+
return new ValidationError(`Model ${model} does not support referenceImages`)
|
|
498
|
+
}
|
|
499
|
+
if (opts.videoUrl && !config.providerOptions?.some((opt) => opt.flag === 'video-url')) {
|
|
500
|
+
return new ValidationError(`Model ${model} does not support video URL input for editing/extension`)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const videoModel = await createVideoModel(model)
|
|
504
|
+
if (videoModel instanceof Error) return videoModel
|
|
505
|
+
|
|
506
|
+
const providerOptions = buildVideoProviderOptions(config.provider, {
|
|
507
|
+
resolution: opts.resolution,
|
|
508
|
+
mode: opts.mode,
|
|
509
|
+
videoUrl: opts.videoUrl,
|
|
510
|
+
referenceImages: opts.referenceImages,
|
|
511
|
+
negativePrompt: opts.negativePrompt,
|
|
512
|
+
model,
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
let result
|
|
516
|
+
try {
|
|
517
|
+
result = await aiGenerateVideo({
|
|
518
|
+
model: videoModel,
|
|
519
|
+
prompt: opts.inputImage
|
|
520
|
+
? { image: opts.inputImage, text: opts.prompt }
|
|
521
|
+
: opts.prompt,
|
|
522
|
+
n: count,
|
|
523
|
+
...(opts.aspectRatio ? { aspectRatio: opts.aspectRatio as `${number}:${number}` } : {}),
|
|
524
|
+
...(opts.resolution ? { resolution: opts.resolution as `${number}x${number}` } : {}),
|
|
525
|
+
...(opts.duration != null ? { duration: opts.duration } : {}),
|
|
526
|
+
...(opts.fps != null ? { fps: opts.fps } : {}),
|
|
527
|
+
...(opts.seed != null ? { seed: opts.seed } : {}),
|
|
528
|
+
...(providerOptions ? { providerOptions } : {}),
|
|
529
|
+
})
|
|
530
|
+
} catch (err) {
|
|
531
|
+
return toError(err)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const videos = result.videos.map((v: { uint8Array: Uint8Array; mediaType: string }) => ({
|
|
535
|
+
uint8Array: v.uint8Array,
|
|
536
|
+
mediaType: v.mediaType,
|
|
537
|
+
}))
|
|
538
|
+
|
|
539
|
+
const cost = calculateCost(config.cost, {
|
|
540
|
+
videosGenerated: result.videos.length,
|
|
541
|
+
durationSeconds: opts.duration,
|
|
542
|
+
resolution: opts.resolution,
|
|
543
|
+
hasVideoInput: Boolean(opts.inputImage || opts.videoUrl || opts.referenceImages?.length),
|
|
544
|
+
}, result.videos.length)
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
videos,
|
|
548
|
+
model,
|
|
549
|
+
cost,
|
|
550
|
+
warnings: result.warnings,
|
|
551
|
+
responses: result.responses,
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ─── internal generation paths ───────────────────────────────────────────────
|
|
556
|
+
|
|
557
|
+
async function generateWithImageModel(opts: {
|
|
558
|
+
prompt: string
|
|
559
|
+
model: string
|
|
560
|
+
count: number
|
|
561
|
+
aspectRatio?: `${number}:${number}`
|
|
562
|
+
seed?: number
|
|
563
|
+
inputImages: Uint8Array[]
|
|
564
|
+
maskImage?: Uint8Array
|
|
565
|
+
allowPeople: boolean
|
|
566
|
+
quality?: string
|
|
567
|
+
resolution?: string
|
|
568
|
+
outputFormat?: string
|
|
569
|
+
negativePrompt?: string
|
|
570
|
+
}): Promise<Error | GenerateImageResult> {
|
|
571
|
+
const imagePrompt = opts.inputImages.length > 0
|
|
572
|
+
? { text: opts.prompt, images: opts.inputImages, ...(opts.maskImage ? { mask: opts.maskImage } : {}) }
|
|
573
|
+
: opts.prompt
|
|
574
|
+
|
|
575
|
+
const config = getModelConfig(opts.model)
|
|
576
|
+
if (config instanceof Error) return config
|
|
577
|
+
|
|
578
|
+
const imageModel = await createImageModel(opts.model)
|
|
579
|
+
if (imageModel instanceof Error) return imageModel
|
|
580
|
+
|
|
581
|
+
const providerOptions = buildImageProviderOptions(config.provider, {
|
|
582
|
+
aspectRatio: opts.aspectRatio,
|
|
583
|
+
allowPeople: opts.allowPeople,
|
|
584
|
+
quality: opts.quality,
|
|
585
|
+
resolution: opts.resolution,
|
|
586
|
+
outputFormat: opts.outputFormat,
|
|
587
|
+
negativePrompt: opts.negativePrompt,
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
let result
|
|
591
|
+
try {
|
|
592
|
+
result = await aiGenerateImage({
|
|
593
|
+
model: imageModel,
|
|
594
|
+
prompt: imagePrompt,
|
|
595
|
+
n: opts.count,
|
|
596
|
+
...(opts.aspectRatio ? { aspectRatio: opts.aspectRatio } : {}),
|
|
597
|
+
...(opts.seed !== undefined ? { seed: opts.seed } : {}),
|
|
598
|
+
providerOptions,
|
|
599
|
+
})
|
|
600
|
+
} catch (err) {
|
|
601
|
+
return toError(err)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const images = result.images.map((img) => ({
|
|
605
|
+
uint8Array: img.uint8Array,
|
|
606
|
+
mediaType: img.mediaType,
|
|
607
|
+
}))
|
|
608
|
+
|
|
609
|
+
const cost = calculateCost(config.cost, result.usage, result.images.length)
|
|
610
|
+
|
|
611
|
+
return {
|
|
612
|
+
images,
|
|
613
|
+
model: opts.model,
|
|
614
|
+
cost,
|
|
615
|
+
usage: result.usage,
|
|
616
|
+
warnings: result.warnings,
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async function generateWithTextModelPath(opts: {
|
|
621
|
+
prompt: string
|
|
622
|
+
model: string
|
|
623
|
+
inputImages: Uint8Array[]
|
|
624
|
+
imageSize?: '1K' | '2K' | '4K'
|
|
625
|
+
aspectRatio?: string
|
|
626
|
+
}): Promise<Error | GenerateImageResult> {
|
|
627
|
+
const textModel = await createTextModel(opts.model)
|
|
628
|
+
if (textModel instanceof Error) return textModel
|
|
629
|
+
|
|
630
|
+
const config = getModelConfig(opts.model)
|
|
631
|
+
if (config instanceof Error) return config
|
|
632
|
+
|
|
633
|
+
const messages = opts.inputImages.length > 0
|
|
634
|
+
? [
|
|
635
|
+
{
|
|
636
|
+
role: 'user' as const,
|
|
637
|
+
content: [
|
|
638
|
+
{ type: 'text' as const, text: opts.prompt },
|
|
639
|
+
...opts.inputImages.map((img) => ({
|
|
640
|
+
type: 'image' as const,
|
|
641
|
+
image: img,
|
|
642
|
+
})),
|
|
643
|
+
],
|
|
644
|
+
},
|
|
645
|
+
]
|
|
646
|
+
: undefined
|
|
647
|
+
|
|
648
|
+
type GoogleAspectRatio = NonNullable<NonNullable<GoogleLanguageModelOptions['imageConfig']>['aspectRatio']>
|
|
649
|
+
const googleOpts = {
|
|
650
|
+
responseModalities: ['TEXT', 'IMAGE'],
|
|
651
|
+
...(opts.imageSize || opts.aspectRatio
|
|
652
|
+
? {
|
|
653
|
+
imageConfig: {
|
|
654
|
+
...(opts.imageSize ? { imageSize: opts.imageSize } : {}),
|
|
655
|
+
...(opts.aspectRatio ? { aspectRatio: opts.aspectRatio as GoogleAspectRatio } : {}),
|
|
656
|
+
},
|
|
657
|
+
}
|
|
658
|
+
: {}),
|
|
659
|
+
} satisfies GoogleLanguageModelOptions
|
|
660
|
+
|
|
661
|
+
const providerOptionsKey = config.provider === 'vertex' ? 'vertex' : 'google'
|
|
662
|
+
|
|
663
|
+
let result
|
|
664
|
+
try {
|
|
665
|
+
result = await generateText({
|
|
666
|
+
model: textModel,
|
|
667
|
+
...(messages ? { messages } : { prompt: opts.prompt }),
|
|
668
|
+
providerOptions: {
|
|
669
|
+
[providerOptionsKey]: googleOpts,
|
|
670
|
+
},
|
|
671
|
+
})
|
|
672
|
+
} catch (err) {
|
|
673
|
+
return toError(err)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const imageFiles = result.files.filter((f) => f.mediaType.startsWith('image/'))
|
|
677
|
+
if (imageFiles.length === 0) {
|
|
678
|
+
return new Error('No images generated by the text model.')
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const images = imageFiles.map((f) => ({
|
|
682
|
+
uint8Array: f.uint8Array,
|
|
683
|
+
mediaType: f.mediaType,
|
|
684
|
+
}))
|
|
685
|
+
|
|
686
|
+
const cost = calculateCost(config.cost, result.usage, imageFiles.length)
|
|
687
|
+
|
|
688
|
+
return {
|
|
689
|
+
images,
|
|
690
|
+
model: opts.model,
|
|
691
|
+
cost,
|
|
692
|
+
usage: result.usage,
|
|
693
|
+
text: result.text || undefined,
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
async function generateWithResponsesApi(opts: {
|
|
698
|
+
prompt: string
|
|
699
|
+
model: string
|
|
700
|
+
count: number
|
|
701
|
+
aspectRatio?: `${number}:${number}`
|
|
702
|
+
seed?: number
|
|
703
|
+
maskImage?: Uint8Array
|
|
704
|
+
inputImages: Uint8Array[]
|
|
705
|
+
}): Promise<Error | GenerateImageResult> {
|
|
706
|
+
if (opts.count !== 1) {
|
|
707
|
+
return new ValidationError('ChatGPT image generation currently supports exactly one output image.')
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (opts.seed !== undefined) {
|
|
711
|
+
return new ValidationError('ChatGPT image generation does not support seed.')
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (opts.maskImage) {
|
|
715
|
+
return new ValidationError('ChatGPT image generation does not support mask yet.')
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const size = opts.aspectRatio ? chatGptImageSizeFromAspectRatio(opts.aspectRatio) : undefined
|
|
719
|
+
if (opts.aspectRatio && !size) {
|
|
720
|
+
return new ValidationError('ChatGPT image generation only supports aspect ratios 1:1, 3:2, or 2:3.')
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const storedAuth = getChatGptAuth()
|
|
724
|
+
if (!storedAuth?.accountId) {
|
|
725
|
+
return new ValidationError('Missing ChatGPT account metadata. Please run `egaki login --provider chatgpt` again.')
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const auth = await getValidChatGptAuth(storedAuth, saveChatGptAuth)
|
|
729
|
+
if (auth instanceof Error) return auth
|
|
730
|
+
|
|
731
|
+
const content: Array<{ type: 'input_text'; text: string } | { type: 'input_image'; image_url: string }> = [
|
|
732
|
+
{ type: 'input_text', text: opts.prompt },
|
|
733
|
+
...opts.inputImages.map((image) => ({
|
|
734
|
+
type: 'input_image' as const,
|
|
735
|
+
image_url: imageBytesToDataUrl(image),
|
|
736
|
+
})),
|
|
737
|
+
]
|
|
738
|
+
|
|
739
|
+
let response: Response
|
|
740
|
+
try {
|
|
741
|
+
response = await fetch('https://chatgpt.com/backend-api/codex/responses', {
|
|
742
|
+
method: 'POST',
|
|
743
|
+
headers: {
|
|
744
|
+
Authorization: `Bearer ${auth.access}`,
|
|
745
|
+
...(auth.accountId && { 'ChatGPT-Account-ID': auth.accountId }),
|
|
746
|
+
'Content-Type': 'application/json',
|
|
747
|
+
},
|
|
748
|
+
body: JSON.stringify({
|
|
749
|
+
model: 'gpt-5.4',
|
|
750
|
+
instructions: 'You are Codex.',
|
|
751
|
+
input: [
|
|
752
|
+
{
|
|
753
|
+
type: 'message',
|
|
754
|
+
role: 'user',
|
|
755
|
+
content,
|
|
756
|
+
},
|
|
757
|
+
],
|
|
758
|
+
tools: [
|
|
759
|
+
{
|
|
760
|
+
type: 'image_generation',
|
|
761
|
+
model: opts.model,
|
|
762
|
+
size: 'auto',
|
|
763
|
+
quality: 'auto',
|
|
764
|
+
output_format: 'png',
|
|
765
|
+
output_compression: 100,
|
|
766
|
+
moderation: 'auto',
|
|
767
|
+
...(size ? { size } : {}),
|
|
768
|
+
},
|
|
769
|
+
],
|
|
770
|
+
tool_choice: 'auto',
|
|
771
|
+
parallel_tool_calls: true,
|
|
772
|
+
stream: true,
|
|
773
|
+
store: false,
|
|
774
|
+
include: [],
|
|
775
|
+
}),
|
|
776
|
+
})
|
|
777
|
+
} catch (err) {
|
|
778
|
+
return toError(err)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (!response.ok || !response.body) {
|
|
782
|
+
const body = await response.text().catch(() => '')
|
|
783
|
+
return new Error(`ChatGPT image generation failed: ${response.status}${body ? ` ${body}` : ''}`)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
let imageBase64: string | undefined
|
|
787
|
+
let revisedPrompt: string | null = null
|
|
788
|
+
|
|
789
|
+
const parser = createParser({
|
|
790
|
+
onEvent: (message: EventSourceMessage) => {
|
|
791
|
+
if (!message.data) return
|
|
792
|
+
try {
|
|
793
|
+
const event = JSON.parse(message.data) as {
|
|
794
|
+
type?: string
|
|
795
|
+
partial_image_b64?: string
|
|
796
|
+
item?: { type?: string; result?: string; revised_prompt?: string | null }
|
|
797
|
+
}
|
|
798
|
+
if (
|
|
799
|
+
event.type === 'response.image_generation_call.partial_image' &&
|
|
800
|
+
event.partial_image_b64 &&
|
|
801
|
+
!imageBase64
|
|
802
|
+
) {
|
|
803
|
+
imageBase64 = event.partial_image_b64
|
|
804
|
+
}
|
|
805
|
+
if (
|
|
806
|
+
event.type === 'response.output_item.done' &&
|
|
807
|
+
event.item?.type === 'image_generation_call'
|
|
808
|
+
) {
|
|
809
|
+
if (event.item.result) imageBase64 = event.item.result
|
|
810
|
+
revisedPrompt = event.item.revised_prompt ?? revisedPrompt
|
|
811
|
+
}
|
|
812
|
+
} catch {
|
|
813
|
+
// Ignore keepalive and partial parse noise.
|
|
814
|
+
}
|
|
815
|
+
},
|
|
816
|
+
})
|
|
817
|
+
|
|
818
|
+
const decoder = new TextDecoder()
|
|
819
|
+
for await (const chunk of response.body as unknown as AsyncIterable<Uint8Array>) {
|
|
820
|
+
parser.feed(decoder.decode(chunk, { stream: true }))
|
|
821
|
+
}
|
|
822
|
+
parser.feed(decoder.decode())
|
|
823
|
+
|
|
824
|
+
if (!imageBase64) {
|
|
825
|
+
return new Error('No images generated by ChatGPT.')
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const imageBytes = new Uint8Array(Buffer.from(imageBase64, 'base64'))
|
|
829
|
+
|
|
830
|
+
return {
|
|
831
|
+
images: [{ uint8Array: imageBytes, mediaType: 'image/png' }],
|
|
832
|
+
model: opts.model,
|
|
833
|
+
cost: null,
|
|
834
|
+
revisedPrompt,
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// ─── cached result types ─────────────────────────────────────────────────────
|
|
839
|
+
|
|
840
|
+
export interface CachedGenerateResult {
|
|
841
|
+
/** Public URL path (e.g. `/generated/image/sunset-a1b2c3d4.png`) */
|
|
842
|
+
src: string
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// ─── cached generateImage ───────────────────────────────────────────────────
|
|
846
|
+
|
|
847
|
+
import { cachedGenerate } from './cached-generate.js'
|
|
848
|
+
import { extensionFromMediaType } from './cache-utils.js'
|
|
849
|
+
|
|
850
|
+
export const generateImage = cachedGenerate<GenerateImageOptions & { seed?: number }, GeneratedFile, CachedGenerateResult>({
|
|
851
|
+
namespace: 'image',
|
|
852
|
+
prefixFrom: (p) => p.prompt,
|
|
853
|
+
modelFrom: (p) => p.model,
|
|
854
|
+
generate: async (params) => {
|
|
855
|
+
const result = await generateImageUncached(params)
|
|
856
|
+
if (result instanceof Error) throw result
|
|
857
|
+
const file = result.images[0]
|
|
858
|
+
if (!file) throw new Error('No image generated')
|
|
859
|
+
return file
|
|
860
|
+
},
|
|
861
|
+
serialize: (file) => ({
|
|
862
|
+
bytes: file.uint8Array,
|
|
863
|
+
extension: extensionFromMediaType(file.mediaType),
|
|
864
|
+
}),
|
|
865
|
+
})
|
|
866
|
+
|
|
867
|
+
// ─── cached generateVideo ───────────────────────────────────────────────────
|
|
868
|
+
|
|
869
|
+
export const generateVideo = cachedGenerate<GenerateVideoOptions & { seed?: number }, GeneratedFile, CachedGenerateResult>({
|
|
870
|
+
namespace: 'video',
|
|
871
|
+
prefixFrom: (p) => p.prompt,
|
|
872
|
+
modelFrom: (p) => p.model,
|
|
873
|
+
generate: async (params) => {
|
|
874
|
+
const result = await generateVideoUncached(params)
|
|
875
|
+
if (result instanceof Error) throw result
|
|
876
|
+
const file = result.videos[0]
|
|
877
|
+
if (!file) throw new Error('No video generated')
|
|
878
|
+
return file
|
|
879
|
+
},
|
|
880
|
+
serialize: (file) => ({
|
|
881
|
+
bytes: file.uint8Array,
|
|
882
|
+
extension: extensionFromMediaType(file.mediaType),
|
|
883
|
+
}),
|
|
884
|
+
})
|
|
885
|
+
|
|
886
|
+
// ─── re-export speech generation ─────────────────────────────────────────────
|
|
887
|
+
|
|
888
|
+
export {
|
|
889
|
+
generateSpeech,
|
|
890
|
+
generateSpeechUncached,
|
|
891
|
+
calculateSpeechCost,
|
|
892
|
+
type GenerateSpeechOptions,
|
|
893
|
+
type GenerateSpeechResult,
|
|
894
|
+
type CachedSpeechResult,
|
|
895
|
+
type SpeechProvider,
|
|
896
|
+
type SpeechProviderOptions,
|
|
897
|
+
type SpeechProviderResult,
|
|
898
|
+
} from './speech-generate.js'
|
|
899
|
+
|
|
900
|
+
// ─── re-export transcription ─────────────────────────────────────────────────
|
|
901
|
+
|
|
902
|
+
export {
|
|
903
|
+
transcribeAudio,
|
|
904
|
+
transcribeAudioUncached,
|
|
905
|
+
calculateTranscriptionCost,
|
|
906
|
+
segmentsToWordTimestamps,
|
|
907
|
+
wordTimestampsToCaptions,
|
|
908
|
+
type TranscribeOptions,
|
|
909
|
+
type TranscribeResult,
|
|
910
|
+
type WordTimestamp,
|
|
911
|
+
type TranscriptionSegment,
|
|
912
|
+
} from './transcription-generate.js'
|