@goodandready/dsh-subscriptions 0.7.50 → 0.7.51

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/lib/client.js CHANGED
@@ -1090,6 +1090,7 @@ window.__ModuleLoader__.load({
1090
1090
  const SourcesPanel = lazyPanel('SourcesPanel')
1091
1091
  const AccountsDashboardPanel = lazyPanel('AccountsDashboardPanel')
1092
1092
  const SpeedToggle = lazyPanel('SpeedToggle')
1093
+ const GeneratedMediaRow = lazyPanel('GeneratedMediaRow')
1093
1094
 
1094
1095
  // #839 model-list helpers
1095
1096
  function modelListSearchText(model) {
@@ -3912,6 +3913,17 @@ h('div', { className: 'dsub-row' },
3912
3913
  }, SpeedToggle))
3913
3914
  }, 'dsh-subscriptions: composer speed toggle')
3914
3915
 
3916
+ ctx.effect(() => {
3917
+ if (!ctx.slots || typeof ctx.slots.inject !== 'function') return () => {}
3918
+ const offs = ['image_generate', 'video_generate'].map((key) => ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({
3919
+ name: 'tool.call.toolview',
3920
+ key,
3921
+ locale: NS,
3922
+ children: { 'tool.call.images': { kind: 'single', scope: 'session' } },
3923
+ }, GeneratedMediaRow)))
3924
+ return () => { for (const off of offs) if (typeof off === 'function') off() }
3925
+ }, 'dsh-subscriptions: generated media rows')
3926
+
3915
3927
  ctx.effect(() => {
3916
3928
  const cleanups = []
3917
3929
  const registerLegacySeats = () => {
@@ -365,6 +365,24 @@ window.__ModuleLoader__.load({
365
365
  }, mode === 'fast' ? '⚡ FAST' : 'Speed')
366
366
  }
367
367
 
368
- return { WebhookTestButton, VaultSnapshotsPanel, ProviderPacksPanel, SourcesPanel, AccountsDashboardPanel, SpeedToggle }
368
+ // #801: inline image/video result row for generated media.
369
+ function GeneratedMediaRow(props) {
370
+ const block = (props && props.block) || {}
371
+ const content = Array.isArray(block.content) ? block.content : []
372
+ const text = content.map((part) => (part && part.text) || '').join('\n')
373
+ const refs = []
374
+ for (const match of text.matchAll(/Attachment:\s*(\S+)/g)) refs.push(match[1])
375
+ const urls = []
376
+ for (const match of text.matchAll(/https?:\/\/\S+\.(?:mp4|webm|mov)\S*/g)) urls.push(match[0])
377
+ return h('div', { className: 'dsub-section-card' },
378
+ h('div', { className: 'dsub-sub' }, text.split('\n').filter((line) => !line.startsWith('Attachment:')).join('\n').slice(0, 500)),
379
+ refs.length && props.renderSlot
380
+ ? props.renderSlot('tool.call.images', { images: refs.map((attachment) => ({ attachment })) })
381
+ : null,
382
+ urls.map((url, i) => h('video', { key: i, src: url, controls: true, style: { maxWidth: '100%', marginTop: 8 } })),
383
+ )
384
+ }
385
+
386
+ return { WebhookTestButton, VaultSnapshotsPanel, ProviderPacksPanel, SourcesPanel, AccountsDashboardPanel, SpeedToggle, GeneratedMediaRow }
369
387
  }
370
388
  })
@@ -0,0 +1,48 @@
1
+ const ASPECTS = new Set(['16:9', '9:16', '1:1', '4:3', '3:4', '3:2', '2:3'])
2
+ const RESOLUTIONS = new Set(['480p', '720p', '1080p'])
3
+
4
+ /** #801: validate video generation parameters before dispatch. */
5
+ export function validateVideoRequest({ duration, aspectRatio, resolution, imageUrl } = {}) {
6
+ const seconds = duration == null ? 5 : Number(duration)
7
+ if (!Number.isFinite(seconds) || seconds < 1 || seconds > 15) {
8
+ const err = new Error('duration must be between 1 and 15 seconds')
9
+ err.code = 'INVALID_VIDEO_PARAMS'
10
+ throw err
11
+ }
12
+ if (aspectRatio != null && !ASPECTS.has(String(aspectRatio))) {
13
+ const err = new Error('unsupported aspect ratio')
14
+ err.code = 'INVALID_VIDEO_PARAMS'
15
+ throw err
16
+ }
17
+ if (resolution != null && !RESOLUTIONS.has(String(resolution))) {
18
+ const err = new Error('unsupported resolution')
19
+ err.code = 'INVALID_VIDEO_PARAMS'
20
+ throw err
21
+ }
22
+ if (imageUrl != null && !/^https?:\/\//i.test(String(imageUrl))) {
23
+ const err = new Error('imageUrl must be http(s)')
24
+ err.code = 'INVALID_VIDEO_PARAMS'
25
+ throw err
26
+ }
27
+ return {
28
+ duration: seconds,
29
+ ...(aspectRatio ? { aspectRatio: String(aspectRatio) } : {}),
30
+ ...(resolution ? { resolution: String(resolution) } : {}),
31
+ ...(imageUrl ? { imageUrl: String(imageUrl) } : {}),
32
+ }
33
+ }
34
+
35
+ export function videoPollDelay(attempt) {
36
+ const n = Number(attempt) || 0
37
+ return Math.min(4000, 500 * 2 ** n)
38
+ }
39
+
40
+ export function extractVideoResult(payload) {
41
+ const body = payload && typeof payload === 'object' ? payload : {}
42
+ const data = body.data && typeof body.data === 'object' ? body.data : body
43
+ return {
44
+ id: data.id || data.video_id || data.request_id || null,
45
+ status: String(data.status || '').toLowerCase(),
46
+ url: data.url || data.video_url || (Array.isArray(data.output) ? data.output[0] : null),
47
+ }
48
+ }
@@ -1,6 +1,7 @@
1
1
  import { mkdir, writeFile } from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
  import { validateReferenceImages } from './media-reference.js'
