@goodandready/dsh-image-gen 0.11.21 → 0.11.22

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.
@@ -105,14 +105,53 @@ export function formatA1111Parameters(metadata = {}) {
105
105
  import { createHash } from 'node:crypto'
106
106
 
107
107
  /** Deterministic sha256 hash for image generation caching. */
108
- export function computeGenerationHash({ provider, model, prompt, seed, size, style }) {
108
+ /**
109
+ * Deterministic sha256 hash for image generation caching (#170, #357).
110
+ * Incorporates full provider job identity: provider, model, prompt, seed, size, style,
111
+ * format, aspect ratio, negative prompt, quality, strength, and source/mask image content hashes.
112
+ */
113
+ export function computeGenerationHash({
114
+ provider,
115
+ model,
116
+ prompt,
117
+ seed,
118
+ size,
119
+ style,
120
+ format,
121
+ aspectRatio,
122
+ negativePrompt,
123
+ quality,
124
+ strength,
125
+ sourceImage,
126
+ maskImage,
127
+ reference,
128
+ } = {}) {
129
+ const hashContent = (item) => {
130
+ if (!item) return ''
131
+ if (Buffer.isBuffer(item)) return createHash('sha256').update(item).digest('hex').slice(0, 16)
132
+ if (typeof item === 'object' && item.bytes) {
133
+ const b = Buffer.isBuffer(item.bytes) ? item.bytes : Buffer.from(item.bytes)
134
+ return createHash('sha256').update(b).digest('hex').slice(0, 16)
135
+ }
136
+ return String(item).trim()
137
+ }
138
+
109
139
  const norm = [
140
+ 'v2', // namespace versioning (#357)
110
141
  String(provider || '').trim().toLowerCase(),
111
142
  String(model || '').trim().toLowerCase(),
112
143
  String(prompt || '').trim(),
113
144
  String(seed ?? ''),
114
145
  String(size || '').trim().toLowerCase(),
115
146
  String(style || '').trim().toLowerCase(),
147
+ String(format || '').trim().toLowerCase(),
148
+ String(aspectRatio || '').trim().toLowerCase(),
149
+ String(negativePrompt || '').trim(),
150
+ String(quality || '').trim().toLowerCase(),
151
+ strength !== undefined && strength !== null ? String(strength) : '',
152
+ hashContent(sourceImage),
153
+ hashContent(maskImage),
154
+ hashContent(reference),
116
155
  ].join('|')
117
156
  return createHash('sha256').update(norm).digest('hex')
118
157
  }
@@ -24,7 +24,7 @@ export function createLocalGenerator(deps, job) {
24
24
  const base = String(cfg.localBaseURL || '').replace(/\/+$/, '')
25
25
  if (!base) throw new Error('Local image provider: server address is not configured (Settings → Image generation)')
26
26
  const [width, height] = pxSize(aspectPixels, size)
27
- const kind = cfg.localKind === 'a1111' ? 'a1111' : 'comfyui'
27
+ const kind = (cfg.localKind === 'a1111' || cfg.localKind === 'automatic1111') ? 'a1111' : 'comfyui'
28
28
 
29
29
  if (kind === 'a1111') {
30
30
  const isImg2Img = Boolean(source && source.bytes)
@@ -26,6 +26,31 @@ import { registerDiagramTools } from './tools/diagram.js'
26
26
  * @param {object} deps shared runtime from apply()
27
27
  */
28
28
  export function registerAllTools(ctx, deps) {
29
+ const originalToolsRegister = ctx?.tools?.register ? ctx.tools.register.bind(ctx.tools) : null
30
+ const wrappedTools = originalToolsRegister ? Object.create(ctx.tools, {
31
+ register: {
32
+ value: (def) => {
33
+ const originalExecute = def.execute
34
+ const wrappedDef = {
35
+ ...def,
36
+ execute: async (args, exec) => {
37
+ const currentCfg = typeof deps.live === 'function' ? deps.live() : (deps.config || {})
38
+ if (currentCfg && currentCfg.enabled === false) {
39
+ throw new Error('Image generation is disabled in settings (dsh-image-gen.enabled is false); enable it in Settings → Image generation.')
40
+ }
41
+ return originalExecute(args, exec)
42
+ },
43
+ }
44
+ return originalToolsRegister(wrappedDef)
45
+ },
46
+ writable: true,
47
+ configurable: true,
48
+ },
49
+ }) : ctx?.tools
50
+
51
+ const targetCtx = originalToolsRegister ? Object.create(ctx, {
52
+ tools: { value: wrappedTools, writable: true, configurable: true }
53
+ }) : ctx
29
54
  const cfg = typeof deps.live === 'function' ? deps.live() : (deps.config || {})
30
55
  const profile = cfg.toolsetProfile || 'minimal'
31
56
  const isAll = profile === 'all'
@@ -36,37 +61,37 @@ export function registerAllTools(ctx, deps) {
36
61
  const creative = isAll || Boolean(sets.creative || cfg.toolsetCreative)
37
62
 
38
63
  // 1. Core tools: generate_image, edit_image, inspect_image_quality (<= 3 tools in minimal)
39
- registerGenerationTools(ctx, deps, { coreOnly: !creative })
40
- registerEditingTools(ctx, deps, { coreOnly: !processing })
41
- registerInspectTools(ctx, deps, { coreOnly: !processing })
64
+ registerGenerationTools(targetCtx, deps, { coreOnly: !creative })
65
+ registerEditingTools(targetCtx, deps, { coreOnly: !processing })
66
+ registerInspectTools(targetCtx, deps, { coreOnly: !processing })
42
67
 
43
68
  // 2. Processing toolset
44
69
  if (processing) {
45
- registerProcessingTools(ctx, deps)
46
- registerProcessingAdvancedTools(ctx, deps)
47
- registerVisionOcrTools(ctx, deps)
70
+ registerProcessingTools(targetCtx, deps)
71
+ registerProcessingAdvancedTools(targetCtx, deps)
72
+ registerVisionOcrTools(targetCtx, deps)
48
73
  }
49
74
 
50
75
  // 3. Design toolset
51
76
  if (design) {
52
- registerAnchorTools(ctx, deps)
53
- registerUiAssetTools(ctx, deps)
54
- registerStyleMatrixTools(ctx, deps)
55
- registerThemePairTools(ctx, deps)
56
- registerCharacterSheetTools(ctx, deps)
57
- registerDiagramTools(ctx, deps)
77
+ registerAnchorTools(targetCtx, deps)
78
+ registerUiAssetTools(targetCtx, deps)
79
+ registerStyleMatrixTools(targetCtx, deps)
80
+ registerThemePairTools(targetCtx, deps)
81
+ registerCharacterSheetTools(targetCtx, deps)
82
+ registerDiagramTools(targetCtx, deps)
58
83
  }
59
84
 
60
85
  // 4. Frontend toolset
61
86
  if (frontend) {
62
- registerFrontendTools(ctx, deps)
63
- registerResponsiveTools(ctx, deps)
87
+ registerFrontendTools(targetCtx, deps)
88
+ registerResponsiveTools(targetCtx, deps)
64
89
  }
65
90
 
66
91
  // 5. Creative toolset
67
92
  if (creative) {
68
- registerSketchTools(ctx, deps)
69
- registerSpritesheetTools(ctx, deps)
70
- registerPatternTools(ctx, deps)
93
+ registerSketchTools(targetCtx, deps)
94
+ registerSpritesheetTools(targetCtx, deps)
95
+ registerPatternTools(targetCtx, deps)
71
96
  }
72
97
  }
@@ -13,6 +13,7 @@ import {
13
13
  saveAttachmentSafe,
14
14
  } from '../providers.js'
15
15
  import { resolveFallbackChain, executeWithFallback } from '../fallback-router.js'
16
+ import { assertBudgetAvailable, calculateGenerationCost, recordSpend } from '../cost-meter.js'
16
17
  import { renderToolOutput } from '../attachment-helper.js'
17
18
  import {
18
19
  CHARACTER_SHEET_LAYOUTS,
@@ -114,6 +115,14 @@ export function registerCharacterSheetTools(ctx, deps) {
114
115
 
115
116
  const aspectPixels = ASPECT_RATIOS[sheetPlan.aspectRatio] || ASPECT_RATIOS['16:9']
116
117
 
118
+ const estimatedCost = calculateGenerationCost({
119
+ provider: activeProvider,
120
+ model: cfg.model || cfg.customModel || 'default',
121
+ size: cfg.defaultSize || '1024x1024',
122
+ count: 1,
123
+ })
124
+ assertBudgetAvailable(estimatedCost, cfg.dailyBudgetUsd)
125
+
117
126
  const jobPayload = {
118
127
  prompt: sheetPlan.prompt,
119
128
  negativePrompt: sheetPlan.negativePrompt,
@@ -157,6 +166,11 @@ export function registerCharacterSheetTools(ctx, deps) {
157
166
  }
158
167
  }
159
168
 
169
+ recordSpend(Number(genResult.cost) || estimatedCost, {
170
+ ctx,
171
+ meta: { provider: usedProvider, model: cfg.model || cfg.customModel, prompt: sheetPlan.prompt, seed },
172
+ })
173
+
160
174
  const response = {
161
175
  prompt: sheetPlan.prompt,
162
176
  characterDescription: args.prompt,
@@ -11,6 +11,7 @@ import {
11
11
  saveAttachmentSafe,
12
12
  } from '../providers.js'
13
13
  import { resolveFallbackChain, executeWithFallback } from '../fallback-router.js'
14
+ import { assertBudgetAvailable, calculateGenerationCost, recordSpend } from '../cost-meter.js'
14
15
  import { renderToolOutput } from '../attachment-helper.js'
15
16
  import {
16
17
  DIAGRAM_3D_STYLES,
@@ -108,6 +109,14 @@ export function registerDiagramTools(ctx, deps) {
108
109
  const fallbackChain = resolveFallbackChain(activeProvider, cfg)
109
110
  const aspectPixels = ASPECT_RATIOS[plan.aspectRatio] || ASPECT_RATIOS['16:9']
110
111
 
112
+ const estimatedCost = calculateGenerationCost({
113
+ provider: activeProvider,
114
+ model: cfg.model || cfg.customModel || 'default',
115
+ size: cfg.defaultSize || '1024x1024',
116
+ count: 1,
117
+ })
118
+ assertBudgetAvailable(estimatedCost, cfg.dailyBudgetUsd)
119
+
111
120
  const jobPayload = {
112
121
  prompt: plan.prompt,
113
122
  negativePrompt: plan.negativePrompt,
@@ -150,6 +159,11 @@ export function registerDiagramTools(ctx, deps) {
150
159
  }
151
160
  }
152
161
 
162
+ recordSpend(Number(genResult.cost) || estimatedCost, {
163
+ ctx,
164
+ meta: { provider: usedProvider, model: cfg.model || cfg.customModel, prompt: plan.prompt, seed },
165
+ })
166
+
153
167
  return {
154
168
  title: args.title || 'System Architecture',
155
169
  style: plan.style,
@@ -84,6 +84,9 @@ export function registerEditingTools(ctx, deps, options = {}) {
84
84
  async execute(args, exec) {
85
85
  try {
86
86
  const cfg = live()
87
+ if (cfg && cfg.enabled === false) {
88
+ throw new Error('Image generation is disabled in settings (dsh-image-gen.enabled is false); enable it in Settings → Image generation.')
89
+ }
87
90
  trackAndAssertLoopGuard(exec?.agent?.session?.id || 'default', { limit: cfg.loopGuardLimit })
88
91
  const estimatedCostEdit = calculateGenerationCost({
89
92
  provider: cfg.provider || 'fal',
@@ -219,6 +222,9 @@ export function registerEditingTools(ctx, deps, options = {}) {
219
222
  async execute(args, exec) {
220
223
  try {
221
224
  const cfg = live()
225
+ if (cfg && cfg.enabled === false) {
226
+ throw new Error('Image generation is disabled in settings (dsh-image-gen.enabled is false); enable it in Settings → Image generation.')
227
+ }
222
228
  trackAndAssertLoopGuard(exec?.agent?.session?.id || 'default', { limit: cfg.loopGuardLimit })
223
229
  const count = normalizeCount(args.count || 1)
224
230
  const estimatedCostVary = calculateGenerationCost({
@@ -357,6 +363,9 @@ export function registerEditingTools(ctx, deps, options = {}) {
357
363
  async execute(args, exec) {
358
364
  try {
359
365
  const cfg = live()
366
+ if (cfg && cfg.enabled === false) {
367
+ throw new Error('Image generation is disabled in settings (dsh-image-gen.enabled is false); enable it in Settings → Image generation.')
368
+ }
360
369
  trackAndAssertLoopGuard(exec?.agent?.session?.id || 'default', { limit: cfg.loopGuardLimit })
361
370
  const count = normalizeCount(args.count || 1)
362
371
  const estimatedCost = calculateGenerationCost({