dsh-draw 0.1.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.
Files changed (104) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +201 -0
  3. package/README.es.md +194 -0
  4. package/README.hi.md +194 -0
  5. package/README.md +194 -0
  6. package/README.pt.md +194 -0
  7. package/README.zh.md +194 -0
  8. package/SECURITY.md +39 -0
  9. package/THIRD_PARTY_NOTICES.md +21 -0
  10. package/cordis.patch.yml +48 -0
  11. package/lib/client.js +4787 -0
  12. package/lib/client.js.map +1 -0
  13. package/lib/index.js +1429 -0
  14. package/lib/typert.host.js +26 -0
  15. package/lib/types/client/DrawResultCard.d.ts +58 -0
  16. package/lib/types/client/DrawResultCard.d.ts.map +1 -0
  17. package/lib/types/client/DrawResultCard.js +48 -0
  18. package/lib/types/client/DrawSettingsTab.d.ts +31 -0
  19. package/lib/types/client/DrawSettingsTab.d.ts.map +1 -0
  20. package/lib/types/client/DrawSettingsTab.js +129 -0
  21. package/lib/types/client/index.d.ts +35 -0
  22. package/lib/types/client/index.d.ts.map +1 -0
  23. package/lib/types/client/index.js +98 -0
  24. package/lib/types/client/locales.d.ts +14 -0
  25. package/lib/types/client/locales.d.ts.map +1 -0
  26. package/lib/types/client/locales.js +57 -0
  27. package/lib/types/client/present.d.ts +80 -0
  28. package/lib/types/client/present.d.ts.map +1 -0
  29. package/lib/types/client/present.js +86 -0
  30. package/lib/types/client/remote.d.ts +268 -0
  31. package/lib/types/client/remote.d.ts.map +1 -0
  32. package/lib/types/client/remote.js +15 -0
  33. package/lib/types/client/styles.d.ts +11 -0
  34. package/lib/types/client/styles.d.ts.map +1 -0
  35. package/lib/types/client/styles.js +43 -0
  36. package/lib/types/config.d.ts +160 -0
  37. package/lib/types/config.d.ts.map +1 -0
  38. package/lib/types/config.js +230 -0
  39. package/lib/types/drawer.d.ts +114 -0
  40. package/lib/types/drawer.d.ts.map +1 -0
  41. package/lib/types/drawer.js +138 -0
  42. package/lib/types/engine.d.ts +58 -0
  43. package/lib/types/engine.d.ts.map +1 -0
  44. package/lib/types/engine.js +135 -0
  45. package/lib/types/http.d.ts +89 -0
  46. package/lib/types/http.d.ts.map +1 -0
  47. package/lib/types/http.js +127 -0
  48. package/lib/types/index.d.ts +43 -0
  49. package/lib/types/index.d.ts.map +1 -0
  50. package/lib/types/index.js +78 -0
  51. package/lib/types/quota.d.ts +69 -0
  52. package/lib/types/quota.d.ts.map +1 -0
  53. package/lib/types/quota.js +56 -0
  54. package/lib/types/router.d.ts +141 -0
  55. package/lib/types/router.d.ts.map +1 -0
  56. package/lib/types/router.js +207 -0
  57. package/lib/types/sanitize.d.ts +40 -0
  58. package/lib/types/sanitize.d.ts.map +1 -0
  59. package/lib/types/sanitize.js +103 -0
  60. package/lib/types/service.d.ts +59 -0
  61. package/lib/types/service.d.ts.map +1 -0
  62. package/lib/types/service.js +131 -0
  63. package/lib/types/session-events.d.ts +66 -0
  64. package/lib/types/session-events.d.ts.map +1 -0
  65. package/lib/types/session-events.js +32 -0
  66. package/lib/types/tool.d.ts +30 -0
  67. package/lib/types/tool.d.ts.map +1 -0
  68. package/lib/types/tool.js +131 -0
  69. package/lib/types/translate.d.ts +64 -0
  70. package/lib/types/translate.d.ts.map +1 -0
  71. package/lib/types/translate.js +56 -0
  72. package/lib/types/typert.host.d.ts +250 -0
  73. package/lib/types/typert.host.d.ts.map +1 -0
  74. package/lib/types/typert.host.js +23 -0
  75. package/lib/types/version.d.ts +10 -0
  76. package/lib/types/version.d.ts.map +1 -0
  77. package/lib/types/version.js +9 -0
  78. package/lib/types/wire.d.ts +699 -0
  79. package/lib/types/wire.d.ts.map +1 -0
  80. package/lib/types/wire.js +273 -0
  81. package/lib/wire-Cc4JZ3jR.js +4370 -0
  82. package/package.json +179 -0
  83. package/src/client/DrawResultCard.tsx +100 -0
  84. package/src/client/DrawSettingsTab.tsx +159 -0
  85. package/src/client/index.ts +123 -0
  86. package/src/client/locales.ts +84 -0
  87. package/src/client/present.ts +137 -0
  88. package/src/client/remote.ts +44 -0
  89. package/src/client/styles.ts +44 -0
  90. package/src/config.ts +358 -0
  91. package/src/drawer.ts +234 -0
  92. package/src/engine.ts +182 -0
  93. package/src/http.ts +161 -0
  94. package/src/index.ts +93 -0
  95. package/src/quota.ts +98 -0
  96. package/src/router.ts +309 -0
  97. package/src/sanitize.ts +113 -0
  98. package/src/service.ts +169 -0
  99. package/src/session-events.ts +70 -0
  100. package/src/tool.ts +145 -0
  101. package/src/translate.ts +101 -0
  102. package/src/typert.host.ts +25 -0
  103. package/src/version.ts +10 -0
  104. package/src/wire.ts +417 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The OpenAI-compatible images adapter: one implementation covers OpenAI