4
+ import { validateVideoRequest, videoPollDelay, extractVideoResult } from './media-video.js'
4
5
 
5
6
  const TOOL_OUTPUT = {
6
7
  schema: {
@@ -204,11 +205,18 @@ export function registerSubscriptionTools(ctx, deps, options = {}) {
204
205
  prompt: { type: 'string', description: 'Video prompt' },
205
206
  duration: { type: 'number', description: 'Duration in seconds (1-15)' },
206
207
  aspect_ratio: { type: 'string', description: 'e.g. 16:9, 1:1, 9:16' },
207
- image_url: { type: 'string', description: 'Optional source image URL' },
208
+ resolution: { type: 'string', enum: ['480p', '720p', '1080p'] },
209
+ image_url: { type: 'string', description: 'Optional source image URL (image-to-video)' },
208
210
  },
209
211
  required: ['prompt'],
210
212
  },
211
213
  async execute(args, exec) {
214
+ const params = validateVideoRequest({
215
+ duration: args && args.duration,
216
+ aspectRatio: args && args.aspect_ratio,
217
+ resolution: args && args.resolution,
218
+ imageUrl: args && args.image_url,
219
+ })
212
220
  const res = await deps.subscriptions.request({
213
221
  provider: 'grok',
214
222
  path: '/v1/videos',
@@ -217,15 +225,33 @@ export function registerSubscriptionTools(ctx, deps, options = {}) {
217
225
  body: {
218
226
  model: 'grok-imagine-video-1.5',
219
227
  prompt: args && args.prompt,
220
- duration: args && args.duration,
221
- aspect_ratio: args && args.aspect_ratio,
222
- image_url: args && args.image_url,
228
+ duration: params.duration,
229
+ ...(params.aspectRatio ? { aspect_ratio: params.aspectRatio } : {}),
230
+ ...(params.resolution ? { resolution: params.resolution } : {}),
231
+ ...(params.imageUrl ? { image_url: params.imageUrl } : {}),
223
232
  },
224
233
  signal: exec && exec.signal,
225
234
  })
226
- const data = await res.json().catch(() => ({}))
235
+ let data = await res.json().catch(() => ({}))
227
236
  if (!res.ok) throw new Error(`video_generate HTTP ${res.status}: ${extractResponseText(data)}`)
228
- return textResult(`Video request submitted: ${JSON.stringify(data).slice(0, 1000)}`)
237
+ let result = extractVideoResult(data)
238
+ for (let attempt = 0; result.id && !result.url && result.status !== 'failed' && attempt < 5; attempt += 1) {
239
+ await new Promise((resolve) => setTimeout(resolve, videoPollDelay(attempt)))
240
+ const poll = await deps.subscriptions.request({
241
+ provider: 'grok',
242
+ path: '/v1/videos/' + encodeURIComponent(result.id),
243
+ method: 'GET',
244
+ headers: { Accept: 'application/json' },
245
+ signal: exec && exec.signal,
246
+ })
247
+ data = await poll.json().catch(() => ({}))
248
+ if (!poll.ok) break
249
+ result = extractVideoResult(data)
250
+ }
251
+ if (result.status === 'failed') throw new Error('video generation failed')
252
+ return textResult(result.url
253
+ ? 'Video: ' + result.url
254
+ : `Video request submitted: ${JSON.stringify(data).slice(0, 1000)}`)
229
255
  },
230
256
  })
231
257
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.7.50",
3
+ "version": "0.7.51",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",