@goodandready/dsh-image-gen 0.11.23 → 0.11.25
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/attachment-helper.js +5 -1
- package/lib/client.js +97 -7
- package/lib/commands.js +9 -3
- package/lib/config-schema.js +50 -2
- package/lib/generation-helpers.js +16 -10
- package/lib/history.js +55 -5
- package/lib/index.js +93 -42
- package/lib/provider-utils.js +2 -0
- package/lib/providers/backends/custom.js +17 -4
- package/lib/providers/backends/gemini.js +45 -4
- package/lib/providers/shared-helpers.js +64 -2
- package/lib/providers.js +21 -29
- package/lib/register-tools.js +106 -37
- package/lib/settings-route.js +54 -4
- package/lib/tools/generation.js +52 -28
- package/lib/vault.js +19 -17
- package/package.json +1 -1
package/lib/tools/generation.js
CHANGED
|
@@ -340,29 +340,6 @@ export function registerGenerationTools(ctx, deps, options = {}) {
|
|
|
340
340
|
// rejection reaches the model as text. Without this guard,
|
|
341
341
|
// execution would proceed with empty bytes.
|
|
342
342
|
const guard = (generated) => { if (generated && generated.ok === false) throw new Error(generated.reason) }
|
|
343
|
-
const one = async (jobSeed, providerKey, promptArg = effectivePrompt) => {
|
|
344
|
-
const gen = await providers[providerKey](jobSeed, promptArg)
|
|
345
|
-
guard(gen)
|
|
346
|
-
return await saveGeneratedImageArtifact({
|
|
347
|
-
ctx,
|
|
348
|
-
exec,
|
|
349
|
-
cfg,
|
|
350
|
-
args,
|
|
351
|
-
gen,
|
|
352
|
-
promptArg,
|
|
353
|
-
jobSeed,
|
|
354
|
-
provider,
|
|
355
|
-
deliverAs,
|
|
356
|
-
size,
|
|
357
|
-
format,
|
|
358
|
-
effectiveNegative,
|
|
359
|
-
effectiveGuidance,
|
|
360
|
-
enhanced,
|
|
361
|
-
slugify,
|
|
362
|
-
estimatedCost,
|
|
363
|
-
totalCount,
|
|
364
|
-
})
|
|
365
|
-
}
|
|
366
343
|
|
|
367
344
|
const seedBase = args.seed ?? Math.floor(Math.random() * 100000)
|
|
368
345
|
// Fallback cascade: evaluate configured provider, falling back to alternates
|
|
@@ -372,8 +349,20 @@ export function registerGenerationTools(ctx, deps, options = {}) {
|
|
|
372
349
|
const generators = Object.fromEntries(PROVIDER_KEYS.map((k) => [
|
|
373
350
|
k,
|
|
374
351
|
async (s, p) => {
|
|
375
|
-
|
|
376
|
-
|
|
352
|
+
let totalAttempts = 0
|
|
353
|
+
let accumulatedCost = 0
|
|
354
|
+
// #353: Pipeline evaluates raw gen.bytes first; saves artifact ONCE after acceptance
|
|
355
|
+
const rawGenerate = async (seedVal) => {
|
|
356
|
+
totalAttempts++
|
|
357
|
+
const gen = await providers[k](seedVal, p)
|
|
358
|
+
guard(gen)
|
|
359
|
+
const attemptCost = Number(gen.cost) || estimatedCost
|
|
360
|
+
accumulatedCost += attemptCost
|
|
361
|
+
return gen
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const { generated: acceptedGen, qualityReport } = await executeWithQualityGate(
|
|
365
|
+
rawGenerate,
|
|
377
366
|
{
|
|
378
367
|
initialSeed: s,
|
|
379
368
|
enabled: qgEnabled,
|
|
@@ -381,7 +370,36 @@ export function registerGenerationTools(ctx, deps, options = {}) {
|
|
|
381
370
|
prompt: p,
|
|
382
371
|
}
|
|
383
372
|
)
|
|
384
|
-
|
|
373
|
+
|
|
374
|
+
// Save the accepted artifact ONCE
|
|
375
|
+
const saved = await saveGeneratedImageArtifact({
|
|
376
|
+
ctx,
|
|
377
|
+
exec,
|
|
378
|
+
cfg,
|
|
379
|
+
args,
|
|
380
|
+
gen: acceptedGen,
|
|
381
|
+
promptArg: p,
|
|
382
|
+
jobSeed: qualityReport.seedUsed ?? s,
|
|
383
|
+
provider,
|
|
384
|
+
deliverAs,
|
|
385
|
+
size,
|
|
386
|
+
format,
|
|
387
|
+
effectiveNegative,
|
|
388
|
+
effectiveGuidance,
|
|
389
|
+
enhanced,
|
|
390
|
+
slugify,
|
|
391
|
+
estimatedCost: accumulatedCost || estimatedCost,
|
|
392
|
+
totalCount,
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
...saved,
|
|
397
|
+
qualityReport: {
|
|
398
|
+
...qualityReport,
|
|
399
|
+
attempts: totalAttempts,
|
|
400
|
+
totalCost: accumulatedCost,
|
|
401
|
+
},
|
|
402
|
+
}
|
|
385
403
|
}
|
|
386
404
|
]))
|
|
387
405
|
const images = []
|
|
@@ -400,7 +418,10 @@ export function registerGenerationTools(ctx, deps, options = {}) {
|
|
|
400
418
|
prompt: promptVal,
|
|
401
419
|
seed: seedVal,
|
|
402
420
|
size: args.aspect_ratio ? 'custom' : (args.image_size || cfg.defaultSize),
|
|
403
|
-
style: args.style
|
|
421
|
+
style: args.style,
|
|
422
|
+
stylePreset: (args.style_preset !== undefined && args.style_preset !== null && args.style_preset !== '')
|
|
423
|
+
? args.style_preset
|
|
424
|
+
: (cfg.stylePreset || cfg.defaultStylePreset),
|
|
404
425
|
format: args.output_format || cfg.defaultFormat || 'png',
|
|
405
426
|
aspectRatio: args.aspect_ratio || cfg.defaultAspectRatio,
|
|
406
427
|
negativePrompt: args.negative_prompt || cfg.negativePrompt,
|
|
@@ -482,7 +503,7 @@ export function registerGenerationTools(ctx, deps, options = {}) {
|
|
|
482
503
|
images.push(...parallelResults)
|
|
483
504
|
}
|
|
484
505
|
const first = images[0]
|
|
485
|
-
|
|
506
|
+
let dualOutputSummary = buildDualOutputMarkdown({
|
|
486
507
|
action: 'generated',
|
|
487
508
|
filePath: first.path,
|
|
488
509
|
width: first.width,
|
|
@@ -494,6 +515,9 @@ export function registerGenerationTools(ctx, deps, options = {}) {
|
|
|
494
515
|
cost: first.cost,
|
|
495
516
|
attachmentId: first.attachment?.attachmentId || 'N/A',
|
|
496
517
|
})
|
|
518
|
+
if (first.qualityReport?.exhausted) {
|
|
519
|
+
dualOutputSummary += `\n\n> ⚠️ **Quality Gate Warning**: Visual quality checks failed after ${first.qualityReport.attempts || 3} attempts (${first.qualityReport.defect || 'defect detected'}). Image retained with potential defects.`
|
|
520
|
+
}
|
|
497
521
|
return toLosslessJson({ summary: dualOutputSummary, channel: provider, provider, model: cfg.model || cfg.customModel, _fallback: first._fallback, anchor: activeAnchor ? { label: activeAnchor.label, mode: activeAnchor.mode } : undefined, ...first, images })
|
|
498
522
|
},
|
|
499
523
|
}),
|
package/lib/vault.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, unlinkSync } from 'node:fs'
|
|
2
|
-
import { readHistory, writeHistory } from './history.js'
|
|
2
|
+
import { readHistory, writeHistory, withHistoryLock } from './history.js'
|
|
3
3
|
import { isTrustedLocalRequest } from './security.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -133,24 +133,26 @@ export async function readBoundedRequestBody(req, maxBytes = MAX_VAULT_BODY_BYTE
|
|
|
133
133
|
|
|
134
134
|
export async function deleteVaultEntry(id) {
|
|
135
135
|
if (!id) return { success: false, error: 'Missing entry id' }
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
return await withHistoryLock(async () => {
|
|
137
|
+
const entries = await readHistory()
|
|
138
|
+
const idx = entries.findIndex((e) => e.id === id || e.attachmentId === id)
|
|
139
|
+
if (idx === -1) {
|
|
140
|
+
return { success: false, error: 'Vault entry not found' }
|
|
141
|
+
}
|
|
141
142
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
143
|
+
const [entry] = entries.splice(idx, 1)
|
|
144
|
+
if (entry && entry.path) {
|
|
145
|
+
try {
|
|
146
|
+
unlinkSync(entry.path)
|
|
147
|
+
} catch (_err) { /* ignore missing file */ }
|
|
148
|
+
try {
|
|
149
|
+
unlinkSync(entry.path.replace(/\.[^.]+$/, '.json'))
|
|
150
|
+
} catch (_err) { /* ignore missing sidecar */ }
|
|
151
|
+
}
|
|
151
152
|
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
await writeHistory(entries)
|
|
154
|
+
return { success: true, id }
|
|
155
|
+
})
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-image-gen",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.25",
|
|
4
4
|
"description": "Image generation for DeepSeek Harness: a generate_image tool with pluggable providers — the FAL queue, any OpenAI-compatible images API, or a ChatGPT/Grok subscription with no API key at all. The picture is shown inline in the conversation; the model receives either a link (works with any chat model) or the image itself (needs dsh-vision-bridge or a vision-capable model).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|