dsh-draw 0.1.3 → 0.2.2
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 +38 -0
- package/README.es.md +39 -32
- package/README.hi.md +39 -32
- package/README.md +41 -34
- package/README.pt.md +39 -32
- package/README.zh.md +39 -32
- package/cordis.patch.yml +5 -1
- package/lib/client.js +3 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +180 -13
- package/lib/types/client/DrawResultCard.d.ts +1 -1
- package/lib/types/client/DrawResultCard.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +3 -2
- package/lib/types/client/present.d.ts +40 -1
- package/lib/types/client/present.d.ts.map +1 -1
- package/lib/types/config.d.ts +6 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/config.js +8 -0
- package/lib/types/event-gate.d.ts +8 -5
- package/lib/types/event-gate.d.ts.map +1 -1
- package/lib/types/event-gate.js +8 -5
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -0
- package/lib/types/provider.d.ts +39 -0
- package/lib/types/provider.d.ts.map +1 -0
- package/lib/types/provider.js +192 -0
- package/lib/types/router.d.ts +1 -3
- package/lib/types/router.d.ts.map +1 -1
- package/lib/types/router.js +3 -7
- package/lib/types/session-events.d.ts +10 -8
- package/lib/types/session-events.d.ts.map +1 -1
- package/lib/types/session-events.js +12 -9
- package/lib/types/version.d.ts +1 -1
- package/lib/types/version.js +1 -1
- package/package.json +32 -6
- package/src/client/DrawResultCard.tsx +1 -2
- package/src/client/index.ts +18 -3
- package/src/client/present.ts +42 -1
- package/src/config.ts +15 -0
- package/src/event-gate.ts +9 -6
- package/src/index.ts +1 -0
- package/src/provider.ts +237 -0
- package/src/router.ts +4 -8
- package/src/session-events.ts +13 -10
- package/src/version.ts +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine-provider seam: one adapter per image-generation vocabulary. The
|
|
3
|
+
* OpenAI provider is the existing OpenAI-compatible `POST /images/generations`
|
|
4
|
+
* adapter; `replicate` and `fal` adapters speak those vendors' native request
|
|
5
|
+
* and response shapes (prediction polling and `fal.run` queue respectively).
|
|
6
|
+
* A configured engine declares its vocabulary (`provider`); the router
|
|
7
|
+
* dispatches through {@link providerFor}, so cooldown fallback, quota, and the
|
|
8
|
+
* audit path stay vocabulary-agnostic.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-draw/provider
|
|
11
|
+
*/
|
|
12
|
+
import { callEngine, EngineCallError, } from './engine.js';
|
|
13
|
+
import { translateRequest } from './translate.js';
|
|
14
|
+
/** The OpenAI-compatible adapter: delegates to the existing `/images/generations` path. */
|
|
15
|
+
const openaiProvider = {
|
|
16
|
+
vocabulary: 'openai',
|
|
17
|
+
generate(engine, request, deps, signal) {
|
|
18
|
+
return callEngine(engine, translateRequest(engine, request), deps, signal);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
/** Replicate prediction polling interval (ms). */
|
|
22
|
+
const REPLICATE_POLL_INTERVAL_MS = 1_000;
|
|
23
|
+
/** Maximum prediction polls before the call fails as a slow request. */
|
|
24
|
+
const REPLICATE_MAX_POLLS = 120;
|
|
25
|
+
/** Parse one concrete `WIDTHxHEIGHT` size string into dimensions. */
|
|
26
|
+
function dimensionsOf(size) {
|
|
27
|
+
const match = /^(\d{2,5})x(\d{2,5})$/u.exec(size);
|
|
28
|
+
if (match === null)
|
|
29
|
+
return undefined;
|
|
30
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
31
|
+
}
|
|
32
|
+
/** Resolve an engine credential, failing closed when it is unconfigured. */
|
|
33
|
+
async function requireCredential(engine, deps) {
|
|
34
|
+
const credential = await deps.resolveCredential(engine.apiKeyRef);
|
|
35
|
+
if (credential === undefined) {
|
|
36
|
+
throw new EngineCallError('credential', 'unconfigured', `engine "${engine.id}" has no resolved credential reference ${engine.apiKeyRef}`);
|
|
37
|
+
}
|
|
38
|
+
return credential;
|
|
39
|
+
}
|
|
40
|
+
/** Parse a JSON response body, failing with a structured parse error. */
|
|
41
|
+
function parseJson(engine, body) {
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(new TextDecoder().decode(body));
|
|
45
|
+
}
|
|
46
|
+
catch (cause) {
|
|
47
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned a non-JSON response`, { cause });
|
|
48
|
+
}
|
|
49
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
50
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned a non-object payload`);
|
|
51
|
+
}
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
/** Download one image URL with an explicit authorization header value. */
|
|
55
|
+
async function downloadImage(engine, url, authorization, deps, signal) {
|
|
56
|
+
const response = await deps.transport.request({
|
|
57
|
+
method: 'GET',
|
|
58
|
+
url,
|
|
59
|
+
headers: { authorization },
|
|
60
|
+
...(signal === undefined ? {} : { signal }),
|
|
61
|
+
});
|
|
62
|
+
if (response.status < 200 || response.status >= 300) {
|
|
63
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" image download failed with HTTP ${response.status}`, { status: response.status });
|
|
64
|
+
}
|
|
65
|
+
return response.body;
|
|
66
|
+
}
|
|
67
|
+
/** Sleep without swallowing the caller's abort (checked by the caller's loop). */
|
|
68
|
+
function sleep(ms) {
|
|
69
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
70
|
+
}
|
|
71
|
+
/** The Replicate adapter: create a prediction, poll to completion, then download its output URLs. */
|
|
72
|
+
const replicateProvider = {
|
|
73
|
+
vocabulary: 'replicate',
|
|
74
|
+
async generate(engine, request, deps, signal) {
|
|
75
|
+
const credential = await requireCredential(engine, deps);
|
|
76
|
+
const dimensions = dimensionsOf(engine.sizeMap[request.size ?? 'square']);
|
|
77
|
+
const input = {
|
|
78
|
+
prompt: request.prompt,
|
|
79
|
+
num_outputs: request.count ?? 1,
|
|
80
|
+
...(dimensions === undefined ? {} : { width: dimensions.width, height: dimensions.height }),
|
|
81
|
+
};
|
|
82
|
+
const create = await deps.transport.request({
|
|
83
|
+
method: 'POST',
|
|
84
|
+
url: `${engine.baseUrl}/models/${engine.model}/predictions`,
|
|
85
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}` },
|
|
86
|
+
body: new TextEncoder().encode(JSON.stringify({ input })),
|
|
87
|
+
...(signal === undefined ? {} : { signal }),
|
|
88
|
+
});
|
|
89
|
+
if (create.status === 401 || create.status === 403) {
|
|
90
|
+
throw new EngineCallError('request', 'auth', `engine "${engine.id}" rejected the credential (HTTP ${create.status})`, { status: create.status });
|
|
91
|
+
}
|
|
92
|
+
if (create.status < 200 || create.status >= 300) {
|
|
93
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" failed with HTTP ${create.status}`, { status: create.status });
|
|
94
|
+
}
|
|
95
|
+
let prediction = parseJson(engine, create.body);
|
|
96
|
+
const id = typeof prediction['id'] === 'string' ? prediction['id'] : '';
|
|
97
|
+
const urls = typeof prediction['urls'] === 'object' && prediction['urls'] !== null
|
|
98
|
+
? prediction['urls']
|
|
99
|
+
: {};
|
|
100
|
+
const pollUrl = typeof urls['get'] === 'string' && urls['get'] !== '' ? urls['get'] : `${engine.baseUrl}/predictions/${id}`;
|
|
101
|
+
for (let poll = 0; poll < REPLICATE_MAX_POLLS; poll += 1) {
|
|
102
|
+
signal?.throwIfAborted();
|
|
103
|
+
const status = typeof prediction['status'] === 'string' ? prediction['status'] : '';
|
|
104
|
+
if (status === 'succeeded')
|
|
105
|
+
break;
|
|
106
|
+
if (status === 'failed' || status === 'canceled') {
|
|
107
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" prediction ${status}`);
|
|
108
|
+
}
|
|
109
|
+
await sleep(REPLICATE_POLL_INTERVAL_MS);
|
|
110
|
+
const polled = await deps.transport.request({
|
|
111
|
+
method: 'GET',
|
|
112
|
+
url: pollUrl,
|
|
113
|
+
headers: { authorization: `Bearer ${credential}` },
|
|
114
|
+
...(signal === undefined ? {} : { signal }),
|
|
115
|
+
});
|
|
116
|
+
if (polled.status < 200 || polled.status >= 300) {
|
|
117
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" prediction poll failed with HTTP ${polled.status}`, { status: polled.status });
|
|
118
|
+
}
|
|
119
|
+
prediction = parseJson(engine, polled.body);
|
|
120
|
+
}
|
|
121
|
+
if (prediction['status'] !== 'succeeded') {
|
|
122
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" prediction did not finish within ${REPLICATE_MAX_POLLS} polls`);
|
|
123
|
+
}
|
|
124
|
+
const output = prediction['output'];
|
|
125
|
+
if (!Array.isArray(output)) {
|
|
126
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" prediction has no output array`);
|
|
127
|
+
}
|
|
128
|
+
const images = [];
|
|
129
|
+
for (const item of output) {
|
|
130
|
+
if (typeof item !== 'string' || item === '') {
|
|
131
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" prediction returned a non-URL output entry`);
|
|
132
|
+
}
|
|
133
|
+
images.push({ data: await downloadImage(engine, item, `Bearer ${credential}`, deps, signal), mediaType: engine.imageMediaType });
|
|
134
|
+
}
|
|
135
|
+
if (images.length === 0) {
|
|
136
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" prediction returned no images`);
|
|
137
|
+
}
|
|
138
|
+
return images;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
/** The fal.ai adapter: POST the `fal.run` queue endpoint and download the returned image URLs. */
|
|
142
|
+
const falProvider = {
|
|
143
|
+
vocabulary: 'fal',
|
|
144
|
+
async generate(engine, request, deps, signal) {
|
|
145
|
+
const credential = await requireCredential(engine, deps);
|
|
146
|
+
const body = {
|
|
147
|
+
prompt: request.prompt,
|
|
148
|
+
image_size: engine.sizeMap[request.size ?? 'square'],
|
|
149
|
+
num_images: request.count ?? 1,
|
|
150
|
+
};
|
|
151
|
+
const response = await deps.transport.request({
|
|
152
|
+
method: 'POST',
|
|
153
|
+
url: `${engine.baseUrl}/${engine.model}`,
|
|
154
|
+
headers: { 'content-type': 'application/json', authorization: `Key ${credential}` },
|
|
155
|
+
body: new TextEncoder().encode(JSON.stringify(body)),
|
|
156
|
+
...(signal === undefined ? {} : { signal }),
|
|
157
|
+
});
|
|
158
|
+
if (response.status === 401 || response.status === 403) {
|
|
159
|
+
throw new EngineCallError('request', 'auth', `engine "${engine.id}" rejected the credential (HTTP ${response.status})`, { status: response.status });
|
|
160
|
+
}
|
|
161
|
+
if (response.status < 200 || response.status >= 300) {
|
|
162
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" failed with HTTP ${response.status}`, { status: response.status });
|
|
163
|
+
}
|
|
164
|
+
const payload = parseJson(engine, response.body);
|
|
165
|
+
const entries = payload['images'];
|
|
166
|
+
if (!Array.isArray(entries)) {
|
|
167
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" response has no images array`);
|
|
168
|
+
}
|
|
169
|
+
const images = [];
|
|
170
|
+
for (const raw of entries) {
|
|
171
|
+
const url = typeof raw === 'object' && raw !== null ? raw['url'] : undefined;
|
|
172
|
+
if (typeof url !== 'string' || url === '') {
|
|
173
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned an image entry without a URL`);
|
|
174
|
+
}
|
|
175
|
+
images.push({ data: await downloadImage(engine, url, `Key ${credential}`, deps, signal), mediaType: engine.imageMediaType });
|
|
176
|
+
}
|
|
177
|
+
if (images.length === 0) {
|
|
178
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned no images`);
|
|
179
|
+
}
|
|
180
|
+
return images;
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
/** The provider registry, keyed by vocabulary. */
|
|
184
|
+
export const PROVIDERS = Object.freeze({
|
|
185
|
+
openai: openaiProvider,
|
|
186
|
+
replicate: replicateProvider,
|
|
187
|
+
fal: falProvider,
|
|
188
|
+
});
|
|
189
|
+
/** Resolve the provider for one vocabulary (always present — the config gate restricts the value). */
|
|
190
|
+
export function providerFor(vocabulary) {
|
|
191
|
+
return PROVIDERS[vocabulary];
|
|
192
|
+
}
|
package/lib/types/router.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ResolvedConfig, ResolvedEngineConfig } from './config.js';
|
|
11
11
|
import { type EngineDeps, type ProducedImage } from './engine.js';
|
|
12
|
-
import {
|
|
12
|
+
import type { StandardImageRequest } from './translate.js';
|
|
13
13
|
/** One recorded attempt against one engine, success or failure. */
|
|
14
14
|
export interface AttemptView {
|
|
15
15
|
/** Engine id. */
|
|
@@ -131,8 +131,6 @@ export declare class EngineRouter {
|
|
|
131
131
|
private chainOrder;
|
|
132
132
|
/** Why an engine may not even be attempted: disabled, cooling down. */
|
|
133
133
|
private skipReason;
|
|
134
|
-
/** Translate the standard request against one engine (the pure translate step). */
|
|
135
|
-
private translate;
|
|
136
134
|
/** Record a success: reset consecutive failures and cooldown. */
|
|
137
135
|
private recordSuccess;
|
|
138
136
|
/** Record a failure: bump the counter, trip cooldown at the threshold, and build the attempt view. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/router.ts"],"names":[],"mappings":"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"}
|
package/lib/types/router.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module dsh-draw/router
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import { EngineCallError } from './engine.js';
|
|
11
|
+
import { providerFor } from './provider.js';
|
|
11
12
|
import { sanitizeError, sanitizeText, sanitizeUrl } from './sanitize.js';
|
|
12
|
-
import { translateRequest } from './translate.js';
|
|
13
13
|
/**
|
|
14
14
|
* The engine chain with per-engine health and cooldown. All mutations are
|
|
15
15
|
* synchronous bookkeeping guarded by `generate`'s single-writer path (the
|
|
@@ -56,7 +56,7 @@ export class EngineRouter {
|
|
|
56
56
|
}
|
|
57
57
|
tried += 1;
|
|
58
58
|
try {
|
|
59
|
-
const images = await
|
|
59
|
+
const images = await providerFor(engine.provider).generate(engine, request, deps, signal);
|
|
60
60
|
this.recordSuccess(engine.id);
|
|
61
61
|
attempts.push({ engine: engine.id, code: 'ok' });
|
|
62
62
|
return {
|
|
@@ -172,10 +172,6 @@ export class EngineRouter {
|
|
|
172
172
|
}
|
|
173
173
|
return undefined;
|
|
174
174
|
}
|
|
175
|
-
/** Translate the standard request against one engine (the pure translate step). */
|
|
176
|
-
translate(engine, request) {
|
|
177
|
-
return translateRequest(engine, request);
|
|
178
|
-
}
|
|
179
175
|
/** Record a success: reset consecutive failures and cooldown. */
|
|
180
176
|
recordSuccess(engineId) {
|
|
181
177
|
const health = this.health.get(engineId);
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The dsh-draw session event: one durable record per completed generation
|
|
3
3
|
* (tool call or panel regenerate), appended to the owning session log. It is
|
|
4
|
-
* log-only
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* log-only and exists so quota accounting and the durable audit trail are
|
|
5
|
+
* reconstructable from the log alone. The model-visible input and output of
|
|
6
|
+
* the call ride the loop-owned `tool/call` and `tool/result` events; this
|
|
7
|
+
* event carries the accounting facts those events do not.
|
|
8
8
|
*
|
|
9
9
|
* The event type is declared only here, so plugin paths append it through
|
|
10
10
|
* {@link commitDrawGenerated}: the adaptive gate (event-gate.ts) appends only
|
|
11
|
-
* when the host knows the type or honors the `ignorable` envelope
|
|
12
|
-
* otherwise records the payload in the in-memory fallback
|
|
13
|
-
* quota keeps working; the durable trail resumes once
|
|
14
|
-
* event surface).
|
|
11
|
+
* when the host knows the type or honors the `ignorable` envelope (pre-0.1.2
|
|
12
|
+
* host lines), and otherwise records the payload in the in-memory fallback
|
|
13
|
+
* ledger (live-session quota keeps working; the durable trail resumes once
|
|
14
|
+
* the host gains a plugin event surface). `0.1.2-alpha.1` removed the
|
|
15
|
+
* envelope and fails closed on unknown event types at read, so the probe
|
|
16
|
+
* reports no support there and every commit degrades to the ledger.
|
|
15
17
|
*
|
|
16
18
|
* @module dsh-draw/session-events
|
|
17
19
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-events.d.ts","sourceRoot":"","sources":["../../src/session-events.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session-events.d.ts","sourceRoot":"","sources":["../../src/session-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,QAAQ,gCAAgC,CAAC;IAC9C,UAAU,eAAe;QACvB;;;WAGG;QACH,gBAAgB,EAAE,kBAAkB,CAAA;KACrC;CACF;AAED,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAA;IACd,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,MAAM,EAAE,MAAM,GAAG,YAAY,CAAA;IAC7B,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAA;IACd,qDAAqD;IACrD,IAAI,EAAE,QAAQ,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,CAAA;IAClD,6CAA6C;IAC7C,OAAO,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAA;IAC3C,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,kBAAkB;;;;;;WAE9E;AAaD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAiBvI;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,kBAAkB,EAAE,CAE3F;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,OAAO,0BAA0B,EAAE,YAAY,CAAC,gBAAgB,CAAC,EAAE,CAElI"}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The dsh-draw session event: one durable record per completed generation
|
|
3
3
|
* (tool call or panel regenerate), appended to the owning session log. It is
|
|
4
|
-
* log-only
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* log-only and exists so quota accounting and the durable audit trail are
|
|
5
|
+
* reconstructable from the log alone. The model-visible input and output of
|
|
6
|
+
* the call ride the loop-owned `tool/call` and `tool/result` events; this
|
|
7
|
+
* event carries the accounting facts those events do not.
|
|
8
8
|
*
|
|
9
9
|
* The event type is declared only here, so plugin paths append it through
|
|
10
10
|
* {@link commitDrawGenerated}: the adaptive gate (event-gate.ts) appends only
|
|
11
|
-
* when the host knows the type or honors the `ignorable` envelope
|
|
12
|
-
* otherwise records the payload in the in-memory fallback
|
|
13
|
-
* quota keeps working; the durable trail resumes once
|
|
14
|
-
* event surface).
|
|
11
|
+
* when the host knows the type or honors the `ignorable` envelope (pre-0.1.2
|
|
12
|
+
* host lines), and otherwise records the payload in the in-memory fallback
|
|
13
|
+
* ledger (live-session quota keeps working; the durable trail resumes once
|
|
14
|
+
* the host gains a plugin event surface). `0.1.2-alpha.1` removed the
|
|
15
|
+
* envelope and fails closed on unknown event types at read, so the probe
|
|
16
|
+
* reports no support there and every commit degrades to the ledger.
|
|
15
17
|
*
|
|
16
18
|
* @module dsh-draw/session-events
|
|
17
19
|
*/
|
|
@@ -29,7 +31,8 @@ export function appendDrawGenerated(session, event) {
|
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
33
|
* In-memory accounting ledger for hosts whose session log cannot carry
|
|
32
|
-
* `draw/generated` safely (rc.6/rc.7 static whitelist
|
|
34
|
+
* `draw/generated` safely (rc.6/rc.7 static whitelist and the envelope-less
|
|
35
|
+
* `0.1.2-alpha.1`, which fails closed on unknown types at read).
|
|
33
36
|
* Keyed by Session identity: the entries live exactly as long as the session.
|
|
34
37
|
*/
|
|
35
38
|
const fallbackLedger = new WeakMap();
|
package/lib/types/version.d.ts
CHANGED
package/lib/types/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-draw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Unified static-image generation router for DeepSeek Harness: one image_generate tool with standard parameters, config-driven OpenAI-compatible engine routing (OpenAI Images, Zhipu CogView, and any compatible endpoint) with health-aware fallback, durable workspace attachment results, per-session quota accounting, an in-conversation result card, and a Plugins settings panel that stores API keys as credential references.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -57,7 +57,6 @@
|
|
|
57
57
|
"platform": "web",
|
|
58
58
|
"inject": [
|
|
59
59
|
"@deepseek-ai/dsh-client-connection",
|
|
60
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
61
60
|
"@deepseek-ai/dsh-client-locale",
|
|
62
61
|
"@deepseek-ai/dsh-client-ui-slots",
|
|
63
62
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
@@ -89,7 +88,9 @@
|
|
|
89
88
|
"attachments:write"
|
|
90
89
|
],
|
|
91
90
|
"compatibility": {
|
|
92
|
-
"dshVersions": [
|
|
91
|
+
"dshVersions": [
|
|
92
|
+
"0.1.1-rc.2"
|
|
93
|
+
]
|
|
93
94
|
},
|
|
94
95
|
"capability": {
|
|
95
96
|
"id": "image-generate-tool",
|
|
@@ -129,7 +130,29 @@
|
|
|
129
130
|
"@deepseek-ai/dsh-session": ">=0.1.0-rc.8 <0.2.0",
|
|
130
131
|
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.8 <0.2.0",
|
|
131
132
|
"@deepseek-ai/dsh-typert-protocol": ">=0.1.0-rc.8 <0.2.0",
|
|
132
|
-
"@deepseek-ai/schemastery": "^3.18.0"
|
|
133
|
+
"@deepseek-ai/schemastery": "^3.18.0",
|
|
134
|
+
"@deepseek-ai/dsh-client-connection": ">=0.1.1-rc.2 <0.2.0",
|
|
135
|
+
"@deepseek-ai/dsh-client-locale": ">=0.1.1-rc.2 <0.2.0",
|
|
136
|
+
"@deepseek-ai/dsh-client-ui-settings": ">=0.1.1-rc.2 <0.2.0",
|
|
137
|
+
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.1-rc.2 <0.2.0",
|
|
138
|
+
"@deepseek-ai/dsh-client-ui-tool": ">=0.1.1-rc.2 <0.2.0"
|
|
139
|
+
},
|
|
140
|
+
"peerDependenciesMeta": {
|
|
141
|
+
"@deepseek-ai/dsh-client-connection": {
|
|
142
|
+
"optional": true
|
|
143
|
+
},
|
|
144
|
+
"@deepseek-ai/dsh-client-locale": {
|
|
145
|
+
"optional": true
|
|
146
|
+
},
|
|
147
|
+
"@deepseek-ai/dsh-client-ui-settings": {
|
|
148
|
+
"optional": true
|
|
149
|
+
},
|
|
150
|
+
"@deepseek-ai/dsh-client-ui-slots": {
|
|
151
|
+
"optional": true
|
|
152
|
+
},
|
|
153
|
+
"@deepseek-ai/dsh-client-ui-tool": {
|
|
154
|
+
"optional": true
|
|
155
|
+
}
|
|
133
156
|
},
|
|
134
157
|
"dependencies": {
|
|
135
158
|
"tsdown": "^0.22.14",
|
|
@@ -147,7 +170,6 @@
|
|
|
147
170
|
"@deepseek-ai/dsh-brand": "0.1.1-rc.2",
|
|
148
171
|
"@deepseek-ai/dsh-client-connection": "0.1.1-rc.2",
|
|
149
172
|
"@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
|
|
150
|
-
"@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
|
|
151
173
|
"@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
|
|
152
174
|
"@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
|
|
153
175
|
"@deepseek-ai/dsh-client-ui-tool": "0.1.1-rc.2",
|
|
@@ -180,5 +202,9 @@
|
|
|
180
202
|
"verify:self-contained": "node scripts/verify-self-contained.mjs",
|
|
181
203
|
"verify:artifacts": "node scripts/verify-artifacts.mjs"
|
|
182
204
|
},
|
|
183
|
-
"license": "Apache-2.0"
|
|
205
|
+
"license": "Apache-2.0",
|
|
206
|
+
"funding": {
|
|
207
|
+
"type": "individual",
|
|
208
|
+
"url": "https://github.com/sponsors/PerryLink"
|
|
209
|
+
}
|
|
184
210
|
}
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { createElement as h, useState, type ReactElement } from 'react'
|
|
15
15
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
16
|
-
import type
|
|
17
|
-
import { presentDrawResult } from './present.ts'
|
|
16
|
+
import { presentDrawResult, type ToolCallBlock } from './present.ts'
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* Standard owner currency for one keyed tool view (mirror of the harness
|
package/src/client/index.ts
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* @module dsh-draw/client
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { ClientContext } from '@deepseek-ai/
|
|
11
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
|
12
|
+
// Type-only: activates the ctx.remote Context merge (TypertClientRemote).
|
|
13
|
+
import type {} from '@deepseek-ai/dsh-api-remotes/client'
|
|
12
14
|
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
13
15
|
// Type-only: pulls the 'settings.plugins.tab' SlotMap declaration into this
|
|
14
16
|
// program so the tab registration typechecks against the real declaration.
|
|
@@ -44,6 +46,18 @@ export const name = 'dsh-draw'
|
|
|
44
46
|
/** Services the browser half reads; `remote.draw` appears once the contribution mounts. */
|
|
45
47
|
export const inject = ['slots', 'locale', 'remote', 'sessions']
|
|
46
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Structural face of the slots service. Declared locally because the owning
|
|
51
|
+
* merge publishes differently across host lines (the removed
|
|
52
|
+
* `dsh-client-runtime` on 0.1.1-rc.2, the unpublished ui-renderer domain on
|
|
53
|
+
* 0.1.2-alpha.1); the runtime contract is structural and read through
|
|
54
|
+
* `ctx.get` without a dependency edge.
|
|
55
|
+
*/
|
|
56
|
+
interface SlotsFace {
|
|
57
|
+
inject(slot: string, callback: () => unknown): void
|
|
58
|
+
register(options: unknown, component: unknown): unknown
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
/**
|
|
48
62
|
* Browser plugin body: dictionaries, the scoped stylesheet, the Remote
|
|
49
63
|
* contribution mount, the result card, and the settings tab registration.
|
|
@@ -60,6 +74,7 @@ export async function apply(ctx: ClientContext): Promise<void> {
|
|
|
60
74
|
|
|
61
75
|
ctx.inject(['remote.draw'], (scope) => {
|
|
62
76
|
const t = scope.locale.bind(NS)
|
|
77
|
+
const slots = scope.get('slots') as SlotsFace
|
|
63
78
|
const unwrap = <T>(result: RemoteResult<T>, method: string): T => {
|
|
64
79
|
if (!result.ok) {
|
|
65
80
|
throw new Error(`draw.${method} failed: ${result.error.code}: ${result.error.message}`)
|
|
@@ -67,7 +82,7 @@ export async function apply(ctx: ClientContext): Promise<void> {
|
|
|
67
82
|
return result.value
|
|
68
83
|
}
|
|
69
84
|
|
|
70
|
-
|
|
85
|
+
slots.inject('tool.call.toolview', () => slots.register({
|
|
71
86
|
name: 'tool.call.toolview',
|
|
72
87
|
key: 'image_generate',
|
|
73
88
|
locale: NS,
|
|
@@ -80,7 +95,7 @@ export async function apply(ctx: ClientContext): Promise<void> {
|
|
|
80
95
|
}),
|
|
81
96
|
}, DrawResultCard))
|
|
82
97
|
|
|
83
|
-
|
|
98
|
+
slots.inject('settings.plugins.tab', () => slots.register({
|
|
84
99
|
name: 'settings.plugins.tab',
|
|
85
100
|
id: 'draw',
|
|
86
101
|
order: 40,
|
package/src/client/present.ts
CHANGED
|
@@ -7,9 +7,50 @@
|
|
|
7
7
|
* @module dsh-draw/client/present
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { ToolCallBlock } from '@deepseek-ai/dsh-client-runtime/client'
|
|
11
10
|
import type { DrawStatusSnapshot } from '../wire.ts'
|
|
12
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Local structural contract for the frozen tool-call block the card reads.
|
|
14
|
+
* Declared locally because the owning packages publish the block union
|
|
15
|
+
* differently across host lines: the published `0.1.1-rc.2` line keeps it in
|
|
16
|
+
* the removed `dsh-client-runtime` package, and the unreleased
|
|
17
|
+
* `0.1.2-alpha.1` host owns it in the unpublished `dsh-client-ui-chat`. The
|
|
18
|
+
* runtime contract is structural — the presenter only discriminates on
|
|
19
|
+
* `kind` and reads the call head, error flag, and meta.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** One frozen running tool call (no `kind`; the presenter's first guard drops it). */
|
|
23
|
+
export interface RunningToolCallBlock {
|
|
24
|
+
/** Tool call identity, stable across running and settled forms. */
|
|
25
|
+
callId: string
|
|
26
|
+
/** Wire tool name. */
|
|
27
|
+
name: string
|
|
28
|
+
/** Verbatim serialized arguments. */
|
|
29
|
+
argsRaw: string
|
|
30
|
+
/** Owning turn. */
|
|
31
|
+
turn: number
|
|
32
|
+
/** Owning step. */
|
|
33
|
+
step: number
|
|
34
|
+
/** Unix epoch ms when the call was logged. */
|
|
35
|
+
time: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** One settled tool result (fields the presenter reads). */
|
|
39
|
+
export interface SettledToolResultBlock {
|
|
40
|
+
kind: 'tool-result'
|
|
41
|
+
isError: boolean
|
|
42
|
+
/** Call head; null when window truncation left the call outside. */
|
|
43
|
+
call: { name: string; argsRaw: string } | null
|
|
44
|
+
/** Tool-owned presentation metadata (the card's engine/quota/limits facts). */
|
|
45
|
+
meta?: unknown
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Frozen running-or-settled tool block. */
|
|
49
|
+
export type ToolCallBlock = RunningToolCallBlock | SettledToolResultBlock
|
|
50
|
+
|
|
51
|
+
/** Settled tool-result node only. */
|
|
52
|
+
export type ToolResultNode = SettledToolResultBlock
|
|
53
|
+
|
|
13
54
|
/** One image of the presented result card. */
|
|
14
55
|
export interface PresentedImage {
|
|
15
56
|
attachmentId: string
|
package/src/config.ts
CHANGED
|
@@ -70,6 +70,9 @@ export interface EngineSizeMap {
|
|
|
70
70
|
/** Whether generated images arrive as base64 payloads or downloadable URLs. */
|
|
71
71
|
export type EngineResponseFormat = 'b64_json' | 'url'
|
|
72
72
|
|
|
73
|
+
/** The image-generation vocabularies a configured engine may declare (provider seam). */
|
|
74
|
+
export type EngineVocabulary = 'openai' | 'replicate' | 'fal'
|
|
75
|
+
|
|
73
76
|
/** One OpenAI-compatible image-generation endpoint the router may use. */
|
|
74
77
|
export interface EngineConfig {
|
|
75
78
|
/** Stable engine id used in tool args, results, and the settings panel. */
|
|
@@ -80,6 +83,8 @@ export interface EngineConfig {
|
|
|
80
83
|
model: string
|
|
81
84
|
/** Credential reference (environment-variable name) the API key resolves from; never a literal key. */
|
|
82
85
|
apiKeyRef: string
|
|
86
|
+
/** Which request/response vocabulary this engine speaks (default `openai`). */
|
|
87
|
+
provider?: EngineVocabulary
|
|
83
88
|
/** Whether the router may use this engine (default true). */
|
|
84
89
|
enabled?: boolean
|
|
85
90
|
/** Standard-size → concrete-size translation (default: the OpenAI sizes). */
|
|
@@ -123,6 +128,7 @@ export const DEFAULT_ENGINES: ReadonlyArray<Required<EngineConfig>> = Object.fre
|
|
|
123
128
|
baseUrl: 'https://api.openai.com/v1',
|
|
124
129
|
model: 'gpt-image-1',
|
|
125
130
|
apiKeyRef: 'OPENAI_API_KEY',
|
|
131
|
+
provider: 'openai',
|
|
126
132
|
enabled: true,
|
|
127
133
|
sizeMap: Object.freeze({ square: '1024x1024', landscape: '1536x1024', portrait: '1024x1536', auto: 'auto' }),
|
|
128
134
|
qualitySupported: true,
|
|
@@ -135,6 +141,7 @@ export const DEFAULT_ENGINES: ReadonlyArray<Required<EngineConfig>> = Object.fre
|
|
|
135
141
|
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
|
136
142
|
model: 'cogview-3-flash',
|
|
137
143
|
apiKeyRef: 'ZHIPU_API_KEY',
|
|
144
|
+
provider: 'openai',
|
|
138
145
|
enabled: true,
|
|
139
146
|
sizeMap: Object.freeze({ square: '1024x1024', landscape: '1344x768', portrait: '768x1344', auto: '1024x1024' }),
|
|
140
147
|
qualitySupported: false,
|
|
@@ -162,6 +169,8 @@ export interface ResolvedEngineConfig {
|
|
|
162
169
|
model: string
|
|
163
170
|
/** Credential reference the API key resolves from. */
|
|
164
171
|
apiKeyRef: string
|
|
172
|
+
/** Request/response vocabulary this engine speaks. */
|
|
173
|
+
provider: EngineVocabulary
|
|
165
174
|
/** Whether the router may use this engine. */
|
|
166
175
|
enabled: boolean
|
|
167
176
|
/** Standard-size → concrete-size translation. */
|
|
@@ -205,6 +214,7 @@ export const Config: z<Config> = z.object({
|
|
|
205
214
|
baseUrl: z.string().min(1).max(2048),
|
|
206
215
|
model: z.string().min(1).max(128),
|
|
207
216
|
apiKeyRef: z.string().min(1).max(128),
|
|
217
|
+
provider: z.union(['openai', 'replicate', 'fal'] as const),
|
|
208
218
|
enabled: z.boolean(),
|
|
209
219
|
sizeMap: z.object({
|
|
210
220
|
square: z.string().min(1).max(64),
|
|
@@ -269,6 +279,10 @@ export function resolveConfig(config: Config | undefined): ResolvedConfig {
|
|
|
269
279
|
if (model === '') throw new Error(`dsh-draw: engine "${id}" model must be a non-empty string of at most 128 characters`)
|
|
270
280
|
const apiKeyRef = typeof engine.apiKeyRef === 'string' && API_KEY_REF_PATTERN.test(engine.apiKeyRef) ? engine.apiKeyRef : ''
|
|
271
281
|
if (apiKeyRef === '') throw new Error(`dsh-draw: engine "${id}" apiKeyRef ${JSON.stringify(engine.apiKeyRef)} must be an environment-variable name`)
|
|
282
|
+
const provider = engine.provider ?? 'openai'
|
|
283
|
+
if (provider !== 'openai' && provider !== 'replicate' && provider !== 'fal') {
|
|
284
|
+
throw new Error(`dsh-draw: engine "${id}" provider must be one of "openai", "replicate", "fal", got ${JSON.stringify(provider)}`)
|
|
285
|
+
}
|
|
272
286
|
const sizeMap = engine.sizeMap === undefined ? DEFAULT_SIZE_MAP : engine.sizeMap
|
|
273
287
|
for (const key of ['square', 'landscape', 'portrait', 'auto'] as const) {
|
|
274
288
|
const value = sizeMap[key]
|
|
@@ -295,6 +309,7 @@ export function resolveConfig(config: Config | undefined): ResolvedConfig {
|
|
|
295
309
|
baseUrl: baseUrl.replace(/\/+$/u, ''),
|
|
296
310
|
model,
|
|
297
311
|
apiKeyRef,
|
|
312
|
+
provider,
|
|
298
313
|
enabled,
|
|
299
314
|
sizeMap: Object.freeze({ ...sizeMap }),
|
|
300
315
|
qualitySupported,
|
package/src/event-gate.ts
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* package's types, so it sits outside every host's `KNOWN_SESSION_EVENT_TYPES`:
|
|
4
4
|
* hosts whose session reader validates a static whitelist (rc.6/rc.7) refuse to
|
|
5
5
|
* reopen a session containing the event unless it carries the `ignorable`
|
|
6
|
-
* envelope — and those hosts' `Session.append` cannot stamp it. The
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* envelope — and those hosts' `Session.append` cannot stamp it. The
|
|
7
|
+
* `0.1.2-alpha.1` host removed the envelope entirely and fails closed on
|
|
8
|
+
* unknown event types at read, so the probe never reports support there. The
|
|
9
|
+
* gate therefore appends only when the type is host-known or a mount-time
|
|
10
|
+
* probe proves envelope support; otherwise the accounting payload goes to the
|
|
9
11
|
* in-memory fallback ledger (see session-events.ts) so quota keeps working for
|
|
10
12
|
* the live session without poisoning the log.
|
|
11
13
|
*
|
|
@@ -44,15 +46,16 @@ export function makeEventGate(knownTypes: ReadonlySet<string>, ignorableAppend =
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
/** The append face the probe needs;
|
|
49
|
+
/** The append face the probe needs; envelope-less hosts declare no options parameter. */
|
|
48
50
|
type EnvelopeAppend = (type: string, data: unknown, opts: { ignorable: boolean }) => { ignorable?: boolean } | undefined
|
|
49
51
|
|
|
50
52
|
/**
|
|
51
53
|
* Probe whether the host `Session.append` stamps the ignorable envelope. Runs
|
|
52
54
|
* on a fully detached SessionStore — never host persistence — appending one
|
|
53
55
|
* probe event with `{ ignorable: true }` and reading the marker back. rc.6's
|
|
54
|
-
* append silently drops the unknown options key (probe false)
|
|
55
|
-
*
|
|
56
|
+
* append silently drops the unknown options key (probe false), and the
|
|
57
|
+
* envelope-less `0.1.2-alpha.1` append takes no options at all (probe false);
|
|
58
|
+
* every failure is contained as "unsupported".
|
|
56
59
|
*
|
|
57
60
|
* @returns whether the envelope is honored.
|
|
58
61
|
*/
|