@wisdoverse/dsh-inline-media-viewer 1.0.1
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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/README.zh-CN.md +155 -0
- package/SECURITY.md +67 -0
- package/client/client.js +554 -0
- package/cordis.patch.yml +3 -0
- package/index.js +185 -0
- package/lib.js +178 -0
- package/package.json +74 -0
package/index.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-inline-media-viewer — host half.
|
|
3
|
+
*
|
|
4
|
+
* Serves media bytes for the web client over a dedicated RPC channel
|
|
5
|
+
* (`/inline-media/read`), reading workspace-local files under the calling
|
|
6
|
+
* session's workspace root and proxying ComfyUI media URLs onto the
|
|
7
|
+
* user-configured ComfyUI origin (default: `http://127.0.0.1:8188`,
|
|
8
|
+
* ComfyUI's standard local address). Registers a persistent user settings
|
|
9
|
+
* namespace (`inline-media`) so the client can tune display preferences
|
|
10
|
+
* and the ComfyUI server address.
|
|
11
|
+
*
|
|
12
|
+
* Security model (see SECURITY.md): local reads are confined to the session
|
|
13
|
+
* workspace (realpath + containment), remote reads always fetch from the
|
|
14
|
+
* configured ComfyUI origin (never the source host), and every payload is
|
|
15
|
+
* size-capped before transfer.
|
|
16
|
+
*
|
|
17
|
+
* @module dsh-inline-media-viewer
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
21
|
+
import { isAbsolute, resolve } from "node:path";
|
|
22
|
+
import z from "@deepseek-ai/schemastery";
|
|
23
|
+
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
24
|
+
|
|
25
|
+
import { COMFY_DEFAULT_ORIGIN, MAX_BYTES, comfyUrl, isInside, mimeOf, normalizeComfyOrigin, testing } from "./lib.js";
|
|
26
|
+
|
|
27
|
+
export { testing } from "./lib.js";
|
|
28
|
+
|
|
29
|
+
export const name = "dsh-inline-media-viewer";
|
|
30
|
+
export const inject = ["connection", "sessions", "settings"];
|
|
31
|
+
|
|
32
|
+
const CHANNEL = "/inline-media";
|
|
33
|
+
const ENDPOINT = "read";
|
|
34
|
+
|
|
35
|
+
/** Persistent user settings namespace for this plugin. */
|
|
36
|
+
export const MEDIA_SETTINGS_NAMESPACE = settingsNamespace("inline-media");
|
|
37
|
+
|
|
38
|
+
/** Schema of the user settings section. */
|
|
39
|
+
export const MEDIA_SETTINGS_SCHEMA = z.object({
|
|
40
|
+
autoRender: z.boolean().required(),
|
|
41
|
+
displayCap: z.number().step(1).min(1).max(30).required(),
|
|
42
|
+
imageMaxPx: z.number().step(1).min(160).max(1200).required(),
|
|
43
|
+
comfyUrl: z.string().max(512).required(),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** Composition defaults; the user layer overrides these. */
|
|
47
|
+
export const MEDIA_SETTINGS_DEFAULTS = Object.freeze({
|
|
48
|
+
autoRender: true,
|
|
49
|
+
displayCap: 12,
|
|
50
|
+
imageMaxPx: 380,
|
|
51
|
+
// Empty means "use the built-in default" (`http://127.0.0.1:8188`),
|
|
52
|
+
// so the settings UI only shows addresses the user actually set.
|
|
53
|
+
comfyUrl: "",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function success(value) {
|
|
57
|
+
return { ok: true, value };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function failure(message) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: { code: "internal", message, details: {} },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function messageOf(error) {
|
|
68
|
+
return error instanceof Error ? error.message : String(error);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function responseBytes(response, signal) {
|
|
72
|
+
const declared = Number(response.headers.get("content-length"));
|
|
73
|
+
if (Number.isFinite(declared) && declared > MAX_BYTES) {
|
|
74
|
+
throw new Error(`media exceeds ${MAX_BYTES} bytes`);
|
|
75
|
+
}
|
|
76
|
+
if (!response.body) return Buffer.alloc(0);
|
|
77
|
+
const reader = response.body.getReader();
|
|
78
|
+
const chunks = [];
|
|
79
|
+
let length = 0;
|
|
80
|
+
try {
|
|
81
|
+
for (;;) {
|
|
82
|
+
if (signal.aborted) throw signal.reason;
|
|
83
|
+
const { done, value } = await reader.read();
|
|
84
|
+
if (done) break;
|
|
85
|
+
length += value.byteLength;
|
|
86
|
+
if (length > MAX_BYTES) throw new Error(`media exceeds ${MAX_BYTES} bytes`);
|
|
87
|
+
chunks.push(Buffer.from(value));
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
reader.releaseLock();
|
|
91
|
+
}
|
|
92
|
+
return Buffer.concat(chunks, length);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function readRemote(source, signal, settingsValue) {
|
|
96
|
+
const configured = settingsValue && typeof settingsValue.comfyUrl === "string"
|
|
97
|
+
? settingsValue.comfyUrl.trim()
|
|
98
|
+
: "";
|
|
99
|
+
const origin = configured === ""
|
|
100
|
+
? normalizeComfyOrigin(COMFY_DEFAULT_ORIGIN)
|
|
101
|
+
: normalizeComfyOrigin(configured);
|
|
102
|
+
if (!origin) throw new Error("configured ComfyUI address is invalid");
|
|
103
|
+
const url = comfyUrl(source, origin);
|
|
104
|
+
if (!url) throw new Error("remote URL is not an allowed ComfyUI media URL");
|
|
105
|
+
const timeout = AbortSignal.timeout(20_000);
|
|
106
|
+
const combined = AbortSignal.any([signal, timeout]);
|
|
107
|
+
const response = await fetch(url, {
|
|
108
|
+
method: "GET",
|
|
109
|
+
redirect: "error",
|
|
110
|
+
signal: combined,
|
|
111
|
+
});
|
|
112
|
+
if (!response.ok) throw new Error(`ComfyUI returned HTTP ${response.status}`);
|
|
113
|
+
const bytes = await responseBytes(response, combined);
|
|
114
|
+
const mime = mimeOf(source) || response.headers.get("content-type")?.split(";", 1)[0];
|
|
115
|
+
if (!mime || !/^(?:image|video|audio)\//.test(mime)) {
|
|
116
|
+
throw new Error("unsupported media type");
|
|
117
|
+
}
|
|
118
|
+
return { dataUrl: `data:${mime};base64,${bytes.toString("base64")}`, mime };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function readLocal(ctx, source, sessionId) {
|
|
122
|
+
const session = ctx.sessions.get(sessionId);
|
|
123
|
+
const cwd = session?.header?.cwd;
|
|
124
|
+
if (!cwd) throw new Error("session working directory is unavailable");
|
|
125
|
+
const mime = mimeOf(source);
|
|
126
|
+
if (!mime) throw new Error("unsupported media extension");
|
|
127
|
+
|
|
128
|
+
const root = await realpath(cwd);
|
|
129
|
+
const candidate = isAbsolute(source) ? resolve(source) : resolve(root, source);
|
|
130
|
+
const target = await realpath(candidate);
|
|
131
|
+
if (!isInside(root, target)) throw new Error("media path is outside the session workspace");
|
|
132
|
+
|
|
133
|
+
const info = await stat(target);
|
|
134
|
+
if (!info.isFile()) throw new Error("media path is not a file");
|
|
135
|
+
if (info.size > MAX_BYTES) throw new Error(`media exceeds ${MAX_BYTES} bytes`);
|
|
136
|
+
const bytes = await readFile(target);
|
|
137
|
+
return { dataUrl: `data:${mime};base64,${bytes.toString("base64")}`, mime };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function handleRead(ctx, endpoint, payload, signal, resolveSettings) {
|
|
141
|
+
if (endpoint !== ENDPOINT) return failure("unknown inline-media endpoint");
|
|
142
|
+
if (!payload || typeof payload !== "object") return failure("invalid request");
|
|
143
|
+
const { source, sessionId } = payload;
|
|
144
|
+
if (typeof source !== "string" || source.length === 0 || source.length > 4096) {
|
|
145
|
+
return failure("invalid media source");
|
|
146
|
+
}
|
|
147
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
148
|
+
return failure("invalid session id");
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const value = /^https?:\/\//i.test(source)
|
|
152
|
+
? await readRemote(source, signal, resolveSettings())
|
|
153
|
+
: await readLocal(ctx, source, sessionId);
|
|
154
|
+
return success(value);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return failure(messageOf(error));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function apply(ctx) {
|
|
161
|
+
let resolveSettings = () => MEDIA_SETTINGS_DEFAULTS;
|
|
162
|
+
ctx.effect(() => {
|
|
163
|
+
const dispose = ctx.connection.rpc.handle(
|
|
164
|
+
CHANNEL,
|
|
165
|
+
(endpoint, payload, signal) => handleRead(ctx, endpoint, payload, signal, resolveSettings),
|
|
166
|
+
{ authority: "trusted-host" },
|
|
167
|
+
);
|
|
168
|
+
return () => {
|
|
169
|
+
void dispose();
|
|
170
|
+
};
|
|
171
|
+
}, "inline-media: rpc");
|
|
172
|
+
|
|
173
|
+
// Register the user settings namespace (defaults + schema). The client reads
|
|
174
|
+
// and writes this section through the settings mirror. autoRender /
|
|
175
|
+
// displayCap / imageMaxPx are consumed client-side; comfyUrl is read here,
|
|
176
|
+
// per request, to decide which remote URLs to proxy and where to fetch them.
|
|
177
|
+
installSettingsSection(ctx, MEDIA_SETTINGS_NAMESPACE, MEDIA_SETTINGS_SCHEMA, MEDIA_SETTINGS_DEFAULTS, {
|
|
178
|
+
setSource: (source) => {
|
|
179
|
+
resolveSettings = source;
|
|
180
|
+
},
|
|
181
|
+
onChange: () => {},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export { MIME } from "./lib.js";
|
package/lib.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-inline-media-viewer — pure helpers.
|
|
3
|
+
*
|
|
4
|
+
* This module has NO runtime dependencies: unit tests and reviewers can import
|
|
5
|
+
* it anywhere. Integration concerns (RPC channel, settings registration) live
|
|
6
|
+
* in `index.js`.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-inline-media-viewer/lib
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { extname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
12
|
+
|
|
13
|
+
export const MAX_BYTES = 48 * 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Built-in loopback aliases: host:port pairs treated as ComfyUI media URLs
|
|
17
|
+
* even before any address is configured. Every alias is rewritten to the
|
|
18
|
+
* configured origin before fetching (like any other accepted source).
|
|
19
|
+
*/
|
|
20
|
+
export const COMFY_HOSTS = Object.freeze([
|
|
21
|
+
"127.0.0.1",
|
|
22
|
+
"localhost",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
/** ComfyUI's standard HTTP port. */
|
|
26
|
+
export const COMFY_PORTS = Object.freeze(["8188"]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Default fetch origin (ComfyUI's standard local address). Point the
|
|
30
|
+
* settings `comfyUrl` at any other server the host process can reach.
|
|
31
|
+
*/
|
|
32
|
+
export const COMFY_DEFAULT_ORIGIN = "http://127.0.0.1:8188";
|
|
33
|
+
|
|
34
|
+
export const MIME = Object.freeze({
|
|
35
|
+
png: "image/png",
|
|
36
|
+
jpg: "image/jpeg",
|
|
37
|
+
jpeg: "image/jpeg",
|
|
38
|
+
webp: "image/webp",
|
|
39
|
+
gif: "image/gif",
|
|
40
|
+
avif: "image/avif",
|
|
41
|
+
bmp: "image/bmp",
|
|
42
|
+
svg: "image/svg+xml",
|
|
43
|
+
mp4: "video/mp4",
|
|
44
|
+
webm: "video/webm",
|
|
45
|
+
mov: "video/quicktime",
|
|
46
|
+
m4v: "video/x-m4v",
|
|
47
|
+
mkv: "video/x-matroska",
|
|
48
|
+
avi: "video/x-msvideo",
|
|
49
|
+
ogv: "video/ogg",
|
|
50
|
+
mp3: "audio/mpeg",
|
|
51
|
+
wav: "audio/wav",
|
|
52
|
+
m4a: "audio/mp4",
|
|
53
|
+
aac: "audio/aac",
|
|
54
|
+
ogg: "audio/ogg",
|
|
55
|
+
oga: "audio/ogg",
|
|
56
|
+
flac: "audio/flac",
|
|
57
|
+
opus: "audio/ogg",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const MEDIA_EXTENSIONS = Object.freeze(Object.keys(MIME));
|
|
61
|
+
|
|
62
|
+
export function extensionOf(source) {
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(source);
|
|
65
|
+
const filename = url.searchParams.get("filename");
|
|
66
|
+
if (filename) return extname(filename).slice(1).toLowerCase();
|
|
67
|
+
return extname(url.pathname).slice(1).toLowerCase();
|
|
68
|
+
} catch {
|
|
69
|
+
return extname(source.split(/[?#]/, 1)[0]).slice(1).toLowerCase();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function mimeOf(source) {
|
|
74
|
+
return MIME[extensionOf(source)];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isInside(root, target) {
|
|
78
|
+
const rel = relative(root, target);
|
|
79
|
+
return rel === "" || (!isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parse a user-configured ComfyUI address into a canonical origin URL.
|
|
84
|
+
*
|
|
85
|
+
* Accepts `http(s)://host[:port]` or a bare `host[:port]` (http assumed).
|
|
86
|
+
* Credentials, extra path segments, queries, and hashes are rejected so the
|
|
87
|
+
* value is always a bare origin. A missing port defaults to ComfyUI's
|
|
88
|
+
* standard 8188 for http and 443 for https.
|
|
89
|
+
*
|
|
90
|
+
* @param {unknown} input - the configured address string.
|
|
91
|
+
* @returns {URL | null} canonical origin URL, or null when unparseable.
|
|
92
|
+
*/
|
|
93
|
+
export function normalizeComfyOrigin(input) {
|
|
94
|
+
if (typeof input !== "string") return null;
|
|
95
|
+
let raw = input.trim();
|
|
96
|
+
if (!raw) return null;
|
|
97
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) raw = `http://${raw}`;
|
|
98
|
+
let url;
|
|
99
|
+
try {
|
|
100
|
+
url = new URL(raw);
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
105
|
+
if (!url.hostname || url.username || url.password) return null;
|
|
106
|
+
const host = url.hostname;
|
|
107
|
+
if (!/^[a-z0-9.-]+$/i.test(host) && !/^\[[0-9a-f:.%]+\]$/i.test(host)) return null;
|
|
108
|
+
if (url.pathname !== "" && url.pathname !== "/") return null;
|
|
109
|
+
if (url.search || url.hash) return null;
|
|
110
|
+
const port = url.port || (url.protocol === "https:" ? "443" : "8188");
|
|
111
|
+
return new URL(`${url.protocol}//${url.hostname}:${port}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* All source origins the proxy accepts for one canonical configured origin:
|
|
116
|
+
* the built-in loopback aliases plus the configured host:port itself.
|
|
117
|
+
*
|
|
118
|
+
* @param {URL} origin - canonical origin from {@link normalizeComfyOrigin}.
|
|
119
|
+
* @returns {Set<string>} accepted "host:port" keys.
|
|
120
|
+
*/
|
|
121
|
+
export function allowedComfyOrigins(origin) {
|
|
122
|
+
const origins = new Set();
|
|
123
|
+
for (const host of COMFY_HOSTS) {
|
|
124
|
+
for (const port of COMFY_PORTS) origins.add(`${host}:${port}`);
|
|
125
|
+
}
|
|
126
|
+
if (origin instanceof URL) {
|
|
127
|
+
const port = origin.port || (origin.protocol === "https:" ? "443" : "8188");
|
|
128
|
+
origins.add(`${origin.hostname}:${port}`);
|
|
129
|
+
}
|
|
130
|
+
return origins;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Rewrite an allowed ComfyUI media URL onto the canonical configured origin.
|
|
135
|
+
*
|
|
136
|
+
* The fetch target is ALWAYS the configured origin (default
|
|
137
|
+
* `http://127.0.0.1:8188`) — never the source host — so chat content can only
|
|
138
|
+
* select a path/query on a server the user configured. Source origins are
|
|
139
|
+
* accepted when their host:port is a loopback alias or matches the configured
|
|
140
|
+
* origin.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} source - absolute http(s) URL.
|
|
143
|
+
* @param {string | URL | null} [origin] - configured ComfyUI address
|
|
144
|
+
* (canonical or parseable string); invalid input falls back to the default.
|
|
145
|
+
* @returns {URL | null} the rewritten fetch URL, or null when not allowed.
|
|
146
|
+
*/
|
|
147
|
+
export function comfyUrl(source, origin = null) {
|
|
148
|
+
let url;
|
|
149
|
+
try {
|
|
150
|
+
url = new URL(source);
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
155
|
+
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
156
|
+
const canonical = typeof origin === "string" && origin.trim() !== ""
|
|
157
|
+
? normalizeComfyOrigin(origin) ?? normalizeComfyOrigin(COMFY_DEFAULT_ORIGIN)
|
|
158
|
+
: origin instanceof URL && origin.hostname
|
|
159
|
+
? origin
|
|
160
|
+
: normalizeComfyOrigin(COMFY_DEFAULT_ORIGIN);
|
|
161
|
+
if (!allowedComfyOrigins(canonical).has(`${url.hostname}:${port}`)) return null;
|
|
162
|
+
const target = new URL(canonical.href);
|
|
163
|
+
target.pathname = url.pathname;
|
|
164
|
+
target.search = url.search;
|
|
165
|
+
target.hash = "";
|
|
166
|
+
target.username = "";
|
|
167
|
+
target.password = "";
|
|
168
|
+
return target;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const testing = Object.freeze({
|
|
172
|
+
allowedComfyOrigins,
|
|
173
|
+
comfyUrl,
|
|
174
|
+
extensionOf,
|
|
175
|
+
isInside,
|
|
176
|
+
mimeOf,
|
|
177
|
+
normalizeComfyOrigin,
|
|
178
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wisdoverse/dsh-inline-media-viewer",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Persistent inline image, video, and audio previews for DeepSeek Harness Web conversations, with workspace-confined local reads and a configurable ComfyUI proxy.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Wisdoverse/dsh-inline-media-viewer-plugin.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/Wisdoverse/dsh-inline-media-viewer-plugin#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/Wisdoverse/dsh-inline-media-viewer-plugin/issues"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"deepseek-harness",
|
|
15
|
+
"dsh",
|
|
16
|
+
"dsh-plugin",
|
|
17
|
+
"web",
|
|
18
|
+
"plugin",
|
|
19
|
+
"media",
|
|
20
|
+
"image",
|
|
21
|
+
"video",
|
|
22
|
+
"audio",
|
|
23
|
+
"comfyui"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"registry": "https://registry.npmjs.org"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "index.js",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": "./index.js",
|
|
34
|
+
"./client": "./client/client.js",
|
|
35
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
36
|
+
"./package.json": "./package.json",
|
|
37
|
+
"./lib.js": "./lib.js"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"index.js",
|
|
41
|
+
"lib.js",
|
|
42
|
+
"client/",
|
|
43
|
+
"cordis.patch.yml",
|
|
44
|
+
"README.md",
|
|
45
|
+
"README.zh-CN.md",
|
|
46
|
+
"SECURITY.md",
|
|
47
|
+
"CHANGELOG.md",
|
|
48
|
+
"LICENSE"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"test": "node test.mjs",
|
|
52
|
+
"lint": "node --check index.js && node --check lib.js && node --check test.mjs"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
56
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
57
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
58
|
+
},
|
|
59
|
+
"dsh": {
|
|
60
|
+
"bundle": {
|
|
61
|
+
"patch": "./cordis.patch.yml"
|
|
62
|
+
},
|
|
63
|
+
"client": {
|
|
64
|
+
"inject": [
|
|
65
|
+
"@deepseek-ai/dsh-client-connection",
|
|
66
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
69
|
+
"@deepseek-ai/dsh-client-locale"
|
|
70
|
+
],
|
|
71
|
+
"platform": "web"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|