dsh-subscriptions 0.2.0 → 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.
Files changed (99) hide show
  1. package/README.md +212 -180
  2. package/lib/auth/claude-code-creds.js +31 -9
  3. package/lib/auth/device-flow.js +16 -9
  4. package/lib/auth/oauth-flow.js +13 -10
  5. package/lib/auth/rpc.js +74 -23
  6. package/lib/auth/store.js +80 -31
  7. package/lib/client/CursorCard.d.ts +1 -1
  8. package/lib/client/CursorCard.js +50 -16
  9. package/lib/client/ExternalUsageCards.d.ts +1 -1
  10. package/lib/client/ExternalUsageCards.js +37 -16
  11. package/lib/client/ImageGallery.d.ts +2 -2
  12. package/lib/client/ImageGallery.js +92 -33
  13. package/lib/client/ImageGenerateToolview.js +50 -14
  14. package/lib/client/OllamaCard.d.ts +1 -1
  15. package/lib/client/OllamaCard.js +26 -8
  16. package/lib/client/OllamaSettingsManager.js +113 -30
  17. package/lib/client/ProviderAccountManager.d.ts +1 -1
  18. package/lib/client/ProviderAccountManager.js +164 -49
  19. package/lib/client/ProviderModelEditor.js +107 -26
  20. package/lib/client/SpeedSelect.d.ts +62 -14
  21. package/lib/client/SpeedSelect.js +257 -46
  22. package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
  23. package/lib/client/SubscriptionUsageBadge.js +300 -112
  24. package/lib/client/SubscriptionsSection.js +258 -85
  25. package/lib/client/UsageBadgeDisplaySetting.js +35 -12
  26. package/lib/client/UsageMeter.d.ts +1 -1
  27. package/lib/client/UsageMeter.js +62 -24
  28. package/lib/client/VideoGenerateToolview.js +69 -17
  29. package/lib/client/account-preferences.d.ts +8 -0
  30. package/lib/client/account-preferences.js +20 -5
  31. package/lib/client/host-contract.d.ts +11 -2
  32. package/lib/client/host-contract.js +20 -3
  33. package/lib/client/index.d.ts +5 -5
  34. package/lib/client/index.js +34 -12
  35. package/lib/client/locales.d.ts +10 -0
  36. package/lib/client/locales.js +61 -51
  37. package/lib/client/ollama-draft.js +21 -12
  38. package/lib/client/reset-action.js +6 -4
  39. package/lib/client/reset-credits-view.d.ts +1 -1
  40. package/lib/client/reset-credits-view.js +66 -17
  41. package/lib/client/subscription-card-styles.js +94 -24
  42. package/lib/client/usage-color-preferences.js +4 -2
  43. package/lib/client/usage-pace.js +30 -12
  44. package/lib/client.js +287 -47
  45. package/lib/client.js.map +1 -1
  46. package/lib/http.js +1 -1
  47. package/lib/index.d.ts +3 -3
  48. package/lib/index.js +118 -64
  49. package/lib/model-defaults.js +4 -2
  50. package/lib/provider-settings.d.ts +12 -0
  51. package/lib/provider-settings.js +58 -16
  52. package/lib/providers/account-preferences.js +51 -22
  53. package/lib/providers/accounts.js +15 -9
  54. package/lib/providers/antigravity-oauth-client.js +2 -2
  55. package/lib/providers/antigravity.js +141 -68
  56. package/lib/providers/catalog-store.js +120 -41
  57. package/lib/providers/claude-cli-version.js +3 -3
  58. package/lib/providers/claude.d.ts +9 -2
  59. package/lib/providers/claude.js +153 -90
  60. package/lib/providers/codex-client-version.js +3 -3
  61. package/lib/providers/codex-search.js +40 -13
  62. package/lib/providers/codex.js +196 -112
  63. package/lib/providers/common.js +71 -39
  64. package/lib/providers/copilot.js +143 -82
  65. package/lib/providers/cursor-adapter.js +42 -18
  66. package/lib/providers/cursor-auth.d.ts +1 -1
  67. package/lib/providers/cursor-auth.js +69 -24
  68. package/lib/providers/cursor-model-settings.js +14 -5
  69. package/lib/providers/cursor-usage.js +27 -13
  70. package/lib/providers/external-usage-controller.js +14 -5
  71. package/lib/providers/external-usage.js +59 -23
  72. package/lib/providers/grok.js +152 -89
  73. package/lib/providers/image-pool.js +29 -11
  74. package/lib/providers/minimax-usage.js +31 -11
  75. package/lib/providers/npm-cli-version.js +10 -3
  76. package/lib/providers/ollama-settings.js +24 -8
  77. package/lib/providers/ollama-web.js +29 -11
  78. package/lib/providers/ollama.js +129 -66
  79. package/lib/providers/pool-family.js +11 -3
  80. package/lib/providers/pool-health.js +28 -8
  81. package/lib/providers/pool-usage.js +16 -9
  82. package/lib/providers/pool.d.ts +8 -0
  83. package/lib/providers/pool.js +81 -42
  84. package/lib/providers/rate-limit.js +8 -2
  85. package/lib/providers/replay.js +40 -17
  86. package/lib/providers/reset-redemption.js +18 -6
  87. package/lib/tools/image-generate.js +123 -65
  88. package/lib/tools/registration.js +5 -2
  89. package/lib/tools/video-generate.js +51 -31
  90. package/lib/tools/x-search.js +50 -22
  91. package/lib/translate/anthropic.js +85 -37
  92. package/lib/translate/antigravity-schema.js +71 -28
  93. package/lib/translate/antigravity-thinking.js +22 -9
  94. package/lib/translate/antigravity.js +141 -50
  95. package/lib/translate/chat-completions.js +67 -23
  96. package/lib/translate/resolved.js +55 -21
  97. package/lib/translate/responses.js +69 -29
  98. package/lib/translate/sse.js +4 -1
  99. 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 || typeof ref !== 'object'
