@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 +12 -0
- package/lib/client.panels.js +19 -1
- package/lib/media-video.js +48 -0
- package/lib/native-tools.js +32 -6
- package/package.json +1 -1
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 = () => {
|
package/lib/client.panels.js
CHANGED
|
@@ -365,6 +365,24 @@ window.__ModuleLoader__.load({
|
|
|
365
365
|
}, mode === 'fast' ? '⚡ FAST' : 'Speed')
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
|
|
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
|
+
}
|
package/lib/native-tools.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
221
|
-
aspect_ratio:
|
|
222
|
-
|
|
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
|
-
|
|
235
|
+
let data = await res.json().catch(() => ({}))
|
|
227
236
|
if (!res.ok) throw new Error(`video_generate HTTP ${res.status}: ${extractResponseText(data)}`)
|
|
228
|
-
|
|
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.
|
|
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",
|