@tanstack/ai-byteplus 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +202 -0
- package/dist/esm/adapters/image.d.ts +89 -0
- package/dist/esm/adapters/image.js +229 -0
- package/dist/esm/adapters/image.js.map +1 -0
- package/dist/esm/adapters/text.d.ts +163 -0
- package/dist/esm/adapters/text.js +347 -0
- package/dist/esm/adapters/text.js.map +1 -0
- package/dist/esm/adapters/transcription.d.ts +102 -0
- package/dist/esm/adapters/transcription.js +274 -0
- package/dist/esm/adapters/transcription.js.map +1 -0
- package/dist/esm/adapters/tts.d.ts +143 -0
- package/dist/esm/adapters/tts.js +307 -0
- package/dist/esm/adapters/tts.js.map +1 -0
- package/dist/esm/adapters/video.d.ts +182 -0
- package/dist/esm/adapters/video.js +442 -0
- package/dist/esm/adapters/video.js.map +1 -0
- package/dist/esm/audio/transcription-provider-options.d.ts +46 -0
- package/dist/esm/audio/tts-provider-options.d.ts +114 -0
- package/dist/esm/audio/wire-types.d.ts +261 -0
- package/dist/esm/audio/wire-types.js +28 -0
- package/dist/esm/audio/wire-types.js.map +1 -0
- package/dist/esm/image/image-provider-options.d.ts +165 -0
- package/dist/esm/image/image-provider-options.js +134 -0
- package/dist/esm/image/image-provider-options.js.map +1 -0
- package/dist/esm/image/wire-types.d.ts +149 -0
- package/dist/esm/index.d.ts +25 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/message-types.d.ts +154 -0
- package/dist/esm/model-meta.d.ts +594 -0
- package/dist/esm/model-meta.js +619 -0
- package/dist/esm/model-meta.js.map +1 -0
- package/dist/esm/text/text-provider-options.d.ts +109 -0
- package/dist/esm/utils/client.d.ts +183 -0
- package/dist/esm/utils/client.js +253 -0
- package/dist/esm/utils/client.js.map +1 -0
- package/dist/esm/video/video-provider-options.d.ts +197 -0
- package/dist/esm/video/video-provider-options.js +191 -0
- package/dist/esm/video/video-provider-options.js.map +1 -0
- package/dist/esm/video/wire-types.d.ts +248 -0
- package/package.json +77 -0
- package/src/adapters/image.ts +409 -0
- package/src/adapters/text.ts +539 -0
- package/src/adapters/transcription.ts +479 -0
- package/src/adapters/tts.ts +447 -0
- package/src/adapters/video.ts +732 -0
- package/src/audio/transcription-provider-options.ts +46 -0
- package/src/audio/tts-provider-options.ts +122 -0
- package/src/audio/wire-types.ts +290 -0
- package/src/image/image-provider-options.ts +288 -0
- package/src/image/wire-types.ts +169 -0
- package/src/index.ts +222 -0
- package/src/message-types.ts +169 -0
- package/src/model-meta.ts +954 -0
- package/src/text/text-provider-options.ts +151 -0
- package/src/utils/client.ts +377 -0
- package/src/video/video-provider-options.ts +361 -0
- package/src/video/wire-types.ts +293 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Tanner Linsley
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# @tanstack/ai-byteplus
|
|
2
|
+
|
|
3
|
+
BytePlus ModelArk adapter for TanStack AI — Seed chat models, Seedance video
|
|
4
|
+
generation, Seedream image generation, and Seed Speech text-to-speech and
|
|
5
|
+
transcription.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @tanstack/ai-byteplus
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @tanstack/ai-byteplus
|
|
13
|
+
# or
|
|
14
|
+
yarn add @tanstack/ai-byteplus
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
BytePlus splits its models across two products with **two different API keys**:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Ark (ModelArk): chat, Seedance video, Seedream image
|
|
23
|
+
export ARK_API_KEY="..."
|
|
24
|
+
|
|
25
|
+
# Seed Speech: text-to-speech and transcription — a separate product key
|
|
26
|
+
export BYTEPLUS_VOICE_API_KEY="..."
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Ark keys are region-isolated. The default base URL is the Asia-Pacific
|
|
30
|
+
south-east endpoint (`https://ark.ap-southeast.bytepluses.com/api/v3`); per the
|
|
31
|
+
BytePlus docs the EU endpoint serves chat and image only (docs-derived — only
|
|
32
|
+
the ap-southeast host was exercised live).
|
|
33
|
+
|
|
34
|
+
## Why there's no Volcengine SDK dependency
|
|
35
|
+
|
|
36
|
+
This package does **not** depend on `@volcengine/ark-runtime` (or any other
|
|
37
|
+
first-party BytePlus/Volcengine SDK). That is deliberate:
|
|
38
|
+
|
|
39
|
+
- **Chat doesn't need one.** Ark's `/chat/completions` is OpenAI-compatible, so
|
|
40
|
+
the chat adapter rides the `openai` SDK through `@tanstack/openai-base` — the
|
|
41
|
+
same path `@tanstack/ai-grok` and `@tanstack/ai-groq` take, with identical
|
|
42
|
+
dependencies. That reuses the shared streaming, tool-calling and
|
|
43
|
+
structured-output machinery instead of forking it per provider.
|
|
44
|
+
- **Nothing else is OpenAI-shaped anyway.** Seedance video, Seedream image and
|
|
45
|
+
Seed Speech are bespoke endpoints on two different hosts with two different
|
|
46
|
+
auth headers. An SDK would not spare us the wire types; it would add a second
|
|
47
|
+
dependency that still had to be translated at the boundary.
|
|
48
|
+
|
|
49
|
+
The cost is that the non-chat wire types are hand-written and pinned by tests
|
|
50
|
+
rather than generated. That is a considered trade, not an oversight — see
|
|
51
|
+
`src/video/wire-types.ts`, `src/image/wire-types.ts` and
|
|
52
|
+
`src/audio/wire-types.ts`, each of which records how its shape was verified.
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Chat
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { chat } from '@tanstack/ai'
|
|
60
|
+
import { byteplusText } from '@tanstack/ai-byteplus'
|
|
61
|
+
|
|
62
|
+
// The adapter carries the model — there is no separate `model` option.
|
|
63
|
+
const adapter = byteplusText('seed-2-0-lite-260428')
|
|
64
|
+
|
|
65
|
+
const text = await chat({
|
|
66
|
+
adapter,
|
|
67
|
+
messages: [{ role: 'user', content: 'Explain diffusion models briefly' }],
|
|
68
|
+
stream: false,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
console.log(text)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Drop `stream: false` to get the default streaming form, which yields
|
|
75
|
+
`StreamChunk`s you can iterate with `for await`.
|
|
76
|
+
|
|
77
|
+
Seed models reason by default. Reasoning arrives as a separate stream of
|
|
78
|
+
`reasoning_content` deltas and is surfaced as reasoning content, not answer
|
|
79
|
+
text. Pass `thinking: { type: 'disabled' }` in provider options to turn it off.
|
|
80
|
+
|
|
81
|
+
### Video (Seedance)
|
|
82
|
+
|
|
83
|
+
Seedance is an async task API, so `generateVideo()` only opens the job and
|
|
84
|
+
returns its `jobId`. Poll `getVideoJobStatus()` until the job settles — the
|
|
85
|
+
video URL arrives with the terminal status.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { generateVideo, getVideoJobStatus } from '@tanstack/ai'
|
|
89
|
+
import { byteplusVideo } from '@tanstack/ai-byteplus'
|
|
90
|
+
|
|
91
|
+
const adapter = byteplusVideo('seedance-1-5-pro-251215')
|
|
92
|
+
|
|
93
|
+
const { jobId } = await generateVideo({
|
|
94
|
+
adapter,
|
|
95
|
+
prompt: 'a guitar being played in a store',
|
|
96
|
+
size: '16:9_720p',
|
|
97
|
+
duration: 5,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
let status = await getVideoJobStatus({ adapter, jobId })
|
|
101
|
+
while (status.status === 'pending' || status.status === 'processing') {
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, 5000))
|
|
103
|
+
status = await getVideoJobStatus({ adapter, jobId })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(status.status === 'completed' ? status.url : status.error)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
To let the core drive the whole lifecycle instead, pass `stream: true` and hand
|
|
110
|
+
the resulting chunk stream to your transport:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { generateVideo, toServerSentEventsResponse } from '@tanstack/ai'
|
|
114
|
+
import { byteplusVideo } from '@tanstack/ai-byteplus'
|
|
115
|
+
|
|
116
|
+
const stream = generateVideo({
|
|
117
|
+
adapter: byteplusVideo('seedance-1-5-pro-251215'),
|
|
118
|
+
prompt: 'a guitar being played in a store',
|
|
119
|
+
stream: true,
|
|
120
|
+
pollingInterval: 5000,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
return toServerSentEventsResponse(stream)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Generated video URLs expire after 24 hours** (the task record itself is kept
|
|
127
|
+
for 7 days), so download anything you need to keep.
|
|
128
|
+
|
|
129
|
+
### Image (Seedream)
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { generateImage } from '@tanstack/ai'
|
|
133
|
+
import { byteplusImage } from '@tanstack/ai-byteplus'
|
|
134
|
+
|
|
135
|
+
const result = await generateImage({
|
|
136
|
+
adapter: byteplusImage('seedream-4-0-250828'),
|
|
137
|
+
prompt: 'a guitar being played in a store',
|
|
138
|
+
size: '2K',
|
|
139
|
+
modelOptions: { watermark: false },
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
console.log(result.images[0]?.url)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`size` takes either a token (`1K`, `2K`, `4K`) or explicit pixels
|
|
146
|
+
(`2048x2048`) — never a mix. Pass image parts in the `prompt` array to edit or
|
|
147
|
+
condition on existing images (up to 14 references, 10 on
|
|
148
|
+
`dola-seedream-5-0-pro-260628`).
|
|
149
|
+
|
|
150
|
+
Two behaviors surprise people:
|
|
151
|
+
|
|
152
|
+
- **`watermark` defaults to `true`.** BytePlus stamps "AI generated" into the
|
|
153
|
+
bottom-right corner unless you pass `watermark: false`. The adapter never
|
|
154
|
+
sets it implicitly, so the provider default applies.
|
|
155
|
+
- **`numberOfImages` is an upper bound, not a count.** Seedream has no `n`
|
|
156
|
+
parameter; asking for more than one image maps to its group-image mode
|
|
157
|
+
(`sequential_image_generation: 'auto'` with `max_images`), where the model
|
|
158
|
+
decides how many images the prompt actually warrants. A request for four can
|
|
159
|
+
return fewer, and the adapter logs a warning when it does.
|
|
160
|
+
|
|
161
|
+
Result URLs expire after 24 hours; pass `response_format: 'b64_json'` in
|
|
162
|
+
`modelOptions` to get bytes inline instead.
|
|
163
|
+
|
|
164
|
+
## Supported models
|
|
165
|
+
|
|
166
|
+
- **Chat** — `dola-seed-2-1-turbo-260628`, the `seed-2-0-*` family,
|
|
167
|
+
`seed-1-8-251228`, the `seed-1-6-*` family, plus `glm-*`, `deepseek-*` and
|
|
168
|
+
`gpt-oss-120b-250805`.
|
|
169
|
+
- **Video** — `dreamina-seedance-2-0-260128` (and `-fast-`/`-mini-`),
|
|
170
|
+
`seedance-1-5-pro-251215`, `seedance-1-0-pro-250528`,
|
|
171
|
+
`seedance-1-0-pro-fast-251015`.
|
|
172
|
+
- **Image** — `dola-seedream-5-0-pro-260628`, `seedream-5-0-260128`,
|
|
173
|
+
`seedream-5-0-lite-260128`, `seedream-4-5-251128`, `seedream-4-0-250828`.
|
|
174
|
+
- **Speech** — `seed-audio-1.0` (TTS) and `seed-asr` (transcription).
|
|
175
|
+
|
|
176
|
+
BytePlus retires model ids aggressively, so only dated ids that were verified
|
|
177
|
+
live against the API are exported. (The two Seed Speech ids are the exception:
|
|
178
|
+
`seed-audio-1.0` is undated, `seed-asr` is a synthetic id for an
|
|
179
|
+
endpoint-addressed API that takes no `model` field, and neither could be
|
|
180
|
+
verified live pending a Seed Speech key.) `BYTEPLUS_CHAT_MODELS`,
|
|
181
|
+
`BYTEPLUS_VIDEO_MODELS`, `BYTEPLUS_IMAGE_MODELS`, `BYTEPLUS_TTS_MODELS` and
|
|
182
|
+
`BYTEPLUS_TRANSCRIPTION_MODELS` are the authoritative lists.
|
|
183
|
+
|
|
184
|
+
## Seedance: direct vs. via fal
|
|
185
|
+
|
|
186
|
+
Seedance is also reachable through `@tanstack/ai-fal`, which proxies it along
|
|
187
|
+
with hundreds of other models. This package talks to BytePlus directly, which
|
|
188
|
+
means BytePlus billing and rate limits, the first-class Seedance request fields
|
|
189
|
+
(`camera_fixed`, `generate_audio`, `watermark`, reference-image roles, …), and
|
|
190
|
+
model ids in BytePlus's own naming. Use whichever fits your account; there is
|
|
191
|
+
no reason to install both for Seedance alone.
|
|
192
|
+
|
|
193
|
+
## Seed Speech needs its own key
|
|
194
|
+
|
|
195
|
+
The TTS and transcription adapters do **not** talk to Ark. They use
|
|
196
|
+
`voice.ap-southeast-1.bytepluses.com` with an `X-Api-Key` header and the
|
|
197
|
+
Seed Speech product key (`BYTEPLUS_VOICE_API_KEY`). Passing an Ark key there
|
|
198
|
+
fails with `45000010 Invalid X-Api-Key`.
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { BaseImageAdapter } from '@tanstack/ai/adapters';
|
|
2
|
+
import { ImageGenerationOptions, ImageGenerationResult } from '@tanstack/ai';
|
|
3
|
+
import { BytePlusImageModelInputModalitiesByName, BytePlusImageModelProviderOptionsByName, BytePlusImageProviderOptions } from '../image/image-provider-options.js';
|
|
4
|
+
import { BytePlusImageModel, BytePlusImageModelSizeByName } from '../model-meta.js';
|
|
5
|
+
import { BytePlusArkConfig } from '../utils/client.js';
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for the BytePlus Seedream image adapter.
|
|
8
|
+
*/
|
|
9
|
+
export interface BytePlusImageConfig extends BytePlusArkConfig {
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* BytePlus Seedream image generation adapter.
|
|
13
|
+
*
|
|
14
|
+
* Drives Ark's `POST /images/generations` endpoint directly rather than
|
|
15
|
+
* through the OpenAI SDK: the endpoint takes size tokens (`2K`) as well as
|
|
16
|
+
* pixel sizes, has no `n` parameter, and carries reference images for editing
|
|
17
|
+
* in the generation request instead of a separate edits endpoint.
|
|
18
|
+
*
|
|
19
|
+
* Features:
|
|
20
|
+
* - Text-to-image and image-conditioned generation (editing, multi-reference)
|
|
21
|
+
* from a single call, with per-model reference-count limits enforced.
|
|
22
|
+
* - Size validation across both accepted forms.
|
|
23
|
+
* - `numberOfImages` mapped onto Seedream's group-image mode.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const adapter = byteplusImage('seedream-4-0-250828')
|
|
28
|
+
* const result = await generateImage({
|
|
29
|
+
* adapter,
|
|
30
|
+
* prompt: 'A guitar in a sunlit workshop',
|
|
31
|
+
* size: '2K',
|
|
32
|
+
* modelOptions: { watermark: false },
|
|
33
|
+
* })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare class BytePlusImageAdapter<TModel extends BytePlusImageModel> extends BaseImageAdapter<TModel, BytePlusImageProviderOptions, BytePlusImageModelProviderOptionsByName, BytePlusImageModelSizeByName, BytePlusImageModelInputModalitiesByName> {
|
|
37
|
+
readonly kind: "image";
|
|
38
|
+
readonly name: "byteplus";
|
|
39
|
+
/** Config with the Ark base URL resolved and trailing slashes trimmed. */
|
|
40
|
+
private readonly clientConfig;
|
|
41
|
+
constructor(model: TModel, config: BytePlusImageConfig);
|
|
42
|
+
generateImages(options: ImageGenerationOptions<BytePlusImageProviderOptions, BytePlusImageModelSizeByName[TModel]>): Promise<ImageGenerationResult>;
|
|
43
|
+
private transformResponse;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Creates a BytePlus Seedream image adapter with an explicit API key.
|
|
47
|
+
* Type resolution happens here at the call site.
|
|
48
|
+
*
|
|
49
|
+
* @param model - The model name (e.g., 'seedream-4-0-250828')
|
|
50
|
+
* @param apiKey - Your BytePlus Ark API key
|
|
51
|
+
* @param config - Optional additional configuration
|
|
52
|
+
* @returns Configured BytePlus image adapter instance with resolved types
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const adapter = createBytePlusImage('seedream-5-0-260128', 'ark-...')
|
|
57
|
+
*
|
|
58
|
+
* const result = await generateImage({
|
|
59
|
+
* adapter,
|
|
60
|
+
* prompt: 'A cute baby sea otter',
|
|
61
|
+
* size: '2K',
|
|
62
|
+
* })
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function createBytePlusImage<TModel extends BytePlusImageModel>(model: TModel, apiKey: string, config?: Omit<BytePlusImageConfig, 'apiKey'>): BytePlusImageAdapter<TModel>;
|
|
66
|
+
/**
|
|
67
|
+
* Creates a BytePlus Seedream image adapter, reading `ARK_API_KEY` from the
|
|
68
|
+
* environment. Type resolution happens here at the call site.
|
|
69
|
+
*
|
|
70
|
+
* Note that Ark keys are region-isolated: a key issued for `ap-southeast`
|
|
71
|
+
* does not work against the EU host.
|
|
72
|
+
*
|
|
73
|
+
* @param model - The model name (e.g., 'seedream-4-0-250828')
|
|
74
|
+
* @param config - Optional configuration (excluding apiKey, auto-detected)
|
|
75
|
+
* @returns Configured BytePlus image adapter instance with resolved types
|
|
76
|
+
* @throws Error if ARK_API_KEY is not found in environment
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const adapter = byteplusImage('seedream-4-0-250828')
|
|
81
|
+
*
|
|
82
|
+
* const result = await generateImage({
|
|
83
|
+
* adapter,
|
|
84
|
+
* prompt: 'A beautiful sunset over mountains',
|
|
85
|
+
* modelOptions: { watermark: false },
|
|
86
|
+
* })
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export declare function byteplusImage<TModel extends BytePlusImageModel>(model: TModel, config?: Omit<BytePlusImageConfig, 'apiKey'>): BytePlusImageAdapter<TModel>;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { bytePlusArkError, bytePlusArkHeaders, bytePlusTimeoutSignal, describeBody, getBytePlusArkApiKeyFromEnv, readJsonBody, toHeaderRecord, withBytePlusArkDefaults } from "../utils/client.js";
|
|
2
|
+
import { resolveBytePlusImageSize, resolveBytePlusSequentialImages, validateBytePlusImagePrompt, validateBytePlusReferenceImages } from "../image/image-provider-options.js";
|
|
3
|
+
import { resolveMediaPrompt } from "@tanstack/ai";
|
|
4
|
+
import { BaseImageAdapter } from "@tanstack/ai/adapters";
|
|
5
|
+
import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
|
|
6
|
+
import { generateId } from "@tanstack/ai-utils";
|
|
7
|
+
//#region src/adapters/image.ts
|
|
8
|
+
/**
|
|
9
|
+
* Roles Seedream can honour. Every input image is a reference — there is no
|
|
10
|
+
* inpainting mask, control-image or frame channel — so this is an allow-list
|
|
11
|
+
* rather than a deny-list: a role added to the core union later (or a
|
|
12
|
+
* video-oriented one like `start_frame`) fails loudly here instead of being
|
|
13
|
+
* silently flattened into a plain reference.
|
|
14
|
+
*/
|
|
15
|
+
var SUPPORTED_INPUT_ROLES = /* @__PURE__ */ new Set(["reference", "character"]);
|
|
16
|
+
/**
|
|
17
|
+
* Converts a prompt image part to the string Seedream's `image` field takes:
|
|
18
|
+
* URLs pass through (BytePlus fetches them server-side), data sources become
|
|
19
|
+
* data URIs. BytePlus requires the format in `data:image/<format>;base64,` to
|
|
20
|
+
* be lowercase, so the mime type is lowercased on the way out.
|
|
21
|
+
*/
|
|
22
|
+
function imagePartToImageRef(part) {
|
|
23
|
+
const { source } = part;
|
|
24
|
+
if (source.type === "url") return source.value;
|
|
25
|
+
if (source.value.startsWith("data:")) return source.value;
|
|
26
|
+
return `data:${source.mimeType.toLowerCase()};base64,${source.value}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Renders provider error objects as `code: message` pairs for a log line or
|
|
30
|
+
* an error message.
|
|
31
|
+
*/
|
|
32
|
+
function describeFailures(failures) {
|
|
33
|
+
return failures.map((failure) => [failure.code, failure.message].filter(Boolean).join(": ")).filter((text) => text.length > 0).join("; ");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Maps Seedream's usage block onto `TokenUsage`.
|
|
37
|
+
*
|
|
38
|
+
* BytePlus bills per generated image and does not count input tokens, so
|
|
39
|
+
* `promptTokens` is always 0 and `generated_images` is surfaced as
|
|
40
|
+
* `unitsBilled` — the count the price is applied to.
|
|
41
|
+
*/
|
|
42
|
+
function buildBytePlusImageUsage(usage) {
|
|
43
|
+
if (!usage) return void 0;
|
|
44
|
+
const completionTokens = usage.output_tokens ?? 0;
|
|
45
|
+
return {
|
|
46
|
+
promptTokens: 0,
|
|
47
|
+
completionTokens,
|
|
48
|
+
totalTokens: usage.total_tokens ?? completionTokens,
|
|
49
|
+
...usage.generated_images !== void 0 && { unitsBilled: usage.generated_images }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* BytePlus Seedream image generation adapter.
|
|
54
|
+
*
|
|
55
|
+
* Drives Ark's `POST /images/generations` endpoint directly rather than
|
|
56
|
+
* through the OpenAI SDK: the endpoint takes size tokens (`2K`) as well as
|
|
57
|
+
* pixel sizes, has no `n` parameter, and carries reference images for editing
|
|
58
|
+
* in the generation request instead of a separate edits endpoint.
|
|
59
|
+
*
|
|
60
|
+
* Features:
|
|
61
|
+
* - Text-to-image and image-conditioned generation (editing, multi-reference)
|
|
62
|
+
* from a single call, with per-model reference-count limits enforced.
|
|
63
|
+
* - Size validation across both accepted forms.
|
|
64
|
+
* - `numberOfImages` mapped onto Seedream's group-image mode.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const adapter = byteplusImage('seedream-4-0-250828')
|
|
69
|
+
* const result = await generateImage({
|
|
70
|
+
* adapter,
|
|
71
|
+
* prompt: 'A guitar in a sunlit workshop',
|
|
72
|
+
* size: '2K',
|
|
73
|
+
* modelOptions: { watermark: false },
|
|
74
|
+
* })
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
var BytePlusImageAdapter = class extends BaseImageAdapter {
|
|
78
|
+
kind = "image";
|
|
79
|
+
name = "byteplus";
|
|
80
|
+
/** Config with the Ark base URL resolved and trailing slashes trimmed. */
|
|
81
|
+
clientConfig;
|
|
82
|
+
constructor(model, config) {
|
|
83
|
+
super(model, {});
|
|
84
|
+
this.clientConfig = withBytePlusArkDefaults(config);
|
|
85
|
+
}
|
|
86
|
+
async generateImages(options) {
|
|
87
|
+
const { numberOfImages, size, modelOptions, logger } = options;
|
|
88
|
+
const model = this.model;
|
|
89
|
+
const resolved = resolveMediaPrompt(options.prompt);
|
|
90
|
+
if (resolved.videos.length > 0 || resolved.audios.length > 0) throw new Error(`byteplus.generateImages does not support video / audio prompt parts on model ${model}.`);
|
|
91
|
+
const unsupportedRole = resolved.images.find((part) => part.metadata?.role !== void 0 && !SUPPORTED_INPUT_ROLES.has(part.metadata.role));
|
|
92
|
+
if (unsupportedRole) throw new Error(`byteplus: Seedream has no ${unsupportedRole.metadata?.role} input; it accepts reference images only (${[...SUPPORTED_INPUT_ROLES].join(", ")}).`);
|
|
93
|
+
validateBytePlusImagePrompt(model, resolved.text);
|
|
94
|
+
validateBytePlusReferenceImages(model, resolved.images.length);
|
|
95
|
+
const imageRefs = resolved.images.map(imagePartToImageRef);
|
|
96
|
+
const request = {
|
|
97
|
+
...imageRefs.length > 0 && { image: imageRefs },
|
|
98
|
+
...size !== void 0 && { size: resolveBytePlusImageSize(size) },
|
|
99
|
+
...resolveBytePlusSequentialImages(model, numberOfImages),
|
|
100
|
+
...modelOptions,
|
|
101
|
+
model,
|
|
102
|
+
prompt: resolved.text
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
logger.request(`activity=image provider=${this.name} model=${model} size=${request.size ?? "default"} refs=${imageRefs.length}`, {
|
|
106
|
+
provider: this.name,
|
|
107
|
+
model
|
|
108
|
+
});
|
|
109
|
+
const fetchImpl = this.clientConfig.fetch ?? fetch;
|
|
110
|
+
const signal = bytePlusTimeoutSignal(this.clientConfig.timeout);
|
|
111
|
+
const response = await fetchImpl(`${this.clientConfig.baseURL}/images/generations`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
...signal && { signal },
|
|
114
|
+
headers: bytePlusArkHeaders(this.clientConfig.apiKey, toHeaderRecord(this.clientConfig.defaultHeaders)),
|
|
115
|
+
body: JSON.stringify(request)
|
|
116
|
+
});
|
|
117
|
+
const body = await readJsonBody(response);
|
|
118
|
+
if (!response.ok) throw bytePlusArkError(response.status, body, "image generation");
|
|
119
|
+
return this.transformResponse(body, logger, numberOfImages);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
logger.errors(`${this.name}.generateImages fatal`, {
|
|
122
|
+
error: toRunErrorPayload(error, `${this.name}.generateImages failed`),
|
|
123
|
+
source: `${this.name}.generateImages`
|
|
124
|
+
});
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
transformResponse(body, logger, numberOfImages) {
|
|
129
|
+
if (typeof body !== "object" || body === null) throw bytePlusArkError(200, body, "image generation returned a non-object body");
|
|
130
|
+
const payload = body;
|
|
131
|
+
const images = [];
|
|
132
|
+
const failures = [];
|
|
133
|
+
let unrecognized = 0;
|
|
134
|
+
for (const item of payload.data ?? []) if (item.b64_json) images.push({ b64Json: item.b64_json });
|
|
135
|
+
else if (item.url) images.push({ url: item.url });
|
|
136
|
+
else if (item.error) failures.push(item.error);
|
|
137
|
+
else unrecognized += 1;
|
|
138
|
+
if (payload.error) failures.push(payload.error);
|
|
139
|
+
if (unrecognized > 0) logger.errors(`${this.name}.generateImages: ${unrecognized} response item(s) matched none of b64_json / url / error — the response shape may have changed.`, {
|
|
140
|
+
source: `${this.name}.generateImages`,
|
|
141
|
+
provider: this.name,
|
|
142
|
+
model: this.model,
|
|
143
|
+
body
|
|
144
|
+
});
|
|
145
|
+
if (images.length === 0) {
|
|
146
|
+
const detail = describeFailures(failures) || (unrecognized > 0 ? `${unrecognized} unrecognized response item(s): ${describeBody(body) ?? ""}` : "");
|
|
147
|
+
throw new Error(`byteplus: image generation returned no images` + (detail ? `: ${detail}` : "."));
|
|
148
|
+
}
|
|
149
|
+
if (failures.length > 0) {
|
|
150
|
+
logger.errors(`${this.name}.generateImages dropped ${failures.length} failed image(s): ${describeFailures(failures)}`, {
|
|
151
|
+
source: `${this.name}.generateImages`,
|
|
152
|
+
provider: this.name,
|
|
153
|
+
model: this.model,
|
|
154
|
+
failures
|
|
155
|
+
});
|
|
156
|
+
logger.warn(`byteplus: ${failures.length} of ${failures.length + images.length} images failed to generate; returning ${images.length}.`, {
|
|
157
|
+
provider: this.name,
|
|
158
|
+
model: this.model
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (numberOfImages !== void 0 && images.length < numberOfImages) logger.warn(`byteplus: requested ${numberOfImages} images, received ${images.length}. Seedream has no exact count — sequential_image_generation.max_images is an upper bound and the model decides how many the prompt warrants.`, {
|
|
162
|
+
provider: this.name,
|
|
163
|
+
model: this.model
|
|
164
|
+
});
|
|
165
|
+
const usage = buildBytePlusImageUsage(payload.usage);
|
|
166
|
+
return {
|
|
167
|
+
id: generateId(this.name),
|
|
168
|
+
model: this.model,
|
|
169
|
+
images,
|
|
170
|
+
...usage ? { usage } : {}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Creates a BytePlus Seedream image adapter with an explicit API key.
|
|
176
|
+
* Type resolution happens here at the call site.
|
|
177
|
+
*
|
|
178
|
+
* @param model - The model name (e.g., 'seedream-4-0-250828')
|
|
179
|
+
* @param apiKey - Your BytePlus Ark API key
|
|
180
|
+
* @param config - Optional additional configuration
|
|
181
|
+
* @returns Configured BytePlus image adapter instance with resolved types
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* const adapter = createBytePlusImage('seedream-5-0-260128', 'ark-...')
|
|
186
|
+
*
|
|
187
|
+
* const result = await generateImage({
|
|
188
|
+
* adapter,
|
|
189
|
+
* prompt: 'A cute baby sea otter',
|
|
190
|
+
* size: '2K',
|
|
191
|
+
* })
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
function createBytePlusImage(model, apiKey, config) {
|
|
195
|
+
return new BytePlusImageAdapter(model, {
|
|
196
|
+
apiKey,
|
|
197
|
+
...config
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Creates a BytePlus Seedream image adapter, reading `ARK_API_KEY` from the
|
|
202
|
+
* environment. Type resolution happens here at the call site.
|
|
203
|
+
*
|
|
204
|
+
* Note that Ark keys are region-isolated: a key issued for `ap-southeast`
|
|
205
|
+
* does not work against the EU host.
|
|
206
|
+
*
|
|
207
|
+
* @param model - The model name (e.g., 'seedream-4-0-250828')
|
|
208
|
+
* @param config - Optional configuration (excluding apiKey, auto-detected)
|
|
209
|
+
* @returns Configured BytePlus image adapter instance with resolved types
|
|
210
|
+
* @throws Error if ARK_API_KEY is not found in environment
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const adapter = byteplusImage('seedream-4-0-250828')
|
|
215
|
+
*
|
|
216
|
+
* const result = await generateImage({
|
|
217
|
+
* adapter,
|
|
218
|
+
* prompt: 'A beautiful sunset over mountains',
|
|
219
|
+
* modelOptions: { watermark: false },
|
|
220
|
+
* })
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
function byteplusImage(model, config) {
|
|
224
|
+
return createBytePlusImage(model, getBytePlusArkApiKeyFromEnv(), config);
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
export { BytePlusImageAdapter, byteplusImage, createBytePlusImage };
|
|
228
|
+
|
|
229
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","names":[],"sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseImageAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { generateId } from '@tanstack/ai-utils'\nimport {\n bytePlusArkError,\n bytePlusArkHeaders,\n bytePlusTimeoutSignal,\n describeBody,\n getBytePlusArkApiKeyFromEnv,\n readJsonBody,\n toHeaderRecord,\n withBytePlusArkDefaults,\n} from '../utils/client'\nimport {\n resolveBytePlusImageSize,\n resolveBytePlusSequentialImages,\n validateBytePlusImagePrompt,\n validateBytePlusReferenceImages,\n} from '../image/image-provider-options'\nimport type {\n GeneratedImage,\n ImageGenerationOptions,\n ImageGenerationResult,\n ImagePart,\n MediaInputMetadata,\n TokenUsage,\n} from '@tanstack/ai'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type {\n BytePlusImageErrorObject,\n BytePlusImageGenerationRequest,\n BytePlusImageGenerationResponse,\n BytePlusImageUsage,\n} from '../image/wire-types'\nimport type {\n BytePlusImageModelInputModalitiesByName,\n BytePlusImageModelProviderOptionsByName,\n BytePlusImageProviderOptions,\n} from '../image/image-provider-options'\nimport type {\n BytePlusImageModel,\n BytePlusImageModelSizeByName,\n} from '../model-meta'\nimport type { BytePlusArkConfig } from '../utils/client'\n\n/**\n * Configuration for the BytePlus Seedream image adapter.\n */\nexport interface BytePlusImageConfig extends BytePlusArkConfig {}\n\n/**\n * Roles Seedream can honour. Every input image is a reference — there is no\n * inpainting mask, control-image or frame channel — so this is an allow-list\n * rather than a deny-list: a role added to the core union later (or a\n * video-oriented one like `start_frame`) fails loudly here instead of being\n * silently flattened into a plain reference.\n */\nconst SUPPORTED_INPUT_ROLES: ReadonlySet<string> = new Set([\n 'reference',\n 'character',\n])\n\n/**\n * Converts a prompt image part to the string Seedream's `image` field takes:\n * URLs pass through (BytePlus fetches them server-side), data sources become\n * data URIs. BytePlus requires the format in `data:image/<format>;base64,` to\n * be lowercase, so the mime type is lowercased on the way out.\n */\nfunction imagePartToImageRef(part: ImagePart<MediaInputMetadata>): string {\n const { source } = part\n if (source.type === 'url') return source.value\n if (source.value.startsWith('data:')) return source.value\n return `data:${source.mimeType.toLowerCase()};base64,${source.value}`\n}\n\n/**\n * Renders provider error objects as `code: message` pairs for a log line or\n * an error message.\n */\nfunction describeFailures(\n failures: ReadonlyArray<BytePlusImageErrorObject>,\n): string {\n return failures\n .map((failure) =>\n [failure.code, failure.message].filter(Boolean).join(': '),\n )\n .filter((text) => text.length > 0)\n .join('; ')\n}\n\n/**\n * Maps Seedream's usage block onto `TokenUsage`.\n *\n * BytePlus bills per generated image and does not count input tokens, so\n * `promptTokens` is always 0 and `generated_images` is surfaced as\n * `unitsBilled` — the count the price is applied to.\n */\nfunction buildBytePlusImageUsage(\n usage: BytePlusImageUsage | undefined,\n): TokenUsage | undefined {\n if (!usage) return undefined\n\n const completionTokens = usage.output_tokens ?? 0\n return {\n promptTokens: 0,\n completionTokens,\n totalTokens: usage.total_tokens ?? completionTokens,\n ...(usage.generated_images !== undefined && {\n unitsBilled: usage.generated_images,\n }),\n }\n}\n\n/**\n * BytePlus Seedream image generation adapter.\n *\n * Drives Ark's `POST /images/generations` endpoint directly rather than\n * through the OpenAI SDK: the endpoint takes size tokens (`2K`) as well as\n * pixel sizes, has no `n` parameter, and carries reference images for editing\n * in the generation request instead of a separate edits endpoint.\n *\n * Features:\n * - Text-to-image and image-conditioned generation (editing, multi-reference)\n * from a single call, with per-model reference-count limits enforced.\n * - Size validation across both accepted forms.\n * - `numberOfImages` mapped onto Seedream's group-image mode.\n *\n * @example\n * ```typescript\n * const adapter = byteplusImage('seedream-4-0-250828')\n * const result = await generateImage({\n * adapter,\n * prompt: 'A guitar in a sunlit workshop',\n * size: '2K',\n * modelOptions: { watermark: false },\n * })\n * ```\n */\nexport class BytePlusImageAdapter<\n TModel extends BytePlusImageModel,\n> extends BaseImageAdapter<\n TModel,\n BytePlusImageProviderOptions,\n BytePlusImageModelProviderOptionsByName,\n BytePlusImageModelSizeByName,\n BytePlusImageModelInputModalitiesByName\n> {\n override readonly kind = 'image' as const\n readonly name = 'byteplus' as const\n\n /** Config with the Ark base URL resolved and trailing slashes trimmed. */\n private readonly clientConfig: Omit<BytePlusImageConfig, 'baseURL'> & {\n baseURL: string\n }\n\n constructor(model: TModel, config: BytePlusImageConfig) {\n super(model, {})\n this.clientConfig = withBytePlusArkDefaults(config)\n }\n\n async generateImages(\n options: ImageGenerationOptions<\n BytePlusImageProviderOptions,\n BytePlusImageModelSizeByName[TModel]\n >,\n ): Promise<ImageGenerationResult> {\n const { numberOfImages, size, modelOptions, logger } = options\n const model = this.model\n\n const resolved = resolveMediaPrompt(options.prompt)\n\n if (resolved.videos.length > 0 || resolved.audios.length > 0) {\n throw new Error(\n `byteplus.generateImages does not support video / audio prompt parts on model ${model}.`,\n )\n }\n\n const unsupportedRole = resolved.images.find(\n (part) =>\n part.metadata?.role !== undefined &&\n !SUPPORTED_INPUT_ROLES.has(part.metadata.role),\n )\n if (unsupportedRole) {\n throw new Error(\n `byteplus: Seedream has no ${unsupportedRole.metadata?.role} input; ` +\n `it accepts reference images only (${[...SUPPORTED_INPUT_ROLES].join(', ')}).`,\n )\n }\n\n validateBytePlusImagePrompt(model, resolved.text)\n validateBytePlusReferenceImages(model, resolved.images.length)\n\n const imageRefs = resolved.images.map(imagePartToImageRef)\n const request: BytePlusImageGenerationRequest = {\n ...(imageRefs.length > 0 && { image: imageRefs }),\n ...(size !== undefined && {\n size: resolveBytePlusImageSize(size),\n }),\n ...resolveBytePlusSequentialImages(model, numberOfImages),\n // Explicit provider options win over the values derived from the\n // generic options above (e.g. forcing `sequential_image_generation`).\n ...modelOptions,\n model,\n prompt: resolved.text,\n }\n\n try {\n logger.request(\n `activity=image provider=${this.name} model=${model} size=${request.size ?? 'default'} refs=${imageRefs.length}`,\n { provider: this.name, model },\n )\n\n const fetchImpl = this.clientConfig.fetch ?? fetch\n const signal = bytePlusTimeoutSignal(this.clientConfig.timeout)\n const response = await fetchImpl(\n `${this.clientConfig.baseURL}/images/generations`,\n {\n method: 'POST',\n ...(signal && { signal }),\n headers: bytePlusArkHeaders(\n this.clientConfig.apiKey,\n toHeaderRecord(this.clientConfig.defaultHeaders),\n ),\n body: JSON.stringify(request),\n },\n )\n\n const body = await readJsonBody(response)\n if (!response.ok) {\n throw bytePlusArkError(response.status, body, 'image generation')\n }\n\n return this.transformResponse(body, logger, numberOfImages)\n } catch (error: unknown) {\n logger.errors(`${this.name}.generateImages fatal`, {\n error: toRunErrorPayload(error, `${this.name}.generateImages failed`),\n source: `${this.name}.generateImages`,\n })\n throw error\n }\n }\n\n private transformResponse(\n body: unknown,\n logger: InternalLogger,\n numberOfImages: number | undefined,\n ): ImageGenerationResult {\n // Shape pinned by a live seedream-4-0-250828 call and the Ark OpenAPI\n // document. Validate rather than cast: `readJsonBody` returns `undefined`\n // for an empty body and the raw text for a non-JSON one (an HTML error\n // page from a proxy in front of the API), and casting either would report\n // \"returned no images\" with the body — the only evidence of what actually\n // happened — thrown away.\n if (typeof body !== 'object' || body === null) {\n throw bytePlusArkError(\n 200,\n body,\n 'image generation returned a non-object body',\n )\n }\n const payload = body as BytePlusImageGenerationResponse\n\n const images: Array<GeneratedImage> = []\n const failures: Array<BytePlusImageErrorObject> = []\n // Items matching none of the three known shapes. Ark's OpenAPI document\n // describes a second, nested item form, so this is a live possibility\n // rather than a defensive branch — and an unrecognized item that is\n // neither counted nor reported turns provider drift into an\n // \"returned no images\" with no attribution at all.\n let unrecognized = 0\n for (const item of payload.data ?? []) {\n if (item.b64_json) {\n images.push({ b64Json: item.b64_json })\n } else if (item.url) {\n images.push({ url: item.url })\n } else if (item.error) {\n // Group-image mode reports per-image failures alongside successes;\n // dropping them silently would make a short result look complete.\n failures.push(item.error)\n } else {\n unrecognized += 1\n }\n }\n if (payload.error) failures.push(payload.error)\n\n if (unrecognized > 0) {\n logger.errors(\n `${this.name}.generateImages: ${unrecognized} response item(s) matched ` +\n `none of b64_json / url / error — the response shape may have changed.`,\n {\n source: `${this.name}.generateImages`,\n provider: this.name,\n model: this.model,\n body,\n },\n )\n }\n\n if (images.length === 0) {\n const detail =\n describeFailures(failures) ||\n (unrecognized > 0\n ? `${unrecognized} unrecognized response item(s): ${describeBody(body) ?? ''}`\n : '')\n throw new Error(\n `byteplus: image generation returned no images` +\n (detail ? `: ${detail}` : '.'),\n )\n }\n\n if (failures.length > 0) {\n logger.errors(\n `${this.name}.generateImages dropped ${failures.length} failed image(s): ${describeFailures(failures)}`,\n {\n source: `${this.name}.generateImages`,\n provider: this.name,\n model: this.model,\n failures,\n },\n )\n // The caller asked for a group and is getting a short array. Warn\n // unconditionally: the `numberOfImages` warning below only fires when\n // the count was set explicitly, so a partial failure would otherwise\n // return successfully with no signal at all.\n logger.warn(\n `byteplus: ${failures.length} of ${failures.length + images.length} ` +\n `images failed to generate; returning ${images.length}.`,\n { provider: this.name, model: this.model },\n )\n }\n\n if (numberOfImages !== undefined && images.length < numberOfImages) {\n logger.warn(\n `byteplus: requested ${numberOfImages} images, received ${images.length}. ` +\n `Seedream has no exact count — sequential_image_generation.max_images is ` +\n `an upper bound and the model decides how many the prompt warrants.`,\n { provider: this.name, model: this.model },\n )\n }\n\n const usage = buildBytePlusImageUsage(payload.usage)\n\n return {\n id: generateId(this.name),\n model: this.model,\n images,\n ...(usage ? { usage } : {}),\n }\n }\n}\n\n/**\n * Creates a BytePlus Seedream image adapter with an explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'seedream-4-0-250828')\n * @param apiKey - Your BytePlus Ark API key\n * @param config - Optional additional configuration\n * @returns Configured BytePlus image adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createBytePlusImage('seedream-5-0-260128', 'ark-...')\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A cute baby sea otter',\n * size: '2K',\n * })\n * ```\n */\nexport function createBytePlusImage<TModel extends BytePlusImageModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<BytePlusImageConfig, 'apiKey'>,\n): BytePlusImageAdapter<TModel> {\n return new BytePlusImageAdapter(model, { apiKey, ...config })\n}\n\n/**\n * Creates a BytePlus Seedream image adapter, reading `ARK_API_KEY` from the\n * environment. Type resolution happens here at the call site.\n *\n * Note that Ark keys are region-isolated: a key issued for `ap-southeast`\n * does not work against the EU host.\n *\n * @param model - The model name (e.g., 'seedream-4-0-250828')\n * @param config - Optional configuration (excluding apiKey, auto-detected)\n * @returns Configured BytePlus image adapter instance with resolved types\n * @throws Error if ARK_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = byteplusImage('seedream-4-0-250828')\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A beautiful sunset over mountains',\n * modelOptions: { watermark: false },\n * })\n * ```\n */\nexport function byteplusImage<TModel extends BytePlusImageModel>(\n model: TModel,\n config?: Omit<BytePlusImageConfig, 'apiKey'>,\n): BytePlusImageAdapter<TModel> {\n return createBytePlusImage(model, getBytePlusArkApiKeyFromEnv(), config)\n}\n"],"mappings":"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"}
|