@goodandready/dsh-image-gen 0.11.13 → 0.11.14
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 +25 -0
- package/lib/config-schema.js +34 -0
- package/lib/register-tools.js +46 -20
- package/lib/tools/editing.js +11 -15
- package/lib/tools/generation.js +25 -28
- package/lib/tools/inspect.js +7 -5
- package/lib/updater.js +68 -4
- package/package.json +3 -3
package/lib/client.js
CHANGED
|
@@ -287,6 +287,11 @@ window.__ModuleLoader__.load({
|
|
|
287
287
|
{ field: 'deliverAs', spec: selectField('deliverAs', DELIVERY_MODES), kind: 'select', options: DELIVERY_MODES, labelKey: 'f.deliverAs', hintKey: 'f.deliverAsHint', tab: 'general' },
|
|
288
288
|
{ field: 'outputDir', spec: textField('outputDir'), kind: 'text', labelKey: 'f.outputDir', hintKey: 'f.outputDirHint', placeholderKey: 'p.outputDir', tab: 'general' },
|
|
289
289
|
{ field: 'historyLimit', spec: numberField('historyLimit'), kind: 'number', labelKey: 'f.historyLimit', hintKey: 'f.historyLimitHint', placeholderKey: 'p.historyLimit', tab: 'general' },
|
|
290
|
+
{ field: 'toolsetProfile', spec: selectField('toolsetProfile', ['minimal', 'all', 'custom']), kind: 'select', options: ['minimal', 'all', 'custom'], labelKey: 'f.toolsetProfile', hintKey: 'f.toolsetProfileHint', tab: 'general' },
|
|
291
|
+
{ field: 'toolsetDesign', spec: booleanField('toolsetDesign'), kind: 'select', options: ['true', 'false'], labelKey: 'f.toolsetDesign', hintKey: 'f.toolsetDesignHint', tab: 'general' },
|
|
292
|
+
{ field: 'toolsetProcessing', spec: booleanField('toolsetProcessing'), kind: 'select', options: ['true', 'false'], labelKey: 'f.toolsetProcessing', hintKey: 'f.toolsetProcessingHint', tab: 'general' },
|
|
293
|
+
{ field: 'toolsetFrontend', spec: booleanField('toolsetFrontend'), kind: 'select', options: ['true', 'false'], labelKey: 'f.toolsetFrontend', hintKey: 'f.toolsetFrontendHint', tab: 'general' },
|
|
294
|
+
{ field: 'toolsetCreative', spec: booleanField('toolsetCreative'), kind: 'select', options: ['true', 'false'], labelKey: 'f.toolsetCreative', hintKey: 'f.toolsetCreativeHint', tab: 'general' },
|
|
290
295
|
|
|
291
296
|
// Provider: FAL
|
|
292
297
|
{ field: 'model', spec: textField('model'), kind: 'text', labelKey: 'f.model', hintKey: 'f.modelHint', placeholderKey: 'p.model', when: ['fal'], tab: 'provider' },
|
|
@@ -2458,6 +2463,16 @@ window.__ModuleLoader__.load({
|
|
|
2458
2463
|
'p.outputDir': 'generated/images',
|
|
2459
2464
|
'f.historyLimit': 'History Limit',
|
|
2460
2465
|
'f.historyLimitHint': 'Maximum number of recent generation records kept in memory.',
|
|
2466
|
+
'f.toolsetProfile': 'Toolset Profile',
|
|
2467
|
+
'f.toolsetProfileHint': 'Select which tools to register into context: minimal (<= 3 core tools, saving ~8k prompt tokens), all (30 tools), or custom.',
|
|
2468
|
+
'f.toolsetDesign': 'Design Toolset',
|
|
2469
|
+
'f.toolsetDesignHint': 'Enable design tools: style_matrix, theme_pair, ui_asset, character_sheet, beautify_diagram, anchor.',
|
|
2470
|
+
'f.toolsetProcessing': 'Processing Toolset',
|
|
2471
|
+
'f.toolsetProcessingHint': 'Enable processing tools: remove_background, upscale, vectorize, convert, grid, smart_crop, asset_pack, replace_image_text.',
|
|
2472
|
+
'f.toolsetFrontend': 'Frontend Toolset',
|
|
2473
|
+
'f.toolsetFrontendHint': 'Enable frontend tools: extract_design_tokens, image_to_css_gradient, check_image_contrast, responsive_mockups.',
|
|
2474
|
+
'f.toolsetCreative': 'Creative Toolset',
|
|
2475
|
+
'f.toolsetCreativeHint': 'Enable creative tools: spritesheet, seamless_pattern, sketch_to_image, image_pack.',
|
|
2461
2476
|
'p.historyLimit': '50',
|
|
2462
2477
|
|
|
2463
2478
|
'f.model': 'FAL Model ID',
|
|
@@ -2724,6 +2739,16 @@ window.__ModuleLoader__.load({
|
|
|
2724
2739
|
'p.outputDir': 'generated/images',
|
|
2725
2740
|
'f.historyLimit': '历史记录数量',
|
|
2726
2741
|
'f.historyLimitHint': '保存在内存历史列表中的最大条目数。',
|
|
2742
|
+
'f.toolsetProfile': '工具集配置档',
|
|
2743
|
+
'f.toolsetProfileHint': '选择注册到对话上下文的工具集:minimal(<= 3个核心工具,节省~8k提示词tokens),all(所有30个工具),或custom。',
|
|
2744
|
+
'f.toolsetDesign': '设计工具集',
|
|
2745
|
+
'f.toolsetDesignHint': '启用设计工具:style_matrix, theme_pair, ui_asset, character_sheet, beautify_diagram, anchor。',
|
|
2746
|
+
'f.toolsetProcessing': '图像处理工具集',
|
|
2747
|
+
'f.toolsetProcessingHint': '启用处理工具:remove_background, upscale, vectorize, convert, grid, smart_crop, asset_pack, replace_image_text。',
|
|
2748
|
+
'f.toolsetFrontend': '前端工具集',
|
|
2749
|
+
'f.toolsetFrontendHint': '启用前端工具:extract_design_tokens, image_to_css_gradient, check_image_contrast, responsive_mockups。',
|
|
2750
|
+
'f.toolsetCreative': '创意工具集',
|
|
2751
|
+
'f.toolsetCreativeHint': '启用创意工具:spritesheet, seamless_pattern, sketch_to_image, image_pack。',
|
|
2727
2752
|
'p.historyLimit': '50',
|
|
2728
2753
|
|
|
2729
2754
|
'f.model': 'FAL 模型 ID',
|
package/lib/config-schema.js
CHANGED
|
@@ -54,6 +54,40 @@ export const Config = z.object({
|
|
|
54
54
|
.description('Default aspect ratio (square, landscape_16_9, portrait_9_16, etc.) used when prompt does not specify dimensions.')
|
|
55
55
|
.default('square')
|
|
56
56
|
.volatile(),
|
|
57
|
+
toolsetProfile: z
|
|
58
|
+
.string()
|
|
59
|
+
.description('Which toolset profile to register into conversation context: "minimal" (default, <= 3 core tools, saving ~8,000 prompt tokens), "all" (all 30 tools), or "custom".')
|
|
60
|
+
.default('minimal')
|
|
61
|
+
.volatile(),
|
|
62
|
+
toolsets: z
|
|
63
|
+
.object({
|
|
64
|
+
design: z.boolean().default(false).description('Design tools: style_matrix, theme_pair, ui_asset, character_sheet, beautify_diagram, anchor'),
|
|
65
|
+
processing: z.boolean().default(false).description('Processing tools: remove_background, upscale, vectorize, convert, grid, smart_crop, asset_pack, replace_image_text'),
|
|
66
|
+
frontend: z.boolean().default(false).description('Frontend tools: extract_design_tokens, image_to_css_gradient, check_image_contrast, responsive_mockups'),
|
|
67
|
+
creative: z.boolean().default(false).description('Creative tools: spritesheet, seamless_pattern, sketch_to_image, image_pack'),
|
|
68
|
+
})
|
|
69
|
+
.default({ design: false, processing: false, frontend: false, creative: false })
|
|
70
|
+
.volatile(),
|
|
71
|
+
toolsetDesign: z
|
|
72
|
+
.boolean()
|
|
73
|
+
.description('Enable design tools: style_matrix, theme_pair, ui_asset, character_sheet, beautify_diagram, anchor')
|
|
74
|
+
.default(false)
|
|
75
|
+
.volatile(),
|
|
76
|
+
toolsetProcessing: z
|
|
77
|
+
.boolean()
|
|
78
|
+
.description('Enable processing tools: remove_background, upscale, vectorize, convert, grid, smart_crop, asset_pack, replace_image_text')
|
|
79
|
+
.default(false)
|
|
80
|
+
.volatile(),
|
|
81
|
+
toolsetFrontend: z
|
|
82
|
+
.boolean()
|
|
83
|
+
.description('Enable frontend tools: extract_design_tokens, image_to_css_gradient, check_image_contrast, responsive_mockups')
|
|
84
|
+
.default(false)
|
|
85
|
+
.volatile(),
|
|
86
|
+
toolsetCreative: z
|
|
87
|
+
.boolean()
|
|
88
|
+
.description('Enable creative tools: spritesheet, seamless_pattern, sketch_to_image, image_pack')
|
|
89
|
+
.default(false)
|
|
90
|
+
.volatile(),
|
|
57
91
|
deliverAs: z
|
|
58
92
|
.string()
|
|
59
93
|
.description('How generated images are returned: "link" keeps them on disk and embeds an inline preview link; "attachment" embeds full image bytes directly in the chat.')
|
package/lib/register-tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// register-tools.js — orchestrates all image-gen tool registrations (#216).
|
|
2
|
-
//
|
|
1
|
+
// register-tools.js — orchestrates all image-gen tool registrations (#216, #337).
|
|
2
|
+
// Supports 'minimal' (default: <= 3 core tools), 'all' (all 30 tools), and 'custom' (toggled toolsets).
|
|
3
3
|
|
|
4
4
|
import { registerGenerationTools } from './tools/generation.js'
|
|
5
5
|
import { registerProcessingTools } from './tools/processing-basic.js'
|
|
@@ -20,27 +20,53 @@ import { registerVisionOcrTools } from './tools/vision-ocr.js'
|
|
|
20
20
|
import { registerDiagramTools } from './tools/diagram.js'
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Registers every image-gen tool on the host tools service.
|
|
23
|
+
* Registers every image-gen tool on the host tools service according to toolsetProfile (#337).
|
|
24
24
|
*
|
|
25
25
|
* @param {object} ctx cordis context
|
|
26
26
|
* @param {object} deps shared runtime from apply()
|
|
27
27
|
*/
|
|
28
28
|
export function registerAllTools(ctx, deps) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
const cfg = typeof deps.live === 'function' ? deps.live() : (deps.config || {})
|
|
30
|
+
const profile = cfg.toolsetProfile || 'minimal'
|
|
31
|
+
const isAll = profile === 'all'
|
|
32
|
+
const sets = cfg.toolsets || {}
|
|
33
|
+
const design = isAll || Boolean(sets.design || cfg.toolsetDesign)
|
|
34
|
+
const processing = isAll || Boolean(sets.processing || cfg.toolsetProcessing)
|
|
35
|
+
const frontend = isAll || Boolean(sets.frontend || cfg.toolsetFrontend)
|
|
36
|
+
const creative = isAll || Boolean(sets.creative || cfg.toolsetCreative)
|
|
37
|
+
|
|
38
|
+
// 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 })
|
|
42
|
+
|
|
43
|
+
// 2. Processing toolset
|
|
44
|
+
if (processing) {
|
|
45
|
+
registerProcessingTools(ctx, deps)
|
|
46
|
+
registerProcessingAdvancedTools(ctx, deps)
|
|
47
|
+
registerVisionOcrTools(ctx, deps)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 3. Design toolset
|
|
51
|
+
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)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 4. Frontend toolset
|
|
61
|
+
if (frontend) {
|
|
62
|
+
registerFrontendTools(ctx, deps)
|
|
63
|
+
registerResponsiveTools(ctx, deps)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 5. Creative toolset
|
|
67
|
+
if (creative) {
|
|
68
|
+
registerSketchTools(ctx, deps)
|
|
69
|
+
registerSpritesheetTools(ctx, deps)
|
|
70
|
+
registerPatternTools(ctx, deps)
|
|
71
|
+
}
|
|
46
72
|
}
|
package/lib/tools/editing.js
CHANGED
|
@@ -16,7 +16,7 @@ import { trackAndAssertLoopGuard } from '../loop-guard.js'
|
|
|
16
16
|
import { sanitizeErrorAndLogs } from '../security.js'
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
export function registerEditingTools(ctx, deps) {
|
|
19
|
+
export function registerEditingTools(ctx, deps, options = {}) {
|
|
20
20
|
const {
|
|
21
21
|
config,
|
|
22
22
|
live,
|
|
@@ -29,41 +29,36 @@ export function registerEditingTools(ctx, deps) {
|
|
|
29
29
|
ctx.tools.register(
|
|
30
30
|
defineTool({
|
|
31
31
|
name: 'edit_image',
|
|
32
|
-
description:
|
|
33
|
-
'Edit an existing image using inpainting, targeted modification, or object replacement. '
|
|
34
|
-
+ 'Describe the desired changes in prompt. '
|
|
35
|
-
+ 'If image is omitted or "latest", automatically resolves the most recent image from the conversation session (#144). '
|
|
36
|
-
+ 'Leverages @goodandready/dsh-vision-bridge for pre-edit composition analysis (#145). '
|
|
37
|
-
+ 'Optionally accepts mask (path or attachment id) and strength (0.1-1.0). Returns safe dual-output (#150).',
|
|
32
|
+
description: 'Edit an image using inpainting, targeted modification, or object replacement. If image is omitted, uses the latest image from conversation.',
|
|
38
33
|
parameters: {
|
|
39
34
|
prompt: {
|
|
40
35
|
type: 'string',
|
|
41
36
|
required: true,
|
|
42
|
-
description: '
|
|
37
|
+
description: 'Description of modification or edit to perform.',
|
|
43
38
|
},
|
|
44
39
|
image: {
|
|
45
40
|
type: 'string',
|
|
46
|
-
description: 'Path or attachment
|
|
41
|
+
description: 'Path or attachment ID of source image. Defaults to "latest".',
|
|
47
42
|
},
|
|
48
43
|
mask: {
|
|
49
44
|
type: 'string',
|
|
45
|
+
description: 'Optional inpaint mask path or attachment ID.',
|
|
50
46
|
},
|
|
51
47
|
mask_image: {
|
|
52
48
|
type: 'string',
|
|
53
|
-
description: '
|
|
54
|
-
description: 'Optional path or attachment id of the inpaint mask image.',
|
|
49
|
+
description: 'Alias for mask.',
|
|
55
50
|
},
|
|
56
51
|
strength: {
|
|
57
52
|
type: 'number',
|
|
58
|
-
description: '
|
|
53
|
+
description: 'Edit strength between 0.1 and 1.0 (default 0.75).',
|
|
59
54
|
},
|
|
60
55
|
output_name: {
|
|
61
56
|
type: 'string',
|
|
62
|
-
description: 'Optional file name stem for
|
|
57
|
+
description: 'Optional file name stem for edited image.',
|
|
63
58
|
},
|
|
64
59
|
output_dir: {
|
|
65
60
|
type: 'string',
|
|
66
|
-
description: 'Optional custom directory
|
|
61
|
+
description: 'Optional custom directory for edited image.',
|
|
67
62
|
},
|
|
68
63
|
},
|
|
69
64
|
output: {
|
|
@@ -165,6 +160,7 @@ export function registerEditingTools(ctx, deps) {
|
|
|
165
160
|
}),
|
|
166
161
|
)
|
|
167
162
|
}, 'dsh-image-gen: tool edit_image')
|
|
163
|
+
if (!options.coreOnly) {
|
|
168
164
|
ctx.effect(() => {
|
|
169
165
|
ctx.tools.register(
|
|
170
166
|
defineTool({
|
|
@@ -450,5 +446,5 @@ export function registerEditingTools(ctx, deps) {
|
|
|
450
446
|
}),
|
|
451
447
|
)
|
|
452
448
|
}, 'dsh-image-gen: tool remix_image')
|
|
453
|
-
|
|
449
|
+
}
|
|
454
450
|
}
|
package/lib/tools/generation.js
CHANGED
|
@@ -52,7 +52,7 @@ async function asyncPool(tasks, concurrency = 3) {
|
|
|
52
52
|
return results
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export function registerGenerationTools(ctx, deps) {
|
|
55
|
+
export function registerGenerationTools(ctx, deps, options = {}) {
|
|
56
56
|
const {
|
|
57
57
|
config,
|
|
58
58
|
live,
|
|
@@ -64,17 +64,12 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
64
64
|
ctx.tools.register(
|
|
65
65
|
defineTool({
|
|
66
66
|
name: 'generate_image',
|
|
67
|
-
description:
|
|
68
|
-
'Generate an image with the configured image provider. '
|
|
69
|
-
+ 'Saves the image to the session workspace and shows it in the conversation. '
|
|
70
|
-
+ 'Depending on how the deployment is configured the result carries either the image itself or a link to it; '
|
|
71
|
-
+ 'when you only get a link, answer from the prompt and the link rather than claiming to see the picture. '
|
|
72
|
-
+ 'Use for any text-to-image request. Pass count (1-4) to generate several variations in one call; cost scales with count (default 1).',
|
|
67
|
+
description: 'Generate an image from prompt. Saves the image to workspace and conversation. Pass count (1-4) for variations.',
|
|
73
68
|
parameters: {
|
|
74
69
|
prompt: {
|
|
75
70
|
type: 'string',
|
|
76
71
|
required: true,
|
|
77
|
-
description: 'Detailed description of the image to generate
|
|
72
|
+
description: 'Detailed description of the image to generate.',
|
|
78
73
|
},
|
|
79
74
|
image_size: {
|
|
80
75
|
type: 'string',
|
|
@@ -83,7 +78,7 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
83
78
|
aspect_ratio: {
|
|
84
79
|
type: 'string',
|
|
85
80
|
enum: ['1:1', '16:9', '9:16', '4:3', '3:4', '3:2', '2:3'],
|
|
86
|
-
description: 'Optional aspect ratio
|
|
81
|
+
description: 'Optional aspect ratio (e.g. 16:9, 1:1), overrides image_size.',
|
|
87
82
|
},
|
|
88
83
|
seed: {
|
|
89
84
|
type: 'integer',
|
|
@@ -96,11 +91,11 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
96
91
|
},
|
|
97
92
|
output_name: {
|
|
98
93
|
type: 'string',
|
|
99
|
-
description: 'Optional file name stem for the saved image
|
|
94
|
+
description: 'Optional file name stem for the saved image.',
|
|
100
95
|
},
|
|
101
96
|
output_dir: {
|
|
102
97
|
type: 'string',
|
|
103
|
-
description: 'Optional custom directory to write images into
|
|
98
|
+
description: 'Optional custom directory to write images into.',
|
|
104
99
|
},
|
|
105
100
|
style_preset: {
|
|
106
101
|
type: 'string',
|
|
@@ -109,11 +104,11 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
109
104
|
palette_colors: {
|
|
110
105
|
type: 'array',
|
|
111
106
|
items: { type: 'string' },
|
|
112
|
-
description: 'Optional
|
|
107
|
+
description: 'Optional hex color codes (#1e1b4b) to harmonize palette.',
|
|
113
108
|
},
|
|
114
109
|
count: {
|
|
115
110
|
type: 'integer',
|
|
116
|
-
description: '
|
|
111
|
+
description: 'Variations count (1-4, default 1).',
|
|
117
112
|
},
|
|
118
113
|
prompts: {
|
|
119
114
|
type: 'array',
|
|
@@ -123,60 +118,60 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
123
118
|
{ type: 'object', additionalProperties: false, properties: { text: { type: 'string' } } },
|
|
124
119
|
],
|
|
125
120
|
},
|
|
126
|
-
description: 'Optional list of prompts
|
|
121
|
+
description: 'Optional list of prompts for batch generation.',
|
|
127
122
|
},
|
|
128
123
|
negative_prompt: {
|
|
129
124
|
type: 'string',
|
|
130
|
-
description: 'Optional text describing what NOT to draw
|
|
125
|
+
description: 'Optional text describing what NOT to draw.',
|
|
131
126
|
},
|
|
132
127
|
guidance_scale: {
|
|
133
128
|
type: 'number',
|
|
134
|
-
description: 'Optional prompt adherence (
|
|
129
|
+
description: 'Optional prompt adherence (1-20).',
|
|
135
130
|
},
|
|
136
131
|
quality: {
|
|
137
132
|
type: 'string',
|
|
138
133
|
enum: ['auto', 'low', 'medium', 'high'],
|
|
139
|
-
description: 'Optional quality for providers
|
|
134
|
+
description: 'Optional quality for supported providers.',
|
|
140
135
|
},
|
|
141
136
|
style: {
|
|
142
137
|
type: 'string',
|
|
143
|
-
description: 'Optional style preset for providers
|
|
138
|
+
description: 'Optional style preset for supported providers.',
|
|
144
139
|
},
|
|
145
140
|
source_image: {
|
|
146
141
|
type: 'string',
|
|
147
|
-
description: 'Optional
|
|
142
|
+
description: 'Optional source image path or attachment ID for img2img.',
|
|
148
143
|
},
|
|
149
144
|
mask: {
|
|
150
145
|
type: 'string',
|
|
151
|
-
description: 'Optional path or attachment
|
|
146
|
+
description: 'Optional inpaint mask path or attachment ID.',
|
|
152
147
|
},
|
|
153
148
|
strength: {
|
|
154
149
|
type: 'number',
|
|
155
|
-
description: 'Optional edit strength (0-1)
|
|
150
|
+
description: 'Optional edit strength (0-1).',
|
|
156
151
|
},
|
|
157
152
|
quality_gate: {
|
|
158
153
|
type: 'boolean',
|
|
159
|
-
description: 'Optional
|
|
154
|
+
description: 'Optional automated quality gate override.',
|
|
160
155
|
},
|
|
161
156
|
force: {
|
|
162
157
|
type: 'boolean',
|
|
163
|
-
description: '
|
|
158
|
+
description: 'Bypass cache and force new generation.',
|
|
164
159
|
},
|
|
165
160
|
reference_image: {
|
|
166
161
|
type: 'string',
|
|
167
|
-
description: 'Optional reference image
|
|
162
|
+
description: 'Optional reference image path or URL for consistency.',
|
|
168
163
|
},
|
|
169
164
|
reference_strength: {
|
|
170
165
|
type: 'number',
|
|
171
|
-
description: '
|
|
166
|
+
description: 'Strength of reference influence (0.1-1.0).',
|
|
172
167
|
},
|
|
173
168
|
face_reference: {
|
|
174
169
|
type: 'string',
|
|
175
|
-
description: 'Portrait reference image
|
|
170
|
+
description: 'Portrait reference image for facial identity preservation.',
|
|
176
171
|
},
|
|
177
172
|
face_strength: {
|
|
178
173
|
type: 'number',
|
|
179
|
-
description: 'Facial identity similarity weight
|
|
174
|
+
description: 'Facial identity similarity weight (0.1-1.0).',
|
|
180
175
|
},
|
|
181
176
|
},
|
|
182
177
|
output: {
|
|
@@ -650,5 +645,7 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
650
645
|
}),
|
|
651
646
|
)
|
|
652
647
|
}, 'dsh-image-gen: tool generate_image')
|
|
653
|
-
|
|
648
|
+
if (!options.coreOnly) {
|
|
649
|
+
registerGenerationPackTool(ctx, deps)
|
|
650
|
+
}
|
|
654
651
|
}
|
package/lib/tools/inspect.js
CHANGED
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
estimateSharpnessAndVariance,
|
|
8
8
|
} from '../providers.js'
|
|
9
9
|
|
|
10
|
-
export function registerInspectTools(ctx, deps) {
|
|
10
|
+
export function registerInspectTools(ctx, deps, options = {}) {
|
|
11
11
|
const { resolveSource } = deps
|
|
12
|
-
|
|
13
|
-
ctx.
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
if (!options.coreOnly) {
|
|
13
|
+
ctx.effect(() => {
|
|
14
|
+
ctx.tools.register(
|
|
15
|
+
defineTool({
|
|
16
|
+
name: 'compare_images',
|
|
16
17
|
description: 'Compare two images (paths or attachment ids) and report the fraction of differing pixels. Use to check how much an edit changed the original.',
|
|
17
18
|
parameters: {
|
|
18
19
|
image_a: { type: 'string', required: true, description: 'Path or attachment id of the first image.' },
|
|
@@ -37,6 +38,7 @@ export function registerInspectTools(ctx, deps) {
|
|
|
37
38
|
}),
|
|
38
39
|
)
|
|
39
40
|
}, 'dsh-image-gen: tool compare_images')
|
|
41
|
+
}
|
|
40
42
|
ctx.effect(() => {
|
|
41
43
|
ctx.tools.register(
|
|
42
44
|
defineTool({
|
package/lib/updater.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process'
|
|
2
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { existsSync, readFileSync, unlinkSync, statSync } from 'node:fs'
|
|
3
3
|
import { readFile } from 'node:fs/promises'
|
|
4
4
|
import { homedir } from 'node:os'
|
|
5
5
|
import { basename, dirname, isAbsolute, resolve } from 'node:path'
|
|
@@ -10,6 +10,60 @@ import { isLoopbackAddress, isPrivateLanAddress, extractHostName } from './secur
|
|
|
10
10
|
* Implements the unified DSH plugin updater specification.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
|
|
14
|
+
export function isPidAlive(pid) {
|
|
15
|
+
if (!Number.isInteger(pid) || pid <= 0) return false
|
|
16
|
+
try {
|
|
17
|
+
process.kill(pid, 0)
|
|
18
|
+
return true
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return err?.code === 'EPERM'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function checkAndCleanProfileLock(profileDir) {
|
|
25
|
+
if (!profileDir) return { locked: false }
|
|
26
|
+
const lockPath = resolve(profileDir, 'package.json.lock')
|
|
27
|
+
if (!existsSync(lockPath)) return { locked: false }
|
|
28
|
+
let pid = null
|
|
29
|
+
try {
|
|
30
|
+
const raw = readFileSync(lockPath, 'utf8').trim()
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(raw)
|
|
33
|
+
pid = parsed?.pid ?? Number(raw)
|
|
34
|
+
} catch (_e) {
|
|
35
|
+
pid = parseInt(raw, 10)
|
|
36
|
+
}
|
|
37
|
+
} catch (_err) {
|
|
38
|
+
/* lock unreadable */
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (Number.isInteger(pid) && pid > 0) {
|
|
42
|
+
if (isPidAlive(pid)) {
|
|
43
|
+
return { locked: true, pid }
|
|
44
|
+
}
|
|
45
|
+
// Stale lock: PID is not running
|
|
46
|
+
try {
|
|
47
|
+
unlinkSync(lockPath)
|
|
48
|
+
} catch (_err) {
|
|
49
|
+
/* non-fatal */
|
|
50
|
+
}
|
|
51
|
+
return { locked: false, staleCleaned: true, pid }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Lockfile without valid PID: check age (> 10 min)
|
|
55
|
+
try {
|
|
56
|
+
const stat = statSync(lockPath)
|
|
57
|
+
if (Date.now() - stat.mtimeMs > 10 * 60_000) {
|
|
58
|
+
unlinkSync(lockPath)
|
|
59
|
+
return { locked: false, staleCleaned: true }
|
|
60
|
+
}
|
|
61
|
+
return { locked: true, pid: null }
|
|
62
|
+
} catch (_err) {
|
|
63
|
+
return { locked: false }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
13
67
|
const UPDATE_HEADER = 'x-dsh-plugin-update'
|
|
14
68
|
const UPDATE_TIMEOUT_MS = 10 * 60_000
|
|
15
69
|
const VERSION_CACHE_MS = 5 * 60_000
|
|
@@ -213,7 +267,6 @@ async function installExact(target, packageSpec, options) {
|
|
|
213
267
|
'--profile',
|
|
214
268
|
target.profileName,
|
|
215
269
|
'add',
|
|
216
|
-
'--config.minimumReleaseAge=0',
|
|
217
270
|
packageSpec,
|
|
218
271
|
`--registry=${options.registry ?? 'https://registry.npmjs.org/'}`,
|
|
219
272
|
],
|
|
@@ -232,9 +285,15 @@ async function installExact(target, packageSpec, options) {
|
|
|
232
285
|
detail = (detail + String(chunk)).slice(-4000)
|
|
233
286
|
})
|
|
234
287
|
const timer = setTimeout(() => {
|
|
235
|
-
|
|
288
|
+
try {
|
|
289
|
+
child.kill('SIGTERM')
|
|
290
|
+
setTimeout(() => {
|
|
291
|
+
try { child.kill('SIGKILL') } catch (_err) {}
|
|
292
|
+
checkAndCleanProfileLock(target.profileDir)
|
|
293
|
+
}, 1500)
|
|
294
|
+
} catch (_err) {}
|
|
236
295
|
reject(new Error('Update timed out; use the normal DSH update flow.'))
|
|
237
|
-
}, UPDATE_TIMEOUT_MS)
|
|
296
|
+
}, options.timeoutMs ?? UPDATE_TIMEOUT_MS)
|
|
238
297
|
child.once('error', (error) => {
|
|
239
298
|
clearTimeout(timer)
|
|
240
299
|
reject(error)
|
|
@@ -290,6 +349,11 @@ export function registerPluginUpdater(ctx, options) {
|
|
|
290
349
|
json(response, 409, { error: 'This plugin is already updating.' })
|
|
291
350
|
return
|
|
292
351
|
}
|
|
352
|
+
const lockState = checkAndCleanProfileLock(target.profileDir)
|
|
353
|
+
if (lockState.locked) {
|
|
354
|
+
json(response, 409, { error: `Another package installation is currently in progress${lockState.pid ? ` (PID ${lockState.pid})` : ''}.` })
|
|
355
|
+
return
|
|
356
|
+
}
|
|
293
357
|
installing = true
|
|
294
358
|
try {
|
|
295
359
|
const before = await status(options, target)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-image-gen",
|
|
3
|
-
"version": "0.11.
|
|
4
|
-
"description": "Image generation for DeepSeek Harness: a generate_image tool with pluggable providers
|
|
3
|
+
"version": "0.11.14",
|
|
4
|
+
"description": "Image generation for DeepSeek Harness: a generate_image tool with pluggable providers \u2014 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",
|
|
7
7
|
"dsh",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
|
-
"test": "node scripts/build-client.mjs --check && node --check lib/client.js && node --test test/*.test.mjs",
|
|
72
|
+
"test": "node scripts/build-client.mjs --check && node --check lib/client.js && node --import ./test/_setup.mjs --test test/*.test.mjs",
|
|
73
73
|
"build:client": "node scripts/build-client.mjs",
|
|
74
74
|
"publish:github": "bash scripts/publish-github.sh",
|
|
75
75
|
"build:client:check": "node scripts/build-client.mjs --check"
|