51
- || typeof ref.attachmentId !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(ref.attachmentId)
52
- || !['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(ref.mediaType)
53
- || ![ref.bytes, ref.width, ref.height].every(value => Number.isSafeInteger(value) && value > 0)) {
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
- || ref.bytes > attachments.imageLimits.maxImageBytes
61
- || totalBytes + ref.bytes > attachments.imageLimits.maxMessageImageBytes) {
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
- 'auto': 'auto',
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' ? 'low'
105
- : args.quality === 'medium' || args.quality === 'high' ? 'medium'
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 ? {} : { aspect_ratio: GROK_ASPECT_RATIOS[args.size] },
112
- ...quality === undefined ? {} : { quality },
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 ? payload : {};
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' && record.revised_prompt.length > 0
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 && data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff)
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 && data.toString('latin1', 0, 4) === 'RIFF' && data.toString('latin1', 8, 12) === 'WEBP') {
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 ? {} : { originalDimensions: image.originalDimensions },
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 => ({ type: 'image', attachment: imageRefFromValue(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
- + (value.images?.length ? `\n\nImage references (for image_generate.referenceImages): ${JSON.stringify(value.images)}` : '')
218
- + (value.revisedPrompt === undefined ? '' : `\n\nRevised prompt: ${value.revisedPrompt}`);
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
- + 'subscription (grok-imagine-image-2.0) and save it as an image file. The `provider` '
232
- + 'parameter picks the preferred provider (default gpt); when the preferred one is logged '
233
- + 'out the other serves as fallback. '
234
- + 'Within the selected provider, image requests use available subscription accounts; '
235
- + 'quota or authentication rejection can switch accounts without changing providers. '
236
- + 'Returns the saved file paths; on image-capable models the image itself is attached. '
237
- + 'To edit or use existing images as references, pass referenceImages copied from the image reference text '
238
- + 'or structured tool results (read_image.image or image_generate.images). Select only the images the user '
239
- + 'intends, in prompt order. For local files, call read_image first. Omit referenceImages only for a new image. '
240
- + 'If an edit reference fails, fix it and retry; never omit it to substitute text-to-image generation.',
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: { type: 'string', enum: ['image/png', 'image/jpeg', 'image/webp', 'image/gif'], required: true },
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: { type: 'string', required: true, description: 'What the image should show.' },
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: { type: 'string', enum: ['image/png', 'image/jpeg', 'image/webp', 'image/gif'], required: true },
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 && options.codexTokens !== undefined && (await options.codexTokens.list()).length > 0;
334
- const grokReady = grokEnabled && options.grokTokens !== undefined && (await options.grokTokens.list()).length > 0;
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', tokens: options.codexTokens, signal: exec.signal,
341
- owner: exec.agent?.session, rateLimitReset: codexRateLimitReset,
342
- send: session => fetchFn(references === undefined ? IMAGE_GENERATE_URL : IMAGE_EDIT_URL, {
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
- 'authorization': `Bearer ${session.accessToken}`,
387
+ authorization: `Bearer ${session.accessToken}`,
346
388
  'chatgpt-account-id': session.accountId,
347
- 'originator': 'codex_cli_rs',
389
+ originator: 'codex_cli_rs',
348
390
  'content-type': 'application/json',
349
- 'accept': 'application/json',
391
+ accept: 'application/json',
350
392
  },
351
393
  body: JSON.stringify({
352
394
  ...buildImageGenerateBody(args),
353
- ...references === undefined ? {} : { images: references.map(image_url => ({ image_url })) },
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', tokens: options.grokTokens, signal: exec.signal,
362
- owner: exec.agent?.session, rateLimitReset: grokRateLimitReset,
363
- send: session => fetchFn(references === undefined ? GROK_IMAGE_GENERATE_URL : GROK_IMAGE_EDIT_URL, {
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
- 'authorization': `Bearer ${session.accessToken}`,
417
+ authorization: `Bearer ${session.accessToken}`,
367
418
  'content-type': 'application/json',
368
- 'accept': 'application/json',
419
+ accept: 'application/json',
369
420
  },
370
421
  body: JSON.stringify({
371
422
  ...buildGrokImageGenerateBody(args),
372
- ...references === undefined ? {} : references.length === 1
373
- ? { image: { type: 'image_url', url: references[0] } }
374
- : { images: references.map(url => ({ type: 'image_url', url })) },
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
- && await routeDeclaresImageInput(options.resolveLlm, exec);
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 { name: alias, dispose: registry.register({ ...definition, name: alias }) };
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
- && (!Number.isInteger(args.duration)
42
- || args.duration < DURATION_RANGE.min
43
- || args.duration > DURATION_RANGE.max)) {
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 ? {} : { aspect_ratio: args.aspect_ratio },
52
- ...args.resolution === undefined ? {} : { resolution: args.resolution },
53
- ...imageUrl === undefined || imageUrl.length === 0 ? {} : { image: { url: imageUrl } },
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' || !REQUEST_ID_PATTERN.test(body.request_id)) {
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' ? { duration: video.duration } : {},
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 ? signal.reason : new Error('video_generate: aborted'));
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
- + 'and save it as an MP4 file. Generation is asynchronous and may take a minute or more; '
156
- + 'the tool waits for completion and returns the saved file path. '
157
- + 'Optionally animate a still image by passing image_url (image-to-video).',
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: { type: 'string', required: true, description: 'What the video should show.' },
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
- + '(image-to-video); the image becomes the starting frame.',
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
- + (value.duration === undefined ? '' : ` (${String(value.duration)}s)`)
194
- + `\nTemporary provider URL (expires soon): ${value.url}`,
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
- 'authorization': `Bearer ${session.accessToken}`,
213
- 'accept': 'application/json',
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
- + ': [provider response body omitted]');
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, { method: 'GET', signal: exec.signal });
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
  },