dsh-subscriptions 0.1.5 → 0.2.2
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/README.md +212 -154
- package/lib/auth/claude-code-creds.js +31 -9
- package/lib/auth/device-flow.js +16 -9
- package/lib/auth/oauth-flow.js +13 -10
- package/lib/auth/rpc.d.ts +73 -2
- package/lib/auth/rpc.js +124 -23
- package/lib/auth/store.js +80 -31
- package/lib/client/CursorCard.d.ts +1 -1
- package/lib/client/CursorCard.js +50 -16
- package/lib/client/ExternalUsageCards.d.ts +1 -1
- package/lib/client/ExternalUsageCards.js +37 -16
- package/lib/client/ImageGallery.d.ts +2 -2
- package/lib/client/ImageGallery.js +92 -33
- package/lib/client/ImageGenerateToolview.js +50 -14
- package/lib/client/OllamaCard.d.ts +13 -0
- package/lib/client/OllamaCard.js +94 -0
- package/lib/client/OllamaSettingsManager.d.ts +24 -0
- package/lib/client/OllamaSettingsManager.js +329 -0
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +164 -49
- package/lib/client/ProviderModelEditor.js +109 -26
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +257 -46
- package/lib/client/SubscriptionUsageBadge.d.ts +5 -11
- package/lib/client/SubscriptionUsageBadge.js +301 -112
- package/lib/client/SubscriptionsSection.d.ts +2 -0
- package/lib/client/SubscriptionsSection.js +259 -85
- package/lib/client/UsageBadgeDisplaySetting.js +35 -12
- package/lib/client/UsageMeter.d.ts +1 -1
- package/lib/client/UsageMeter.js +69 -27
- package/lib/client/VideoGenerateToolview.js +69 -17
- package/lib/client/account-preferences.d.ts +8 -0
- package/lib/client/account-preferences.js +20 -5
- package/lib/client/host-contract.d.ts +11 -2
- package/lib/client/host-contract.js +20 -3
- package/lib/client/index.d.ts +5 -5
- package/lib/client/index.js +34 -12
- package/lib/client/locales.d.ts +78 -0
- package/lib/client/locales.js +121 -34
- package/lib/client/ollama-draft.d.ts +229 -0
- package/lib/client/ollama-draft.js +255 -0
- package/lib/client/reset-action.js +6 -4
- package/lib/client/reset-credits-view.d.ts +1 -1
- package/lib/client/reset-credits-view.js +66 -17
- package/lib/client/subscription-card-styles.js +94 -24
- package/lib/client/usage-color-preferences.js +4 -2
- package/lib/client/usage-pace.d.ts +7 -0
- package/lib/client/usage-pace.js +43 -10
- package/lib/client.js +1647 -266
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +28 -3
- package/lib/index.js +9329 -8217
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +14 -1
- package/lib/provider-settings.js +64 -20
- package/lib/providers/account-preferences.js +51 -22
- package/lib/providers/accounts.js +15 -9
- package/lib/providers/antigravity-oauth-client.js +2 -2
- package/lib/providers/antigravity.js +141 -68
- package/lib/providers/catalog-store.js +120 -41
- package/lib/providers/claude-cli-version.js +3 -3
- package/lib/providers/claude.d.ts +23 -3
- package/lib/providers/claude.js +167 -92
- package/lib/providers/codex-client-version.js +3 -3
- package/lib/providers/codex-search.js +40 -13
- package/lib/providers/codex.js +196 -112
- package/lib/providers/common.d.ts +14 -0
- package/lib/providers/common.js +118 -38
- package/lib/providers/copilot.js +143 -82
- package/lib/providers/cursor-adapter.js +42 -18
- package/lib/providers/cursor-auth.d.ts +1 -1
- package/lib/providers/cursor-auth.js +69 -24
- package/lib/providers/cursor-model-settings.js +14 -5
- package/lib/providers/cursor-usage.js +27 -13
- package/lib/providers/external-usage-controller.d.ts +9 -2
- package/lib/providers/external-usage-controller.js +30 -7
- package/lib/providers/external-usage.d.ts +8 -0
- package/lib/providers/external-usage.js +140 -20
- package/lib/providers/grok.js +152 -89
- package/lib/providers/image-pool.js +29 -11
- package/lib/providers/minimax-usage.js +31 -11
- package/lib/providers/npm-cli-version.js +10 -3
- package/lib/providers/ollama-settings.d.ts +90 -0
- package/lib/providers/ollama-settings.js +213 -0
- package/lib/providers/ollama-web.d.ts +46 -0
- package/lib/providers/ollama-web.js +231 -0
- package/lib/providers/ollama.d.ts +107 -0
- package/lib/providers/ollama.js +608 -0
- package/lib/providers/pool-family.js +11 -3
- package/lib/providers/pool-health.js +28 -8
- package/lib/providers/pool-usage.js +16 -9
- package/lib/providers/pool.d.ts +8 -0
- package/lib/providers/pool.js +81 -42
- package/lib/providers/rate-limit.js +8 -2
- package/lib/providers/replay.js +40 -17
- package/lib/providers/reset-redemption.js +18 -6
- package/lib/tools/image-generate.js +123 -65
- package/lib/tools/registration.js +5 -2
- package/lib/tools/video-generate.js +51 -31
- package/lib/tools/x-search.js +50 -22
- package/lib/translate/anthropic.js +85 -37
- package/lib/translate/antigravity-schema.js +71 -28
- package/lib/translate/antigravity-thinking.js +22 -9
- package/lib/translate/antigravity.js +141 -50
- package/lib/translate/chat-completions.js +67 -23
- package/lib/translate/resolved.js +55 -21
- package/lib/translate/responses.js +69 -29
- package/lib/translate/sse.js +4 -1
- package/package.json +26 -4
|
@@ -47,18 +47,20 @@ async function resolveReferenceImages(refs, attachments, signal) {
|
|
|
47
47
|
const urls = [];
|
|
48
48
|
for (const ref of refs) {
|
|
49
49
|
signal.throwIfAborted();
|
|
50
|
-
if (ref === null ||
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
if (ref === null ||
|
|
51
|
+
typeof ref !== 'object' ||
|
|
52
|
+
typeof ref.attachmentId !== 'string' ||
|
|
53
|
+
!/^sha256:[a-f0-9]{64}$/.test(ref.attachmentId) ||
|
|
54
|
+
!['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(ref.mediaType) ||
|
|
55
|
+
![ref.bytes, ref.width, ref.height].every((value) => Number.isSafeInteger(value) && value > 0)) {
|
|
54
56
|
throw new Error('image_generate: invalid referenceImages entry; copy a complete image reference or call read_image for a local file. Do not omit references to retry an edit.');
|
|
55
57
|
}
|
|
56
58
|
if (seen.has(ref.attachmentId))
|
|
57
59
|
throw new Error('image_generate: referenceImages contains duplicate images');
|
|
58
60
|
seen.add(ref.attachmentId);
|
|
59
|
-
if (refs.length > attachments.imageLimits.maxImagesPerMessage
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
if (refs.length > attachments.imageLimits.maxImagesPerMessage ||
|
|
62
|
+
ref.bytes > attachments.imageLimits.maxImageBytes ||
|
|
63
|
+
totalBytes + ref.bytes > attachments.imageLimits.maxMessageImageBytes) {
|
|
62
64
|
throw new Error('image_generate: referenceImages exceed DSH image limits');
|
|
63
65
|
}
|
|
64
66
|
const stored = await attachments.readImage(imageRefFromValue(ref), signal);
|
|
@@ -81,8 +83,8 @@ export function buildImageGenerateBody(args) {
|
|
|
81
83
|
return {
|
|
82
84
|
prompt,
|
|
83
85
|
model: IMAGE_GENERATE_MODEL,
|
|
84
|
-
...args.size === undefined ? {} : { size: args.size },
|
|
85
|
-
...args.quality === undefined ? {} : { quality: args.quality },
|
|
86
|
+
...(args.size === undefined ? {} : { size: args.size }),
|
|
87
|
+
...(args.quality === undefined ? {} : { quality: args.quality }),
|
|
86
88
|
};
|
|
87
89
|
}
|
|
88
90
|
/** The codex `size` values mapped onto grok aspect ratios. */
|
|
@@ -90,7 +92,7 @@ const GROK_ASPECT_RATIOS = {
|
|
|
90
92
|
'1024x1024': '1:1',
|
|
91
93
|
'1024x1536': '2:3',
|
|
92
94
|
'1536x1024': '3:2',
|
|
93
|
-
|
|
95
|
+
auto: 'auto',
|
|
94
96
|
};
|
|
95
97
|
/**
|
|
96
98
|
* Assemble the grok request body from the same tool arguments: `size` maps
|
|
@@ -101,15 +103,19 @@ export function buildGrokImageGenerateBody(args) {
|
|
|
101
103
|
const prompt = args.prompt.trim();
|
|
102
104
|
if (prompt.length === 0)
|
|
103
105
|
throw new Error('image_generate: prompt must be a non-empty string');
|
|
104
|
-
const quality = args.quality === 'low'
|
|
105
|
-
|
|
106
|
+
const quality = args.quality === 'low'
|
|
107
|
+
? 'low'
|
|
108
|
+
: args.quality === 'medium' || args.quality === 'high'
|
|
109
|
+
? 'medium'
|
|
106
110
|
: undefined;
|
|
107
111
|
return {
|
|
108
112
|
prompt,
|
|
109
113
|
model: GROK_IMAGE_GENERATE_MODEL,
|
|
110
114
|
response_format: 'b64_json',
|
|
111
|
-
...args.size === undefined
|
|
112
|
-
|
|
115
|
+
...(args.size === undefined
|
|
116
|
+
? {}
|
|
117
|
+
: { aspect_ratio: GROK_ASPECT_RATIOS[args.size] }),
|
|
118
|
+
...(quality === undefined ? {} : { quality }),
|
|
113
119
|
};
|
|
114
120
|
}
|
|
115
121
|
/**
|
|
@@ -117,7 +123,9 @@ export function buildGrokImageGenerateBody(args) {
|
|
|
117
123
|
* payload carries no usable `b64_json` entries.
|
|
118
124
|
*/
|
|
119
125
|
export function parseImageGenerateResponse(payload) {
|
|
120
|
-
const body = typeof payload === 'object' && payload !== null
|
|
126
|
+
const body = typeof payload === 'object' && payload !== null
|
|
127
|
+
? payload
|
|
128
|
+
: {};
|
|
121
129
|
const entries = Array.isArray(body.data) ? body.data : [];
|
|
122
130
|
const images = [];
|
|
123
131
|
for (const entry of entries) {
|
|
@@ -128,9 +136,10 @@ export function parseImageGenerateResponse(payload) {
|
|
|
128
136
|
continue;
|
|
129
137
|
images.push({
|
|
130
138
|
data: Buffer.from(record.b64_json, 'base64'),
|
|
131
|
-
...typeof record.revised_prompt === 'string' &&
|
|
139
|
+
...(typeof record.revised_prompt === 'string' &&
|
|
140
|
+
record.revised_prompt.length > 0
|
|
132
141
|
? { revisedPrompt: record.revised_prompt }
|
|
133
|
-
: {},
|
|
142
|
+
: {}),
|
|
134
143
|
});
|
|
135
144
|
}
|
|
136
145
|
if (images.length === 0)
|
|
@@ -147,9 +156,14 @@ export function imagesDirectory() {
|
|
|
147
156
|
* defaults to PNG, matching the historical behavior.
|
|
148
157
|
*/
|
|
149
158
|
export function sniffImageMediaType(data) {
|
|
150
|
-
if (data.length >= 3 &&
|
|
159
|
+
if (data.length >= 3 &&
|
|
160
|
+
data[0] === 0xff &&
|
|
161
|
+
data[1] === 0xd8 &&
|
|
162
|
+
data[2] === 0xff)
|
|
151
163
|
return 'image/jpeg';
|
|
152
|
-
if (data.length >= 12 &&
|
|
164
|
+
if (data.length >= 12 &&
|
|
165
|
+
data.toString('latin1', 0, 4) === 'RIFF' &&
|
|
166
|
+
data.toString('latin1', 8, 12) === 'WEBP') {
|
|
153
167
|
return 'image/webp';
|
|
154
168
|
}
|
|
155
169
|
return 'image/png';
|
|
@@ -200,22 +214,31 @@ function imageRefFromValue(image) {
|
|
|
200
214
|
bytes: image.bytes,
|
|
201
215
|
width: image.width,
|
|
202
216
|
height: image.height,
|
|
203
|
-
...image.name === undefined ? {} : { name: image.name },
|
|
204
|
-
...image.originalDimensions === undefined
|
|
217
|
+
...(image.name === undefined ? {} : { name: image.name }),
|
|
218
|
+
...(image.originalDimensions === undefined
|
|
219
|
+
? {}
|
|
220
|
+
: { originalDimensions: image.originalDimensions }),
|
|
205
221
|
};
|
|
206
222
|
}
|
|
207
223
|
/** Project the canonical value into the model-facing text + image blocks. */
|
|
208
224
|
function imageGenerateContent(value) {
|
|
209
225
|
return [
|
|
210
226
|
imageGenerateText(value),
|
|
211
|
-
...(value.images ?? []).map(image => ({
|
|
227
|
+
...(value.images ?? []).map((image) => ({
|
|
228
|
+
type: 'image',
|
|
229
|
+
attachment: imageRefFromValue(image),
|
|
230
|
+
})),
|
|
212
231
|
];
|
|
213
232
|
}
|
|
214
233
|
/** The text summary of one generation, shared by the model content and the UI card. */
|
|
215
234
|
function imageGenerateText(value) {
|
|
216
|
-
const text = `Saved ${value.paths.length} image(s):\n${value.paths.map(path => `- ${path}`).join('\n')}`
|
|
217
|
-
|
|
218
|
-
|
|
235
|
+
const text = `Saved ${value.paths.length} image(s):\n${value.paths.map((path) => `- ${path}`).join('\n')}` +
|
|
236
|
+
(value.images?.length
|
|
237
|
+
? `\n\nImage references (for image_generate.referenceImages): ${JSON.stringify(value.images)}`
|
|
238
|
+
: '') +
|
|
239
|
+
(value.revisedPrompt === undefined
|
|
240
|
+
? ''
|
|
241
|
+
: `\n\nRevised prompt: ${value.revisedPrompt}`);
|
|
219
242
|
return { type: 'text', text };
|
|
220
243
|
}
|
|
221
244
|
/**
|
|
@@ -227,17 +250,17 @@ export function createImageGenerateTool(options) {
|
|
|
227
250
|
const imagePool = options.imagePool ?? new ImageAccountPool();
|
|
228
251
|
return defineTool({
|
|
229
252
|
name: 'image_generate',
|
|
230
|
-
description: 'Generate an image with the ChatGPT subscription (gpt-image-2) or the Grok '
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
253
|
+
description: 'Generate an image with the ChatGPT subscription (gpt-image-2) or the Grok ' +
|
|
254
|
+
'subscription (grok-imagine-image-2.0) and save it as an image file. The `provider` ' +
|
|
255
|
+
'parameter picks the preferred provider (default gpt); when the preferred one is logged ' +
|
|
256
|
+
'out the other serves as fallback. ' +
|
|
257
|
+
'Within the selected provider, image requests use available subscription accounts; ' +
|
|
258
|
+
'quota or authentication rejection can switch accounts without changing providers. ' +
|
|
259
|
+
'Returns the saved file paths; on image-capable models the image itself is attached. ' +
|
|
260
|
+
'To edit or use existing images as references, pass referenceImages copied from the image reference text ' +
|
|
261
|
+
'or structured tool results (read_image.image or image_generate.images). Select only the images the user ' +
|
|
262
|
+
'intends, in prompt order. For local files, call read_image first. Omit referenceImages only for a new image. ' +
|
|
263
|
+
'If an edit reference fails, fix it and retry; never omit it to substitute text-to-image generation.',
|
|
241
264
|
parameters: {
|
|
242
265
|
referenceImages: {
|
|
243
266
|
type: 'array',
|
|
@@ -247,7 +270,11 @@ export function createImageGenerateTool(options) {
|
|
|
247
270
|
additionalProperties: false,
|
|
248
271
|
properties: {
|
|
249
272
|
attachmentId: { type: 'string', required: true },
|
|
250
|
-
mediaType: {
|
|
273
|
+
mediaType: {
|
|
274
|
+
type: 'string',
|
|
275
|
+
enum: ['image/png', 'image/jpeg', 'image/webp', 'image/gif'],
|
|
276
|
+
required: true,
|
|
277
|
+
},
|
|
251
278
|
bytes: { type: 'integer', required: true },
|
|
252
279
|
width: { type: 'integer', required: true },
|
|
253
280
|
height: { type: 'integer', required: true },
|
|
@@ -263,7 +290,11 @@ export function createImageGenerateTool(options) {
|
|
|
263
290
|
},
|
|
264
291
|
},
|
|
265
292
|
},
|
|
266
|
-
prompt: {
|
|
293
|
+
prompt: {
|
|
294
|
+
type: 'string',
|
|
295
|
+
required: true,
|
|
296
|
+
description: 'What the image should show.',
|
|
297
|
+
},
|
|
267
298
|
size: {
|
|
268
299
|
type: 'string',
|
|
269
300
|
enum: ['1024x1024', '1024x1536', '1536x1024', 'auto'],
|
|
@@ -292,7 +323,11 @@ export function createImageGenerateTool(options) {
|
|
|
292
323
|
additionalProperties: false,
|
|
293
324
|
properties: {
|
|
294
325
|
attachmentId: { type: 'string', required: true },
|
|
295
|
-
mediaType: {
|
|
326
|
+
mediaType: {
|
|
327
|
+
type: 'string',
|
|
328
|
+
enum: ['image/png', 'image/jpeg', 'image/webp', 'image/gif'],
|
|
329
|
+
required: true,
|
|
330
|
+
},
|
|
296
331
|
bytes: { type: 'integer', required: true },
|
|
297
332
|
width: { type: 'integer', required: true },
|
|
298
333
|
height: { type: 'integer', required: true },
|
|
@@ -306,7 +341,7 @@ export function createImageGenerateTool(options) {
|
|
|
306
341
|
},
|
|
307
342
|
render: (_args, value) => imageGenerateContent(value),
|
|
308
343
|
},
|
|
309
|
-
presentCall: args => ({
|
|
344
|
+
presentCall: (args) => ({
|
|
310
345
|
card: 'generic',
|
|
311
346
|
title: `image_generate${args.referenceImages === undefined ? '' : ` (edit, ${args.referenceImages.length} images)`}: ${truncate(args.prompt)}`,
|
|
312
347
|
}),
|
|
@@ -315,7 +350,7 @@ export function createImageGenerateTool(options) {
|
|
|
315
350
|
// image block stays model-facing in the render output.
|
|
316
351
|
presentResult: (_args, result) => ({
|
|
317
352
|
card: 'generic',
|
|
318
|
-
content: result.content.filter(block => block.type === 'text'),
|
|
353
|
+
content: result.content.filter((block) => block.type === 'text'),
|
|
319
354
|
}),
|
|
320
355
|
async execute(args, exec) {
|
|
321
356
|
const fetchFn = options.fetchFn ?? hostFetch;
|
|
@@ -330,27 +365,38 @@ export function createImageGenerateTool(options) {
|
|
|
330
365
|
const createdAt = exec.agent?.session.header?.createdAt;
|
|
331
366
|
const codexEnabled = options.providerEnabled?.('codex', createdAt) !== false;
|
|
332
367
|
const grokEnabled = options.providerEnabled?.('grok', createdAt) !== false;
|
|
333
|
-
const codexReady = codexEnabled &&
|
|
334
|
-
|
|
368
|
+
const codexReady = codexEnabled &&
|
|
369
|
+
options.codexTokens !== undefined &&
|
|
370
|
+
(await options.codexTokens.list()).length > 0;
|
|
371
|
+
const grokReady = grokEnabled &&
|
|
372
|
+
options.grokTokens !== undefined &&
|
|
373
|
+
(await options.grokTokens.list()).length > 0;
|
|
335
374
|
const useGrok = preferGrok ? grokReady : grokReady && !codexReady;
|
|
336
375
|
const useCodex = !useGrok && codexReady;
|
|
337
376
|
let response;
|
|
338
377
|
if (useCodex && options.codexTokens !== undefined) {
|
|
339
378
|
response = await imagePool.request({
|
|
340
|
-
provider: 'codex',
|
|
341
|
-
|
|
342
|
-
|
|
379
|
+
provider: 'codex',
|
|
380
|
+
tokens: options.codexTokens,
|
|
381
|
+
signal: exec.signal,
|
|
382
|
+
owner: exec.agent?.session,
|
|
383
|
+
rateLimitReset: codexRateLimitReset,
|
|
384
|
+
send: (session) => fetchFn(references === undefined ? IMAGE_GENERATE_URL : IMAGE_EDIT_URL, {
|
|
343
385
|
method: 'POST',
|
|
344
386
|
headers: {
|
|
345
|
-
|
|
387
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
346
388
|
'chatgpt-account-id': session.accountId,
|
|
347
|
-
|
|
389
|
+
originator: 'codex_cli_rs',
|
|
348
390
|
'content-type': 'application/json',
|
|
349
|
-
|
|
391
|
+
accept: 'application/json',
|
|
350
392
|
},
|
|
351
393
|
body: JSON.stringify({
|
|
352
394
|
...buildImageGenerateBody(args),
|
|
353
|
-
...references === undefined
|
|
395
|
+
...(references === undefined
|
|
396
|
+
? {}
|
|
397
|
+
: {
|
|
398
|
+
images: references.map((image_url) => ({ image_url })),
|
|
399
|
+
}),
|
|
354
400
|
}),
|
|
355
401
|
signal: exec.signal,
|
|
356
402
|
}),
|
|
@@ -358,20 +404,32 @@ export function createImageGenerateTool(options) {
|
|
|
358
404
|
}
|
|
359
405
|
else if (useGrok && options.grokTokens !== undefined) {
|
|
360
406
|
response = await imagePool.request({
|
|
361
|
-
provider: 'grok',
|
|
362
|
-
|
|
363
|
-
|
|
407
|
+
provider: 'grok',
|
|
408
|
+
tokens: options.grokTokens,
|
|
409
|
+
signal: exec.signal,
|
|
410
|
+
owner: exec.agent?.session,
|
|
411
|
+
rateLimitReset: grokRateLimitReset,
|
|
412
|
+
send: (session) => fetchFn(references === undefined
|
|
413
|
+
? GROK_IMAGE_GENERATE_URL
|
|
414
|
+
: GROK_IMAGE_EDIT_URL, {
|
|
364
415
|
method: 'POST',
|
|
365
416
|
headers: {
|
|
366
|
-
|
|
417
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
367
418
|
'content-type': 'application/json',
|
|
368
|
-
|
|
419
|
+
accept: 'application/json',
|
|
369
420
|
},
|
|
370
421
|
body: JSON.stringify({
|
|
371
422
|
...buildGrokImageGenerateBody(args),
|
|
372
|
-
...references === undefined
|
|
373
|
-
? {
|
|
374
|
-
:
|
|
423
|
+
...(references === undefined
|
|
424
|
+
? {}
|
|
425
|
+
: references.length === 1
|
|
426
|
+
? { image: { type: 'image_url', url: references[0] } }
|
|
427
|
+
: {
|
|
428
|
+
images: references.map((url) => ({
|
|
429
|
+
type: 'image_url',
|
|
430
|
+
url,
|
|
431
|
+
})),
|
|
432
|
+
}),
|
|
375
433
|
}),
|
|
376
434
|
signal: exec.signal,
|
|
377
435
|
}),
|
|
@@ -382,7 +440,7 @@ export function createImageGenerateTool(options) {
|
|
|
382
440
|
throw new Error('image_generate: disabled for this session');
|
|
383
441
|
const codex = codexEnabled ? options.codexTokens : undefined;
|
|
384
442
|
const grok = grokEnabled ? options.grokTokens : undefined;
|
|
385
|
-
const manager = preferGrok ? grok ?? codex : codex ?? grok;
|
|
443
|
+
const manager = preferGrok ? (grok ?? codex) : (codex ?? grok);
|
|
386
444
|
if (manager === undefined)
|
|
387
445
|
throw new Error('image_generate: no image provider is configured');
|
|
388
446
|
await manager.session(); // logged out: throws the provider's log-in hint
|
|
@@ -404,8 +462,8 @@ export function createImageGenerateTool(options) {
|
|
|
404
462
|
// only enter session history on a route that declares image input.
|
|
405
463
|
// Either condition failing degrades to the text-only result.
|
|
406
464
|
const attachments = options.resolveAttachments?.();
|
|
407
|
-
const imageCapable = attachments !== undefined
|
|
408
|
-
|
|
465
|
+
const imageCapable = attachments !== undefined &&
|
|
466
|
+
(await routeDeclaresImageInput(options.resolveLlm, exec));
|
|
409
467
|
const refs = [];
|
|
410
468
|
if (attachments !== undefined && imageCapable) {
|
|
411
469
|
for (const [index, image] of images.entries()) {
|
|
@@ -420,15 +478,15 @@ export function createImageGenerateTool(options) {
|
|
|
420
478
|
bytes: ref.bytes,
|
|
421
479
|
width: ref.width,
|
|
422
480
|
height: ref.height,
|
|
423
|
-
...ref.name === undefined ? {} : { name: ref.name },
|
|
481
|
+
...(ref.name === undefined ? {} : { name: ref.name }),
|
|
424
482
|
});
|
|
425
483
|
}
|
|
426
484
|
}
|
|
427
|
-
const revisedPrompt = images.find(image => image.revisedPrompt !== undefined)?.revisedPrompt;
|
|
485
|
+
const revisedPrompt = images.find((image) => image.revisedPrompt !== undefined)?.revisedPrompt;
|
|
428
486
|
const value = {
|
|
429
487
|
paths,
|
|
430
|
-
...refs.length > 0 ? { images: refs } : {},
|
|
431
|
-
...revisedPrompt === undefined ? {} : { revisedPrompt },
|
|
488
|
+
...(refs.length > 0 ? { images: refs } : {}),
|
|
489
|
+
...(revisedPrompt === undefined ? {} : { revisedPrompt }),
|
|
432
490
|
};
|
|
433
491
|
// Nested (Code Mode) dispatches need no defer here: the harness's code
|
|
434
492
|
// mode already defers any image-bearing sub-result as a user message, so
|
|
@@ -5,7 +5,7 @@ export const TOOL_ALIASES = {
|
|
|
5
5
|
image_generate: 'dsh_subscriptions_image_generate',
|
|
6
6
|
};
|
|
7
7
|
/** Register a tool under its canonical name, then a plugin-scoped alias. */
|
|
8
|
-
export function registerWithAlias(registry, definition, warn = message => console.warn(message)) {
|
|
8
|
+
export function registerWithAlias(registry, definition, warn = (message) => console.warn(message)) {
|
|
9
9
|
try {
|
|
10
10
|
return { name: definition.name, dispose: registry.register(definition) };
|
|
11
11
|
}
|
|
@@ -14,7 +14,10 @@ export function registerWithAlias(registry, definition, warn = message => consol
|
|
|
14
14
|
if (alias === undefined)
|
|
15
15
|
throw error;
|
|
16
16
|
try {
|
|
17
|
-
return {
|
|
17
|
+
return {
|
|
18
|
+
name: alias,
|
|
19
|
+
dispose: registry.register({ ...definition, name: alias }),
|
|
20
|
+
};
|
|
18
21
|
}
|
|
19
22
|
catch (aliasError) {
|
|
20
23
|
warn(`dsh-subscriptions: tool ${JSON.stringify(definition.name)} and alias ${JSON.stringify(alias)} are already registered; skipping (${aliasError instanceof Error ? aliasError.message : String(aliasError)})`);
|
|
@@ -37,20 +37,24 @@ export function buildVideoGenerateBody(args) {
|
|
|
37
37
|
const prompt = args.prompt.trim();
|
|
38
38
|
if (prompt.length === 0)
|
|
39
39
|
throw new Error('video_generate: prompt must be a non-empty string');
|
|
40
|
-
if (args.duration !== undefined
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
if (args.duration !== undefined &&
|
|
41
|
+
(!Number.isInteger(args.duration) ||
|
|
42
|
+
args.duration < DURATION_RANGE.min ||
|
|
43
|
+
args.duration > DURATION_RANGE.max)) {
|
|
44
44
|
throw new Error(`video_generate: duration must be an integer between ${String(DURATION_RANGE.min)} and ${String(DURATION_RANGE.max)} seconds`);
|
|
45
45
|
}
|
|
46
46
|
const imageUrl = args.image_url?.trim();
|
|
47
47
|
return {
|
|
48
48
|
prompt,
|
|
49
49
|
model: VIDEO_GENERATE_MODEL,
|
|
50
|
-
...args.duration === undefined ? {} : { duration: args.duration },
|
|
51
|
-
...args.aspect_ratio === undefined
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
...(args.duration === undefined ? {} : { duration: args.duration }),
|
|
51
|
+
...(args.aspect_ratio === undefined
|
|
52
|
+
? {}
|
|
53
|
+
: { aspect_ratio: args.aspect_ratio }),
|
|
54
|
+
...(args.resolution === undefined ? {} : { resolution: args.resolution }),
|
|
55
|
+
...(imageUrl === undefined || imageUrl.length === 0
|
|
56
|
+
? {}
|
|
57
|
+
: { image: { url: imageUrl } }),
|
|
54
58
|
};
|
|
55
59
|
}
|
|
56
60
|
function isRecord(value) {
|
|
@@ -72,7 +76,8 @@ function isRecord(value) {
|
|
|
72
76
|
const REQUEST_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
73
77
|
export function parseVideoStartResponse(payload) {
|
|
74
78
|
const body = isRecord(payload) ? payload : {};
|
|
75
|
-
if (typeof body.request_id !== 'string' ||
|
|
79
|
+
if (typeof body.request_id !== 'string' ||
|
|
80
|
+
!REQUEST_ID_PATTERN.test(body.request_id)) {
|
|
76
81
|
throw new Error('video_generate: the response carried no usable request_id');
|
|
77
82
|
}
|
|
78
83
|
return body.request_id;
|
|
@@ -95,7 +100,9 @@ export function parseVideoStatusResponse(payload) {
|
|
|
95
100
|
return {
|
|
96
101
|
status: 'done',
|
|
97
102
|
url: video.url,
|
|
98
|
-
...typeof video.duration === 'number'
|
|
103
|
+
...(typeof video.duration === 'number'
|
|
104
|
+
? { duration: video.duration }
|
|
105
|
+
: {}),
|
|
99
106
|
};
|
|
100
107
|
}
|
|
101
108
|
case 'failed':
|
|
@@ -128,7 +135,9 @@ function sleep(ms, signal) {
|
|
|
128
135
|
return new Promise((resolve, reject) => {
|
|
129
136
|
const onAbort = () => {
|
|
130
137
|
clearTimeout(timer);
|
|
131
|
-
reject(signal.reason instanceof Error
|
|
138
|
+
reject(signal.reason instanceof Error
|
|
139
|
+
? signal.reason
|
|
140
|
+
: new Error('video_generate: aborted'));
|
|
132
141
|
};
|
|
133
142
|
const timer = setTimeout(() => {
|
|
134
143
|
signal.removeEventListener('abort', onAbort);
|
|
@@ -151,12 +160,16 @@ export function createVideoGenerateTool(options) {
|
|
|
151
160
|
const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
|
|
152
161
|
return defineTool({
|
|
153
162
|
name: 'video_generate',
|
|
154
|
-
description: `Generate a short video (1-15 seconds) with the grok subscription (${VIDEO_GENERATE_MODEL}) `
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
description: `Generate a short video (1-15 seconds) with the grok subscription (${VIDEO_GENERATE_MODEL}) ` +
|
|
164
|
+
'and save it as an MP4 file. Generation is asynchronous and may take a minute or more; ' +
|
|
165
|
+
'the tool waits for completion and returns the saved file path. ' +
|
|
166
|
+
'Optionally animate a still image by passing image_url (image-to-video).',
|
|
158
167
|
parameters: {
|
|
159
|
-
prompt: {
|
|
168
|
+
prompt: {
|
|
169
|
+
type: 'string',
|
|
170
|
+
required: true,
|
|
171
|
+
description: 'What the video should show.',
|
|
172
|
+
},
|
|
160
173
|
duration: {
|
|
161
174
|
type: 'integer',
|
|
162
175
|
description: 'Clip length in seconds (1-15); omit for the provider default.',
|
|
@@ -173,8 +186,8 @@ export function createVideoGenerateTool(options) {
|
|
|
173
186
|
},
|
|
174
187
|
image_url: {
|
|
175
188
|
type: 'string',
|
|
176
|
-
description: 'Optional public URL or base64 data URL of a JPEG/PNG/WebP image to animate '
|
|
177
|
-
|
|
189
|
+
description: 'Optional public URL or base64 data URL of a JPEG/PNG/WebP image to animate ' +
|
|
190
|
+
'(image-to-video); the image becomes the starting frame.',
|
|
178
191
|
},
|
|
179
192
|
},
|
|
180
193
|
output: {
|
|
@@ -187,20 +200,24 @@ export function createVideoGenerateTool(options) {
|
|
|
187
200
|
},
|
|
188
201
|
additionalProperties: false,
|
|
189
202
|
},
|
|
190
|
-
render: (_args, value) => [
|
|
203
|
+
render: (_args, value) => [
|
|
204
|
+
{
|
|
191
205
|
type: 'text',
|
|
192
|
-
text: `Saved video to ${value.path}`
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
206
|
+
text: `Saved video to ${value.path}` +
|
|
207
|
+
(value.duration === undefined
|
|
208
|
+
? ''
|
|
209
|
+
: ` (${String(value.duration)}s)`) +
|
|
210
|
+
`\nTemporary provider URL (expires soon): ${value.url}`,
|
|
211
|
+
},
|
|
212
|
+
],
|
|
196
213
|
// The client toolview fetches the bytes by bare file name through the
|
|
197
214
|
// `/subscriptions-auth` `video` endpoint; meta hands it that name.
|
|
198
215
|
presentationMeta: (_args, value) => ({
|
|
199
216
|
fileName: basename(value.path),
|
|
200
|
-
...value.duration === undefined ? {} : { duration: value.duration },
|
|
217
|
+
...(value.duration === undefined ? {} : { duration: value.duration }),
|
|
201
218
|
}),
|
|
202
219
|
},
|
|
203
|
-
presentCall: args => ({
|
|
220
|
+
presentCall: (args) => ({
|
|
204
221
|
card: 'generic',
|
|
205
222
|
title: `video_generate: ${truncate(args.prompt)}`,
|
|
206
223
|
}),
|
|
@@ -209,8 +226,8 @@ export function createVideoGenerateTool(options) {
|
|
|
209
226
|
const session = await options.tokens.session();
|
|
210
227
|
const fetchFn = options.fetchFn ?? hostFetch;
|
|
211
228
|
const headers = {
|
|
212
|
-
|
|
213
|
-
|
|
229
|
+
authorization: `Bearer ${session.accessToken}`,
|
|
230
|
+
accept: 'application/json',
|
|
214
231
|
};
|
|
215
232
|
const submit = await fetchFn(VIDEO_GENERATE_URL, {
|
|
216
233
|
method: 'POST',
|
|
@@ -241,15 +258,18 @@ export function createVideoGenerateTool(options) {
|
|
|
241
258
|
// No provider response text on this path. The request id is shaped like an
|
|
242
259
|
// opaque id before it is stored (see REQUEST_ID_PATTERN), so it is an
|
|
243
260
|
// identifier rather than provider prose; anything else was refused at parse.
|
|
244
|
-
throw new Error(`video_generate: generation ${status.status} (request ${requestId})`
|
|
245
|
-
|
|
261
|
+
throw new Error(`video_generate: generation ${status.status} (request ${requestId})` +
|
|
262
|
+
': [provider response body omitted]');
|
|
246
263
|
}
|
|
247
264
|
if (Date.now() >= deadline) {
|
|
248
265
|
throw new Error(`video_generate: timed out after ${String(maxWaitMs)}ms waiting for request ${requestId}`);
|
|
249
266
|
}
|
|
250
267
|
}
|
|
251
268
|
// The MP4 lives on a separate signed host: no auth header on purpose.
|
|
252
|
-
const download = await fetchFn(done.url, {
|
|
269
|
+
const download = await fetchFn(done.url, {
|
|
270
|
+
method: 'GET',
|
|
271
|
+
signal: exec.signal,
|
|
272
|
+
});
|
|
253
273
|
if (!download.ok)
|
|
254
274
|
throw await httpLlmError(download, 'video_generate download');
|
|
255
275
|
const data = Buffer.from(await download.arrayBuffer());
|
|
@@ -260,7 +280,7 @@ export function createVideoGenerateTool(options) {
|
|
|
260
280
|
const value = {
|
|
261
281
|
path,
|
|
262
282
|
url: done.url,
|
|
263
|
-
...done.duration === undefined ? {} : { duration: done.duration },
|
|
283
|
+
...(done.duration === undefined ? {} : { duration: done.duration }),
|
|
264
284
|
};
|
|
265
285
|
return value;
|
|
266
286
|
},
|