3
+ * Images, Zhipu CogView, and any config-driven compatible endpoint. The
4
+ * request is a `POST {baseUrl}/images/generations` JSON body; the response is
5
+ * `{ data: [{ b64_json } | { url }] }`. Engines declare their response format
6
+ * and media type in config, so no provider-specific code branches exist.
7
+ *
8
+ * @module dsh-draw/engine
9
+ */
10
+ import { decodeBase64, fusedSignal } from './http.js';
11
+ import { sanitizeError } from './sanitize.js';
12
+ /**
13
+ * A single engine call failure. `message` is display-safe (never carries the
14
+ * API key); `status` carries the HTTP status when a response existed.
15
+ */
16
+ export class EngineCallError extends Error {
17
+ /** Which stage failed. */
18
+ phase;
19
+ /** Stable machine code: `unconfigured`, `auth`, `http`, `parse`. */
20
+ code;
21
+ /** HTTP status when a response existed. */
22
+ status;
23
+ /** @param phase - failing stage. @param code - stable code. @param message - display-safe message. @param options - optional status and cause. */
24
+ constructor(phase, code, message, options) {
25
+ super(message, options?.cause === undefined ? undefined : { cause: options.cause });
26
+ this.name = 'EngineCallError';
27
+ this.phase = phase;
28
+ this.code = code;
29
+ if (options?.status !== undefined)
30
+ this.status = options.status;
31
+ }
32
+ }
33
+ /** Timeout for one image URL download (fraction of the request budget). */
34
+ const DOWNLOAD_TIMEOUT_MS = 60_000;
35
+ /**
36
+ * Call one engine for the given translated request.
37
+ *
38
+ * @param engine - resolved engine configuration.
39
+ * @param request - translated request body fields.
40
+ * @param deps - transport and credential resolution.
41
+ * @param signal - caller cancellation.
42
+ * @returns the produced images.
43
+ * @throws {@link EngineCallError} with a phase the router can act on.
44
+ */
45
+ export async function callEngine(engine, request, deps, signal) {
46
+ const credential = await deps.resolveCredential(engine.apiKeyRef);
47
+ if (credential === undefined) {
48
+ throw new EngineCallError('credential', 'unconfigured', `engine "${engine.id}" has no resolved credential reference ${engine.apiKeyRef}`);
49
+ }
50
+ const headers = {
51
+ 'content-type': 'application/json',
52
+ authorization: `Bearer ${credential}`,
53
+ };
54
+ const body = {
55
+ model: request.model,
56
+ prompt: request.prompt,
57
+ size: request.size,
58
+ n: request.n,
59
+ ...(request.quality !== undefined ? { quality: request.quality } : {}),
60
+ ...(request.style !== undefined ? { style: request.style } : {}),
61
+ ...(request.responseFormat === 'b64_json' ? { response_format: request.responseFormat } : {}),
62
+ };
63
+ const response = await deps.transport.request({
64
+ method: 'POST',
65
+ url: `${engine.baseUrl}/images/generations`,
66
+ headers,
67
+ body: new TextEncoder().encode(JSON.stringify(body)),
68
+ ...(signal === undefined ? {} : { signal }),
69
+ });
70
+ if (response.status === 401 || response.status === 403) {
71
+ throw new EngineCallError('request', 'auth', `engine "${engine.id}" rejected the credential (HTTP ${response.status})`, { status: response.status });
72
+ }
73
+ if (response.status < 200 || response.status >= 300) {
74
+ throw new EngineCallError('request', 'http', `engine "${engine.id}" failed with HTTP ${response.status}`, { status: response.status });
75
+ }
76
+ let parsed;
77
+ try {
78
+ parsed = JSON.parse(new TextDecoder().decode(response.body));
79
+ }
80
+ catch (cause) {
81
+ throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned a non-JSON response`, { cause });
82
+ }
83
+ const items = Array.isArray(parsed.data) ? parsed.data : undefined;
84
+ if (items === undefined) {
85
+ throw new EngineCallError('parse', 'parse', `engine "${engine.id}" response has no data array`);
86
+ }
87
+ const images = [];
88
+ for (const raw of items) {
89
+ if (typeof raw !== 'object' || raw === null) {
90
+ throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned a malformed image entry`);
91
+ }
92
+ const item = raw;
93
+ if (typeof item.b64_json === 'string' && item.b64_json.length > 0) {
94
+ images.push({ data: decodeBase64(item.b64_json), mediaType: engine.imageMediaType });
95
+ continue;
96
+ }
97
+ if (typeof item.url === 'string' && item.url.length > 0) {
98
+ images.push({ data: await downloadImageUrl(engine, item.url, credential, deps, signal), mediaType: engine.imageMediaType });
99
+ continue;
100
+ }
101
+ throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned an image entry without bytes or a URL`);
102
+ }
103
+ if (images.length === 0) {
104
+ throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned no images`);
105
+ }
106
+ return images;
107
+ }
108
+ /**
109
+ * Download one image URL with the engine's bearer credential. The download is
110
+ * one GET request on the same transport; a non-2xx status is an engine
111
+ * failure, not silent emptiness.
112
+ */
113
+ async function downloadImageUrl(engine, url, credential, deps, signal) {
114
+ const { signal: downloadSignal, dispose } = fusedSignal(signal, DOWNLOAD_TIMEOUT_MS);
115
+ try {
116
+ const response = await deps.transport.request({
117
+ method: 'GET',
118
+ url,
119
+ headers: { authorization: `Bearer ${credential}` },
120
+ signal: downloadSignal,
121
+ });
122
+ if (response.status < 200 || response.status >= 300) {
123
+ throw new EngineCallError('request', 'http', `engine "${engine.id}" image download failed with HTTP ${response.status}`, { status: response.status });
124
+ }
125
+ return response.body;
126
+ }
127
+ catch (error) {
128
+ if (error instanceof EngineCallError)
129
+ throw error;
130
+ throw new EngineCallError('request', 'http', `engine "${engine.id}" image download failed: ${sanitizeError(error)}`, { cause: error });
131
+ }
132
+ finally {
133
+ dispose();
134
+ }
135
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The HTTP transport seam: engine calls go through one injectable
3
+ * request function so tests can pin every wire interaction without network
4
+ * I/O. The default implementation is Node's global `fetch` (undici) with a
5
+ * per-call timeout fused onto the caller's cancellation signal.
6
+ *
7
+ * @module dsh-draw/http
8
+ */
9
+ /** One transport request: URL, headers, optional body, and cancellation. */
10
+ export interface HttpRequest {
11
+ /** HTTP method. */
12
+ method: string;
13
+ /** Absolute target URL. */
14
+ url: string;
15
+ /** Header map (values are raw secrets — never logged). */
16
+ headers?: Readonly<Record<string, string>>;
17
+ /** Request body bytes; omitted for body-less requests. */
18
+ body?: Uint8Array<ArrayBuffer>;
19
+ /** Caller cancellation; transport failures must observe it. */
20
+ signal?: AbortSignal;
21
+ }
22
+ /** One transport response: status plus raw bytes. */
23
+ export interface HttpResponse {
24
+ /** HTTP status code. */
25
+ status: number;
26
+ /** Raw response body bytes. */
27
+ body: Uint8Array;
28
+ }
29
+ /** Machine-routable failure codes of the transport seam. */
30
+ export type HttpErrorCode = 'timeout' | 'aborted' | 'network' | 'invalid-response';
31
+ /**
32
+ * A transport-level failure. `status` is absent for network/timeout failures;
33
+ * `code` routes fallback and cooldown decisions.
34
+ */
35
+ export declare class HttpError extends Error {
36
+ /** Stable failure code. */
37
+ readonly code: HttpErrorCode;
38
+ /** HTTP status when a response existed. */
39
+ readonly status?: number;
40
+ /** @param code - stable failure code. @param message - display-safe message. @param options - optional status and cause. */
41
+ constructor(code: HttpErrorCode, message: string, options?: {
42
+ status?: number;
43
+ cause?: unknown;
44
+ });
45
+ }
46
+ /** Transport face the engine and probe layers consume. */
47
+ export interface HttpTransport {
48
+ /**
49
+ * Perform one request and return the raw response. Never resolves with a
50
+ * thrown engine business error; non-2xx statuses are ordinary results.
51
+ *
52
+ * @param request - method, URL, headers, body, and cancellation.
53
+ * @returns status plus raw bytes.
54
+ * @throws {@link HttpError} for timeouts, aborts, and network failures.
55
+ */
56
+ request(request: HttpRequest): Promise<HttpResponse>;
57
+ }
58
+ /**
59
+ * Fuse a caller signal with a per-call timeout into one signal and a disposer
60
+ * that clears the timer when the call settles.
61
+ *
62
+ * @param signal - caller signal, or undefined.
63
+ * @param timeoutMs - positive timeout.
64
+ * @returns the fused signal plus its disposer.
65
+ */
66
+ export declare function fusedSignal(signal: AbortSignal | undefined, timeoutMs: number): {
67
+ signal: AbortSignal;
68
+ dispose: () => void;
69
+ };
70
+ /**
71
+ * The production transport: global `fetch` (undici under Node ≥ 22) with the
72
+ * timeout fused onto the caller signal. The response body is read to bytes;
73
+ * oversized or unreadable bodies surface as `invalid-response`.
74
+ *
75
+ * @param timeoutMs - per-call timeout.
76
+ * @param maxBytes - response byte cap (the engine's image cap plus headroom).
77
+ * @returns a transport ready for the router.
78
+ */
79
+ export declare function defaultHttpTransport(timeoutMs: number, maxBytes: number): HttpTransport;
80
+ /**
81
+ * Decode a standard base64 string to bytes. A malformed string fails loud —
82
+ * a provider change would otherwise silently corrupt an image.
83
+ *
84
+ * @param data - base64 payload without a data: prefix.
85
+ * @returns decoded bytes.
86
+ * @throws when the payload is not valid base64.
87
+ */
88
+ export declare function decodeBase64(data: string): Uint8Array;
89
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,2BAA2B;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,0DAA0D;IAC1D,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAC1C,0DAA0D;IAC1D,IAAI,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,CAAA;IAC9B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,+BAA+B;IAC/B,IAAI,EAAE,UAAU,CAAA;CACjB;AAED,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,kBAAkB,CAAA;AAElF;;;GAGG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,4HAA4H;IAC5H,YAAY,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAK/F;CACF;AAED,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CACrD;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAgB5H;AAcD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAgCvF;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAMrD"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * The HTTP transport seam: engine calls go through one injectable
3
+ * request function so tests can pin every wire interaction without network
4
+ * I/O. The default implementation is Node's global `fetch` (undici) with a
5
+ * per-call timeout fused onto the caller's cancellation signal.
6
+ *
7
+ * @module dsh-draw/http
8
+ */
9
+ /**
10
+ * A transport-level failure. `status` is absent for network/timeout failures;
11
+ * `code` routes fallback and cooldown decisions.
12
+ */
13
+ export class HttpError extends Error {
14
+ /** Stable failure code. */
15
+ code;
16
+ /** HTTP status when a response existed. */
17
+ status;
18
+ /** @param code - stable failure code. @param message - display-safe message. @param options - optional status and cause. */
19
+ constructor(code, message, options) {
20
+ super(message, options?.cause === undefined ? undefined : { cause: options.cause });
21
+ this.name = 'HttpError';
22
+ this.code = code;
23
+ if (options?.status !== undefined)
24
+ this.status = options.status;
25
+ }
26
+ }
27
+ /**
28
+ * Fuse a caller signal with a per-call timeout into one signal and a disposer
29
+ * that clears the timer when the call settles.
30
+ *
31
+ * @param signal - caller signal, or undefined.
32
+ * @param timeoutMs - positive timeout.
33
+ * @returns the fused signal plus its disposer.
34
+ */
35
+ export function fusedSignal(signal, timeoutMs) {
36
+ if (signal === undefined) {
37
+ const controller = new AbortController();
38
+ const timer = setTimeout(() => controller.abort(new HttpError('timeout', `request exceeded ${timeoutMs} ms`)), timeoutMs);
39
+ return { signal: controller.signal, dispose: () => clearTimeout(timer) };
40
+ }
41
+ if (signal.aborted)
42
+ return { signal, dispose: () => undefined };
43
+ const controller = new AbortController();
44
+ const timer = setTimeout(() => controller.abort(new HttpError('timeout', `request exceeded ${timeoutMs} ms`)), timeoutMs);
45
+ const forward = () => { controller.abort(signal.reason); };
46
+ signal.addEventListener('abort', forward, { once: true });
47
+ const dispose = () => {
48
+ clearTimeout(timer);
49
+ signal.removeEventListener('abort', forward);
50
+ };
51
+ return { signal: controller.signal, dispose };
52
+ }
53
+ /** Map an undici/fetch rejection to an {@link HttpError} by its observable identity. */
54
+ function mapFetchFailure(signal, error) {
55
+ const reason = signal.reason;
56
+ if (signal.aborted && reason instanceof HttpError)
57
+ return reason;
58
+ if (signal.aborted)
59
+ return new HttpError('aborted', reason instanceof Error ? reason.message : 'request aborted', { cause: error });
60
+ if (error instanceof Error && error.name === 'TimeoutError') {
61
+ return new HttpError('timeout', error.message, { cause: error });
62
+ }
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ return new HttpError('network', message, { cause: error });
65
+ }
66
+ /**
67
+ * The production transport: global `fetch` (undici under Node ≥ 22) with the
68
+ * timeout fused onto the caller signal. The response body is read to bytes;
69
+ * oversized or unreadable bodies surface as `invalid-response`.
70
+ *
71
+ * @param timeoutMs - per-call timeout.
72
+ * @param maxBytes - response byte cap (the engine's image cap plus headroom).
73
+ * @returns a transport ready for the router.
74
+ */
75
+ export function defaultHttpTransport(timeoutMs, maxBytes) {
76
+ return {
77
+ async request(request) {
78
+ const { signal, dispose } = fusedSignal(request.signal, timeoutMs);
79
+ try {
80
+ let response;
81
+ try {
82
+ response = await fetch(request.url, {
83
+ method: request.method,
84
+ ...(request.headers === undefined ? {} : { headers: request.headers }),
85
+ ...(request.body === undefined ? {} : { body: request.body }),
86
+ signal,
87
+ redirect: 'follow',
88
+ });
89
+ }
90
+ catch (error) {
91
+ throw mapFetchFailure(signal, error);
92
+ }
93
+ let body;
94
+ try {
95
+ body = new Uint8Array(await response.arrayBuffer());
96
+ }
97
+ catch (error) {
98
+ throw new HttpError('invalid-response', `failed to read response body: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
99
+ }
100
+ if (body.byteLength > maxBytes) {
101
+ throw new HttpError('invalid-response', `response body exceeds ${maxBytes} bytes`, { status: response.status });
102
+ }
103
+ return { status: response.status, body };
104
+ }
105
+ finally {
106
+ dispose();
107
+ }
108
+ },
109
+ };
110
+ }
111
+ /**
112
+ * Decode a standard base64 string to bytes. A malformed string fails loud —
113
+ * a provider change would otherwise silently corrupt an image.
114
+ *
115
+ * @param data - base64 payload without a data: prefix.
116
+ * @returns decoded bytes.
117
+ * @throws when the payload is not valid base64.
118
+ */
119
+ export function decodeBase64(data) {
120
+ if (data.length === 0)
121
+ throw new HttpError('invalid-response', 'empty base64 image payload');
122
+ const binary = atob(data);
123
+ const bytes = new Uint8Array(binary.length);
124
+ for (let index = 0; index < binary.length; index += 1)
125
+ bytes[index] = binary.charCodeAt(index);
126
+ return bytes;
127
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * `dsh-draw` — the unified static-image generation router for DeepSeek Harness.
3
+ *
4
+ * Host half: resolves config, builds the health-aware engine router and the
5
+ * shared drawer (validation, quota, routing, durable attachment storage, and
6
+ * the `draw/generated` session audit event), registers the `image_generate`
7
+ * tool, and mounts the `draw` Typert Remote service the settings panel and
8
+ * result card consume. The browser half lives in `src/client/` and registers
9
+ * the keyed `tool.call.toolview` result card plus the Plugins settings tab.
10
+ *
11
+ * Function plugin — no default export (the Loader unwraps
12
+ * `exports.default ?? exports`).
13
+ *
14
+ * @module dsh-draw
15
+ */
16
+ import type { Context } from '@deepseek-ai/cordis';
17
+ import { Config } from './config.ts';
18
+ export declare const name = "dsh-draw";
19
+ /** Hard services: the tool registry every contribution lands in. */
20
+ export declare const inject: string[];
21
+ export { Config, resolveConfig, type Config as DrawConfig, type ResolvedConfig, DEFAULT_ENGINES, engineById } from './config.ts';
22
+ export { EngineRouter, type AttemptView, type EngineStatus, type ProbeOutcome } from './router.ts';
23
+ export { Drawer, type DrawImage, type DrawFailureReason, type DrawOutcome, type DrawOptions, type DrawSuccess } from './drawer.ts';
24
+ export { imageGenerateTool } from './tool.ts';
25
+ export { DrawService } from './service.ts';
26
+ export { defaultHttpTransport, fusedSignal, type HttpTransport, type HttpRequest, type HttpResponse, HttpError } from './http.ts';
27
+ export { callEngine, EngineCallError, type EngineDeps, type ProducedImage } from './engine.ts';
28
+ export { translateRequest, normalizeRequest, type StandardImageRequest } from './translate.ts';
29
+ export { quotaState, checkQuotaGenerations, checkQuotaBytes, type QuotaLimits, type QuotaState } from './quota.ts';
30
+ export { sanitizeUrl, sanitizeText, sanitizeError, REDACTED } from './sanitize.ts';
31
+ export { appendDrawGenerated, drawGeneratedEvents, type DrawGeneratedEvent } from './session-events.ts';
32
+ export { PLUGIN_VERSION } from './version.ts';
33
+ export { DRAW_INVOCATIONS, imageToWire, statusToView, probeToWire, type DrawStatusSnapshot } from './wire.ts';
34
+ /**
35
+ * Mount the plugin: router, drawer, the `image_generate` tool, and the `draw`
36
+ * Remote service. Every registration is an effect on this fiber, so
37
+ * unload/hot-reload removes the tool and the service together.
38
+ *
39
+ * @param ctx - context carrying tools plus the optional attachment/credentials seams.
40
+ * @param config - raw loader config; defaults applied through {@link resolveConfig}.
41
+ */
42
+ export declare function apply(ctx: Context, config: Config): Promise<void>;
43
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAMlD,OAAO,EAAE,MAAM,EAAiB,MAAM,aAAa,CAAA;AAOnD,eAAO,MAAM,IAAI,aAAa,CAAA;AAE9B,oEAAoE;AACpE,eAAO,MAAM,MAAM,UAAY,CAAA;AAE/B,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,MAAM,IAAI,UAAU,EAAE,KAAK,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAChI,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAClG,OAAO,EAAE,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAClI,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACjI,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAC9F,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,eAAe,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAClH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAClF,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACvG,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAK7G;;;;;;;GAOG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCvE"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * `dsh-draw` — the unified static-image generation router for DeepSeek Harness.
3
+ *
4
+ * Host half: resolves config, builds the health-aware engine router and the
5
+ * shared drawer (validation, quota, routing, durable attachment storage, and
6
+ * the `draw/generated` session audit event), registers the `image_generate`
7
+ * tool, and mounts the `draw` Typert Remote service the settings panel and
8
+ * result card consume. The browser half lives in `src/client/` and registers
9
+ * the keyed `tool.call.toolview` result card plus the Plugins settings tab.
10
+ *
11
+ * Function plugin — no default export (the Loader unwraps
12
+ * `exports.default ?? exports`).
13
+ *
14
+ * @module dsh-draw
15
+ */
16
+ import { credentialRef } from '@deepseek-ai/dsh-credentials';
17
+ import { resolveConfig } from './config.js';
18
+ import { Drawer } from './drawer.js';
19
+ import { defaultHttpTransport } from './http.js';
20
+ import { EngineRouter } from './router.js';
21
+ import { DrawService } from './service.js';
22
+ import { imageGenerateTool } from './tool.js';
23
+ export const name = 'dsh-draw';
24
+ /** Hard services: the tool registry every contribution lands in. */
25
+ export const inject = ['tools'];
26
+ export { Config, resolveConfig, DEFAULT_ENGINES, engineById } from './config.js';
27
+ export { EngineRouter } from './router.js';
28
+ export { Drawer } from './drawer.js';
29
+ export { imageGenerateTool } from './tool.js';
30
+ export { DrawService } from './service.js';
31
+ export { defaultHttpTransport, fusedSignal, HttpError } from './http.js';
32
+ export { callEngine, EngineCallError } from './engine.js';
33
+ export { translateRequest, normalizeRequest } from './translate.js';
34
+ export { quotaState, checkQuotaGenerations, checkQuotaBytes } from './quota.js';
35
+ export { sanitizeUrl, sanitizeText, sanitizeError, REDACTED } from './sanitize.js';
36
+ export { appendDrawGenerated, drawGeneratedEvents } from './session-events.js';
37
+ export { PLUGIN_VERSION } from './version.js';
38
+ export { DRAW_INVOCATIONS, imageToWire, statusToView, probeToWire } from './wire.js';
39
+ /** Response byte ceiling: one engine call may carry several full-size images. */
40
+ const MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
41
+ /**
42
+ * Mount the plugin: router, drawer, the `image_generate` tool, and the `draw`
43
+ * Remote service. Every registration is an effect on this fiber, so
44
+ * unload/hot-reload removes the tool and the service together.
45
+ *
46
+ * @param ctx - context carrying tools plus the optional attachment/credentials seams.
47
+ * @param config - raw loader config; defaults applied through {@link resolveConfig}.
48
+ */
49
+ export async function apply(ctx, config) {
50
+ const resolved = resolveConfig(config);
51
+ const logger = ctx.logger('draw');
52
+ // Optional test seam: an embedding context may pre-select the transport
53
+ // under 'dsh-draw/transport'; real deployments use the fetch transport.
54
+ const transport = ctx.get('dsh-draw/transport')
55
+ ?? defaultHttpTransport(resolved.requestTimeoutMs, MAX_RESPONSE_BYTES);
56
+ const router = new EngineRouter(resolved, {
57
+ failureThreshold: resolved.failureThreshold,
58
+ cooldownMs: resolved.cooldownMs,
59
+ });
60
+ const credentials = () => ctx.get('credentials');
61
+ const drawer = new Drawer(resolved, router, {
62
+ engine: {
63
+ transport,
64
+ resolveCredential: async (reference) => {
65
+ const service = credentials();
66
+ if (service === undefined)
67
+ return undefined;
68
+ const resolvedCredential = await service.resolve(credentialRef(reference));
69
+ return resolvedCredential?.value;
70
+ },
71
+ },
72
+ attachments: () => ctx.get('attachments'),
73
+ sessions: () => ctx.get('sessions'),
74
+ });
75
+ ctx.effect(() => ctx.tools.register(imageGenerateTool(drawer, resolved)), 'dsh-draw: image_generate tool');
76
+ await ctx.plugin(DrawService, { config: resolved, router, drawer, credentials: credentials() });
77
+ logger.info(`image generation enabled: ${resolved.engines.map(engine => engine.id).join(', ')} (preferred ${resolved.defaultEngine})`);
78
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Per-session quota accounting. The durable source of truth is the session
3
+ * log: every completed generation appends one `draw/generated` event, and
4
+ * quota is computed by folding those events, so usage survives restart and
5
+ * fork and cannot drift from what the log records.
6
+ *
7
+ * @module dsh-draw/quota
8
+ */
9
+ import type { Session } from '@deepseek-ai/dsh-session';
10
+ /** The two quota axes. */
11
+ export interface QuotaLimits {
12
+ /** Cap on generation calls (each call may produce several images). */
13
+ maxGenerations: number;
14
+ /** Cap on generated image bytes. */
15
+ maxBytes: number;
16
+ }
17
+ /** Current usage, folded from the session log. */
18
+ export interface QuotaState {
19
+ /** Generation calls recorded in the log. */
20
+ generations: number;
21
+ /** Sum of recorded image bytes. */
22
+ bytes: number;
23
+ }
24
+ /** A denied quota check with the blocking axis and current state. */
25
+ export interface QuotaDenial {
26
+ /** Discriminant. */
27
+ allowed: false;
28
+ /** Which axis blocked the call. */
29
+ reason: 'generations' | 'bytes';
30
+ /** Usage at decision time. */
31
+ state: QuotaState;
32
+ }
33
+ /** An allowed quota check with the current state. */
34
+ export interface QuotaAllowance {
35
+ /** Discriminant. */
36
+ allowed: true;
37
+ /** Usage at decision time. */
38
+ state: QuotaState;
39
+ }
40
+ /** Quota decision. */
41
+ export type QuotaCheck = QuotaAllowance | QuotaDenial;
42
+ /**
43
+ * Fold one session's `draw/generated` events into current usage.
44
+ *
45
+ * @param session - session whose log is folded.
46
+ * @returns generation and byte totals.
47
+ */
48
+ export declare function quotaState(session: Session): QuotaState;
49
+ /**
50
+ * Check the generation-call axis before any engine is contacted: a session at
51
+ * its cap fails fast without spending engine credits.
52
+ *
53
+ * @param session - owning session.
54
+ * @param limits - configured limits.
55
+ * @returns allowance or denial.
56
+ */
57
+ export declare function checkQuotaGenerations(session: Session, limits: QuotaLimits): QuotaCheck;
58
+ /**
59
+ * Check the byte axis after the engine produced images but before anything is
60
+ * stored: the incoming bytes must fit under the cap, otherwise the images are
61
+ * discarded without touching the attachment store.
62
+ *
63
+ * @param session - owning session.
64
+ * @param limits - configured limits.
65
+ * @param incomingBytes - bytes the new images would add.
66
+ * @returns allowance or denial.
67
+ */
68
+ export declare function checkQuotaBytes(session: Session, limits: QuotaLimits, incomingBytes: number): QuotaCheck;
69
+ //# sourceMappingURL=quota.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.d.ts","sourceRoot":"","sources":["../../src/quota.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AAGvD,0BAA0B;AAC1B,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAA;IACtB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,kDAAkD;AAClD,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAA;IACnB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;CACd;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,oBAAoB;IACpB,OAAO,EAAE,KAAK,CAAA;IACd,mCAAmC;IACnC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAA;IAC/B,8BAA8B;IAC9B,KAAK,EAAE,UAAU,CAAA;CAClB;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,oBAAoB;IACpB,OAAO,EAAE,IAAI,CAAA;IACb,8BAA8B;IAC9B,KAAK,EAAE,UAAU,CAAA;CAClB;AAED,sBAAsB;AACtB,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,WAAW,CAAA;AAErD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAQvD;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,UAAU,CAMvF;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,GAAG,UAAU,CAMxG"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Per-session quota accounting. The durable source of truth is the session
3
+ * log: every completed generation appends one `draw/generated` event, and
4
+ * quota is computed by folding those events, so usage survives restart and
5
+ * fork and cannot drift from what the log records.
6
+ *
7
+ * @module dsh-draw/quota
8
+ */
9
+ import { drawGeneratedEvents } from './session-events.js';
10
+ /**
11
+ * Fold one session's `draw/generated` events into current usage.
12
+ *
13
+ * @param session - session whose log is folded.
14
+ * @returns generation and byte totals.
15
+ */
16
+ export function quotaState(session) {
17
+ let generations = 0;
18
+ let bytes = 0;
19
+ for (const event of drawGeneratedEvents(session)) {
20
+ generations += 1;
21
+ bytes += event.data.bytes;
22
+ }
23
+ return { generations, bytes };
24
+ }
25
+ /**
26
+ * Check the generation-call axis before any engine is contacted: a session at
27
+ * its cap fails fast without spending engine credits.
28
+ *
29
+ * @param session - owning session.
30
+ * @param limits - configured limits.
31
+ * @returns allowance or denial.
32
+ */
33
+ export function checkQuotaGenerations(session, limits) {
34
+ const state = quotaState(session);
35
+ if (state.generations >= limits.maxGenerations) {
36
+ return { allowed: false, reason: 'generations', state };
37
+ }
38
+ return { allowed: true, state };
39
+ }
40
+ /**
41
+ * Check the byte axis after the engine produced images but before anything is
42
+ * stored: the incoming bytes must fit under the cap, otherwise the images are
43
+ * discarded without touching the attachment store.
44
+ *
45
+ * @param session - owning session.
46
+ * @param limits - configured limits.
47
+ * @param incomingBytes - bytes the new images would add.
48
+ * @returns allowance or denial.
49
+ */
50
+ export function checkQuotaBytes(session, limits, incomingBytes) {
51
+ const state = quotaState(session);
52
+ if (state.bytes + incomingBytes > limits.maxBytes) {
53
+ return { allowed: false, reason: 'bytes', state };
54
+ }
55
+ return { allowed: true, state };
56
+ }