@deepblend/dsh-blender-tool 0.6.4 → 0.7.0
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-image.js +47 -0
- package/lib/committed-preview.js +57 -0
- package/lib/image-delivery.js +23 -0
- package/lib/inspection.js +4 -12
- package/lib/shared.js +3 -0
- package/lib/tools.js +20 -9
- package/lib/visual-tools.js +3 -2
- package/package.json +2 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { persistImage } from './shared.js'
|
|
3
|
+
|
|
4
|
+
const MAX_IMAGE_BYTES = 32 * 1024 * 1024
|
|
5
|
+
|
|
6
|
+
/** Persist one already verified PNG; validate any normalized durable reference. */
|
|
7
|
+
export async function persistVerifiedPng(attachments, bytes, artifact, name, signal) {
|
|
8
|
+
const store = typeof attachments?.saveImage === 'function' ? {
|
|
9
|
+
async saveImage(input) {
|
|
10
|
+
const ref = await attachments.saveImage(input)
|
|
11
|
+
const exact = ref?.mediaType === 'image/png' && ref.bytes === artifact.bytes &&
|
|
12
|
+
ref.width === artifact.width && ref.height === artifact.height
|
|
13
|
+
const dimensionsMatch = ref?.width === artifact.width && ref?.height === artifact.height ||
|
|
14
|
+
ref?.originalDimensions?.width === artifact.width && ref?.originalDimensions?.height === artifact.height &&
|
|
15
|
+
ref.width <= artifact.width && ref.height <= artifact.height
|
|
16
|
+
if (typeof ref?.attachmentId !== 'string' || !ref.attachmentId.trim() ||
|
|
17
|
+
!['image/png', 'image/jpeg', 'image/webp'].includes(ref.mediaType) ||
|
|
18
|
+
!Number.isSafeInteger(ref.bytes) || ref.bytes <= 0 || ref.bytes > MAX_IMAGE_BYTES ||
|
|
19
|
+
!Number.isSafeInteger(ref.width) || ref.width <= 0 || !Number.isSafeInteger(ref.height) || ref.height <= 0 ||
|
|
20
|
+
!dimensionsMatch) {
|
|
21
|
+
throw new Error('attachment reference does not match the verified PNG')
|
|
22
|
+
}
|
|
23
|
+
// DSH may normalize the verified PNG into a smaller encoded image. Its
|
|
24
|
+
// read API verifies that durable image's digest, format and dimensions.
|
|
25
|
+
if (!exact) {
|
|
26
|
+
if (typeof attachments.readImage !== 'function') throw new Error('normalized attachment cannot be read back')
|
|
27
|
+
const stored = await attachments.readImage(ref, signal)
|
|
28
|
+
if (!(stored?.data instanceof Uint8Array) || stored.data.byteLength !== ref.bytes ||
|
|
29
|
+
['attachmentId', 'mediaType', 'bytes', 'width', 'height'].some(key => stored.ref?.[key] !== ref[key]) ||
|
|
30
|
+
ref.attachmentId.startsWith('sha256:') &&
|
|
31
|
+
createHash('sha256').update(stored.data).digest('hex') !== ref.attachmentId.slice(7)) {
|
|
32
|
+
throw new Error('normalized attachment bytes do not match its durable reference')
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return ref
|
|
36
|
+
},
|
|
37
|
+
} : attachments
|
|
38
|
+
const attached = await persistImage(store, bytes, name)
|
|
39
|
+
let { image, note } = attached
|
|
40
|
+
if (image && (image.mediaType !== 'image/png' || image.bytes !== artifact.bytes ||
|
|
41
|
+
image.width !== artifact.width || image.height !== artifact.height)) {
|
|
42
|
+
note = 'The attached image is the attachment service normalization of the verified source PNG. ' +
|
|
43
|
+
`Attached format: ${image.mediaType}; dimensions: ${image.width}x${image.height}. ` +
|
|
44
|
+
'The original PNG and its source digest remain at the project path in this result.'
|
|
45
|
+
}
|
|
46
|
+
return { image, note }
|
|
47
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { BlenderError, BlenderErrorCode } from '@deepblend/dsh-blender-contracts'
|
|
3
|
+
import { persistVerifiedPng } from './attachment-image.js'
|
|
4
|
+
import { imageForCallingModel } from './image-delivery.js'
|
|
5
|
+
|
|
6
|
+
const MAX_IMAGE_BYTES = 32 * 1024 * 1024
|
|
7
|
+
const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
|
|
8
|
+
|
|
9
|
+
/** Attach the receipt's existing preview, with no compile, render or output-time IO. */
|
|
10
|
+
export async function attachCommittedPreview(ctx, exec, resolved, value, { projectId, summary }) {
|
|
11
|
+
const artifact = summary?.previews?.[0]
|
|
12
|
+
if (!artifact) return value
|
|
13
|
+
const cancelled = () => {
|
|
14
|
+
if (exec?.signal?.aborted) throw new BlenderError(BlenderErrorCode.ABORTED,
|
|
15
|
+
'Preview attachment cancelled; the saved revision may already exist.')
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
cancelled()
|
|
19
|
+
const revision = summary.revision
|
|
20
|
+
if (typeof projectId !== 'string' || !projectId || !/^r[0-9]{4,}$/.test(revision) ||
|
|
21
|
+
!/^[a-f0-9]{64}$/.test(summary.digest) || artifact.kind !== 'preview' ||
|
|
22
|
+
artifact.sourceRevision !== revision || artifact.sourceDigest !== summary.digest ||
|
|
23
|
+
artifact.mime !== 'image/png' || typeof artifact.path !== 'string' ||
|
|
24
|
+
!artifact.path.startsWith(`revisions/${revision}/previews/`) ||
|
|
25
|
+
artifact.path.split('/').some(part => !part || part === '.' || part === '..') ||
|
|
26
|
+
!/^[a-f0-9]{64}$/.test(artifact.sha256) ||
|
|
27
|
+
!Number.isSafeInteger(artifact.bytes) || artifact.bytes <= 0 || artifact.bytes > MAX_IMAGE_BYTES ||
|
|
28
|
+
!Number.isSafeInteger(artifact.width) || artifact.width <= 0 || artifact.width > 2048 ||
|
|
29
|
+
!Number.isSafeInteger(artifact.height) || artifact.height <= 0 || artifact.height > 2048 ||
|
|
30
|
+
typeof artifact.cameraId !== 'string' || !artifact.cameraId || !Number.isSafeInteger(artifact.frame) ||
|
|
31
|
+
typeof resolved.studio.readArtifact !== 'function') throw new Error('Invalid saved preview receipt')
|
|
32
|
+
const read = await resolved.studio.readArtifact({ projectId, path: artifact.path })
|
|
33
|
+
cancelled()
|
|
34
|
+
if (read?.path !== artifact.path || read.contentType !== 'image/png' || !Buffer.isBuffer(read.bytes) ||
|
|
35
|
+
read.size !== artifact.bytes || read.bytes.length !== artifact.bytes || read.bytes.length < 24 ||
|
|
36
|
+
!read.bytes.subarray(0, 8).equals(PNG_SIGNATURE) ||
|
|
37
|
+
read.bytes.readUInt32BE(16) !== artifact.width || read.bytes.readUInt32BE(20) !== artifact.height ||
|
|
38
|
+
createHash('sha256').update(read.bytes).digest('hex') !== artifact.sha256) {
|
|
39
|
+
throw new Error('Saved PNG differs from its receipt')
|
|
40
|
+
}
|
|
41
|
+
const attached = await persistVerifiedPng(resolved.attachments, read.bytes, artifact,
|
|
42
|
+
`preview-${revision}-${artifact.cameraId}-${artifact.frame}.png`, exec?.signal)
|
|
43
|
+
cancelled()
|
|
44
|
+
return await imageForCallingModel(ctx, exec, {
|
|
45
|
+
...value, image: attached.image,
|
|
46
|
+
imageSource: { ...artifact, projectId, revision, digest: summary.digest },
|
|
47
|
+
text: value.text + '\n\nThe image uses the preview already saved with this revision; no extra render was started.' +
|
|
48
|
+
(attached.note ? '\n' + attached.note : ''),
|
|
49
|
+
})
|
|
50
|
+
} catch (cause) {
|
|
51
|
+
cancelled()
|
|
52
|
+
if (cause?.code === BlenderErrorCode.ABORTED) throw cause
|
|
53
|
+
return { ...value, image: null,
|
|
54
|
+
text: value.text + '\n\nThe revision remains saved, but its preview could not be verified and attached here. ' +
|
|
55
|
+
'The model has not seen this image. Use the receipt to inspect the saved artifact; do not recreate the project or repeat the render to repair an attachment.' }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BlenderError, BlenderErrorCode } from '@deepblend/dsh-blender-contracts'
|
|
2
|
+
|
|
3
|
+
/** Keep rendered artifacts available without sending images to a known text-only route. */
|
|
4
|
+
export async function imageForCallingModel(ctx, exec, value) {
|
|
5
|
+
if (!value.image) return value
|
|
6
|
+
const route = exec?.agent?.session?.requestHeader?.()?.config
|
|
7
|
+
const llm = ctx.get('llm')
|
|
8
|
+
if (!route?.provider || !route.model || typeof llm?.listModels !== 'function') return value
|
|
9
|
+
let catalog
|
|
10
|
+
try { catalog = await llm.listModels(route.provider) } catch {
|
|
11
|
+
if (exec?.signal?.aborted) throw new BlenderError(BlenderErrorCode.ABORTED, 'Image delivery cancelled.')
|
|
12
|
+
return value
|
|
13
|
+
}
|
|
14
|
+
if (exec?.signal?.aborted) throw new BlenderError(BlenderErrorCode.ABORTED, 'Image delivery cancelled.')
|
|
15
|
+
const model = catalog?.find(entry => entry.id === route.model)
|
|
16
|
+
if (!Array.isArray(model?.inputModalities) || model.inputModalities.includes('image')) return value
|
|
17
|
+
return { ...value, image: null,
|
|
18
|
+
imageDelivery: { provider: route.provider, model: route.model, deliveredToModel: false,
|
|
19
|
+
reason: 'The active request model declares no image input.' },
|
|
20
|
+
text: value.text + '\n\nThe rendered image remains saved at the path in this result. ' +
|
|
21
|
+
`It was not attached to the ${route.provider}/${route.model} conversation because this model does not accept images. ` +
|
|
22
|
+
'The model has not seen these pixels; report measurements separately from appearance and artistic approval.' }
|
|
23
|
+
}
|
package/lib/inspection.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
2
|
import { BlenderError, BlenderErrorCode, warning } from '@deepblend/dsh-blender-contracts'
|
|
3
|
-
import { canonicalCall, definedFields,
|
|
3
|
+
import { canonicalCall, definedFields, renderSuccess } from './shared.js'
|
|
4
|
+
|
|
5
|
+
import { persistVerifiedPng } from './attachment-image.js'
|
|
4
6
|
|
|
5
7
|
const REQUIRED_HOST_API = 6
|
|
6
8
|
const MAX_IMAGE_BYTES = 32 * 1024 * 1024
|
|
@@ -66,17 +68,7 @@ export async function renderInspection(resolved, request) {
|
|
|
66
68
|
let image = null
|
|
67
69
|
let note = null
|
|
68
70
|
try {
|
|
69
|
-
const
|
|
70
|
-
async saveImage(input) {
|
|
71
|
-
const ref = await attachments.saveImage(input)
|
|
72
|
-
if (typeof ref?.attachmentId !== 'string' || !ref.attachmentId.trim() || ref.mediaType !== 'image/png' ||
|
|
73
|
-
ref.bytes !== artifact.bytes || ref.width !== artifact.width || ref.height !== artifact.height) {
|
|
74
|
-
throw new Error('attachment reference does not match the verified PNG')
|
|
75
|
-
}
|
|
76
|
-
return ref
|
|
77
|
-
},
|
|
78
|
-
} : attachments
|
|
79
|
-
const attached = await persistImage(store, read.bytes, `${mode}-${revision}-${cameraId}-${frame}.png`)
|
|
71
|
+
const attached = await persistVerifiedPng(attachments, read.bytes, artifact, `${mode}-${revision}-${cameraId}-${frame}.png`, signal)
|
|
80
72
|
aborted(signal)
|
|
81
73
|
image = attached.image
|
|
82
74
|
note = attached.note
|
package/lib/shared.js
CHANGED
|
@@ -99,6 +99,9 @@ export async function persistImage(attachments, bytes, name) {
|
|
|
99
99
|
width: Number(ref.width),
|
|
100
100
|
height: Number(ref.height),
|
|
101
101
|
name: typeof ref.name === 'string' ? ref.name : name,
|
|
102
|
+
...(ref.originalDimensions ? { originalDimensions: {
|
|
103
|
+
width: Number(ref.originalDimensions.width), height: Number(ref.originalDimensions.height),
|
|
104
|
+
} } : {}),
|
|
102
105
|
},
|
|
103
106
|
note: null,
|
|
104
107
|
}
|
package/lib/tools.js
CHANGED
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
|
|
29
29
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
30
30
|
import { renderInspection } from './inspection.js'
|
|
31
|
+
import { imageForCallingModel } from './image-delivery.js'
|
|
32
|
+
import { attachCommittedPreview } from './committed-preview.js'
|
|
31
33
|
|
|
32
34
|
import {
|
|
33
35
|
SCENE_OPERATION_NAMES,
|
|
@@ -116,7 +118,7 @@ function recipeParameters(ctx) {
|
|
|
116
118
|
'Saving needs that exact baseRevision and numeric parameters. It updates the same project, preserves independent ' +
|
|
117
119
|
'materials, bevels, photography and history, and refuses manually conflicting controlled geometry. ' +
|
|
118
120
|
'A scene without declared assembly controls returns available:false; do not invent parameters or replace the project. ' +
|
|
119
|
-
'Saving compiles a checkpoint and preview by default; explicit false flags can request a data-only revision. ' +
|
|
121
|
+
'Saving compiles a checkpoint and preview by default and can attach that existing preview for inspection; explicit false flags can request a data-only revision. ' +
|
|
120
122
|
'Use a unique idempotencyKey per user save and reuse it only when retrying the same intent.',
|
|
121
123
|
parameters: {
|
|
122
124
|
action: { type: 'string', enum: ['read', 'save'], description: 'read (default) is read-only; save creates one atomic revision.' },
|
|
@@ -128,7 +130,7 @@ function recipeParameters(ctx) {
|
|
|
128
130
|
saveCheckpoint: { type: 'boolean', description: 'Save a Blender checkpoint (default true).' },
|
|
129
131
|
renderPreview: { type: 'boolean', description: 'Render an actual new preview (default true).' },
|
|
130
132
|
},
|
|
131
|
-
output:
|
|
133
|
+
output: TOOL_OUTPUT_WITH_IMAGE,
|
|
132
134
|
async execute(args, exec) {
|
|
133
135
|
const resolved = resolveStudio(ctx)
|
|
134
136
|
if (resolved.unavailable !== undefined) return { ok: false, ...resolved.unavailable }
|
|
@@ -143,7 +145,9 @@ function recipeParameters(ctx) {
|
|
|
143
145
|
saveCheckpoint: args.saveCheckpoint !== false, renderPreview: args.renderPreview !== false,
|
|
144
146
|
signal: exec?.signal })
|
|
145
147
|
const { data, canonicalWarnings } = await canonicalCall(result, warning)
|
|
146
|
-
|
|
148
|
+
const value = { ok: true, text: renderSuccess(action === 'read' ? 'Saved assembly controls.' : 'Assembly dimensions saved.', data, { warnings: canonicalWarnings }), data }
|
|
149
|
+
return action === 'save' && args.renderPreview !== false
|
|
150
|
+
? await attachCommittedPreview(ctx, exec, resolved, value, { projectId: args.projectId, summary: data }) : value
|
|
147
151
|
} catch (cause) { return { ok: false, ...renderFailure(cause, 'RECIPE_PARAMETER_FAILED') } }
|
|
148
152
|
},
|
|
149
153
|
presentCall: args => ({ card: 'generic', title: args?.action === 'save' ? 'Save assembly dimensions' : 'Read assembly dimensions',
|
|
@@ -194,6 +198,9 @@ function projectCreate(ctx) {
|
|
|
194
198
|
'Give it the operator\'s brief as `goal`: it is stored with the project and never parsed, so write it ' +
|
|
195
199
|
'in full. For an existing product design, call blender_recipe_list and pass its id/version/digest ' +
|
|
196
200
|
'as recipe, with only its declared parameter overrides. recipe and sceneSpec are mutually exclusive. ' +
|
|
201
|
+
'When the brief asks for an editable Blender file and a preview, set saveCheckpoint:true and ' +
|
|
202
|
+
'renderPreview:true together to save both in this creation. The saved preview can be attached in this result; inspect it before rendering that view again. Inspect those returned receipts before ' +
|
|
203
|
+
'reading a full scene or starting another render. ' +
|
|
197
204
|
'Omit both to start from a minimal renderable scaffold and build up with ' +
|
|
198
205
|
'blender_scene_patch; pass one only when you already know the whole scene. ' +
|
|
199
206
|
'Set saveCheckpoint:false to skip the Blender compile and store the SceneSpec alone. The revision then ' +
|
|
@@ -246,7 +253,7 @@ function projectCreate(ctx) {
|
|
|
246
253
|
description: 'Optional stable key for this creation intent (1–128 characters, no control characters). Reuse it with the same inputs when retrying an uncertain result. A completed request opens the same project without another compile or render; a new project needs a new key. Recovery returns its current saved revision.',
|
|
247
254
|
},
|
|
248
255
|
},
|
|
249
|
-
output:
|
|
256
|
+
output: TOOL_OUTPUT_WITH_IMAGE,
|
|
250
257
|
async execute(args, exec) {
|
|
251
258
|
const resolved = resolveStudio(ctx)
|
|
252
259
|
if (resolved.unavailable !== undefined) return { ok: false, ...resolved.unavailable }
|
|
@@ -282,7 +289,7 @@ function projectCreate(ctx) {
|
|
|
282
289
|
notes.push('Compiler notes (these describe decisions made on your behalf):')
|
|
283
290
|
for (const entry of data.warnings) notes.push(` - ${entry.message}`)
|
|
284
291
|
}
|
|
285
|
-
|
|
292
|
+
const value = {
|
|
286
293
|
ok: true,
|
|
287
294
|
text: renderSuccess(`Created project "${data.projectId}" at revision ${data.revision.revision}.`, data, {
|
|
288
295
|
notes,
|
|
@@ -290,6 +297,8 @@ function projectCreate(ctx) {
|
|
|
290
297
|
}),
|
|
291
298
|
data,
|
|
292
299
|
}
|
|
300
|
+
return args.renderPreview === true
|
|
301
|
+
? await attachCommittedPreview(ctx, exec, resolved, value, { projectId: data.projectId, summary: data.revision }) : value
|
|
293
302
|
} catch (cause) {
|
|
294
303
|
const failure = renderFailure(cause, 'PROJECT_CREATE_FAILED')
|
|
295
304
|
return { ok: false, ...failure }
|
|
@@ -563,7 +572,7 @@ function scenePatch(ctx) {
|
|
|
563
572
|
description: 'Also render the preview profile as part of this commit, so the revision arrives with an image. Default false.',
|
|
564
573
|
},
|
|
565
574
|
},
|
|
566
|
-
output:
|
|
575
|
+
output: TOOL_OUTPUT_WITH_IMAGE,
|
|
567
576
|
async execute(args, exec) {
|
|
568
577
|
const resolved = resolveStudio(ctx)
|
|
569
578
|
if (resolved.unavailable !== undefined) return { ok: false, ...resolved.unavailable }
|
|
@@ -619,7 +628,7 @@ function scenePatch(ctx) {
|
|
|
619
628
|
}
|
|
620
629
|
const scene = data.scene ?? {}
|
|
621
630
|
if (scene.counts) notes.push(`Scene now: ${JSON.stringify(scene.counts)}`)
|
|
622
|
-
|
|
631
|
+
const value = {
|
|
623
632
|
ok: true,
|
|
624
633
|
text: renderSuccess(
|
|
625
634
|
`${data.idempotentReplay === true ? 'Reused' : 'Committed'} revision ${data.revision}.`,
|
|
@@ -628,6 +637,8 @@ function scenePatch(ctx) {
|
|
|
628
637
|
),
|
|
629
638
|
data,
|
|
630
639
|
}
|
|
640
|
+
return args.renderPreview === true
|
|
641
|
+
? await attachCommittedPreview(ctx, exec, resolved, value, { projectId: args.projectId, summary: data }) : value
|
|
631
642
|
} catch (cause) {
|
|
632
643
|
const failure = renderFailure(cause, 'SCENE_PATCH_FAILED')
|
|
633
644
|
return { ok: false, ...failure }
|
|
@@ -688,11 +699,11 @@ function previewRender(ctx) {
|
|
|
688
699
|
const resolved = resolveStudio(ctx)
|
|
689
700
|
if (resolved.unavailable !== undefined) return { ok: false, ...resolved.unavailable }
|
|
690
701
|
try {
|
|
691
|
-
if (args.mode !== undefined) return await renderInspection(resolved, {
|
|
702
|
+
if (args.mode !== undefined) return await imageForCallingModel(ctx, exec, await renderInspection(resolved, {
|
|
692
703
|
projectId: args.projectId, revision: args.revision, mode: args.mode,
|
|
693
704
|
cameraId: args.cameraId, frame: args.frame, width: args.width, height: args.height,
|
|
694
705
|
samples: args.samples, signal: exec.signal,
|
|
695
|
-
})
|
|
706
|
+
}))
|
|
696
707
|
const { data, canonicalWarnings } = await canonicalCall(resolved.studio.renderPreview({
|
|
697
708
|
...definedFields({
|
|
698
709
|
projectId: args.projectId,
|
package/lib/visual-tools.js
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
42
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
43
|
+
import { imageForCallingModel } from './image-delivery.js'
|
|
43
44
|
|
|
44
45
|
import { BlenderWarningCode, warning } from '@deepblend/dsh-blender-contracts'
|
|
45
46
|
|
|
@@ -229,12 +230,12 @@ function visualReview(ctx) {
|
|
|
229
230
|
)
|
|
230
231
|
if (persisted.note !== null) notes.push(persisted.note)
|
|
231
232
|
|
|
232
|
-
return {
|
|
233
|
+
return await imageForCallingModel(ctx, exec, {
|
|
233
234
|
ok: true,
|
|
234
235
|
text: renderSuccess(`Visual review of ${data.revision}: ${data.score}/100.`, data, { notes, warnings: [...(data.warnings ?? []), ...canonicalWarnings] }),
|
|
235
236
|
data,
|
|
236
237
|
image: persisted.image,
|
|
237
|
-
}
|
|
238
|
+
})
|
|
238
239
|
} catch (cause) {
|
|
239
240
|
const failure = renderFailure(cause, 'VISUAL_REVIEW_FAILED')
|
|
240
241
|
return { ok: false, ...failure, image: null }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepblend/dsh-blender-tool",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-preset tool consumer: registers the model-visible DeepBlend Blender tools. Publishes no service.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"./package.json": "./package.json"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@deepblend/dsh-blender-contracts": "0.
|
|
14
|
+
"@deepblend/dsh-blender-contracts": "0.7.0"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|