@klarkxy/dsh-plugin-kit 0.2.0 → 0.3.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/README.md +15 -2
- package/docs/README.zh-CN.md +15 -2
- package/lib/cli-media.d.ts +70 -0
- package/lib/cli-media.js +249 -0
- package/lib/cli-media.js.map +1 -0
- package/lib/cli-process.d.ts +33 -0
- package/lib/cli-process.js +166 -0
- package/lib/cli-process.js.map +1 -0
- package/lib/contracts.d.ts +64 -105
- package/lib/contracts.js +31 -1
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/model-menu.d.ts +2 -2
- package/lib/model-menu.js +4 -4
- package/lib/model-menu.js.map +1 -1
- package/lib/model-settings-client.d.ts +47 -0
- package/lib/model-settings-client.js +143 -0
- package/lib/model-settings-client.js.map +1 -0
- package/lib/model-settings.d.ts +55 -0
- package/lib/model-settings.js +60 -0
- package/lib/model-settings.js.map +1 -0
- package/lib/official-ui.js +1 -1
- package/lib/official-ui.js.map +1 -1
- package/package.json +38 -12
- package/lib/model-menu-ui.d.ts +0 -1
- package/lib/model-menu-ui.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-process.js","names":[],"sources":["../src/cli-process.ts"],"sourcesContent":["import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'\nimport { StringDecoder } from 'node:string_decoder'\n\n/** A resolved executable, never a shell command or an argument string. */\nexport type CliExecutable = { command: string; args?: string[]; env?: NodeJS.ProcessEnv; displayPath: string }\nexport type CliOutput = { stdout: string; stderr: string; exitCode: number }\nexport class CliProcessError extends Error {\n constructor(public readonly code: string, message: string) { super(message); this.name = 'CliProcessError' }\n}\nexport type CliOptions = {\n cwd: string; signal: AbortSignal; input?: string; timeoutMs?: number;\n maxOutputBytes?: number; maxLineBytes?: number;\n onLine?: (line: string) => void | Promise<void>;\n spawnChild?: typeof spawn;\n}\n\n/** Own one process tree until it exits, including caller cancellation and protocol failure. */\nexport async function runCli(executable: CliExecutable, args: string[], options: CliOptions): Promise<CliOutput> {\n options.signal.throwIfAborted()\n const timed = AbortSignal.timeout(options.timeoutMs ?? 900_000)\n const signal = AbortSignal.any([options.signal, timed])\n const maxBytes = options.maxOutputBytes ?? 16 * 1024 * 1024\n const maxLine = options.maxLineBytes ?? 8 * 1024 * 1024\n let child: ChildProcessWithoutNullStreams\n try {\n child = (options.spawnChild ?? spawn)(executable.command, [...(executable.args ?? []), ...args], {\n cwd: options.cwd, env: executable.env ?? process.env, shell: false,\n windowsHide: true, detached: process.platform !== 'win32', stdio: ['pipe', 'pipe', 'pipe'],\n }) as ChildProcessWithoutNullStreams\n } catch { throw new CliProcessError('unavailable', 'Unable to start the official CLI.') }\n let failure: unknown, total = 0, stderr = '', stdout = '', pending = Buffer.alloc(0)\n const stdoutDecoder = new StringDecoder('utf8'), stderrDecoder = new StringDecoder('utf8')\n let exited = false, exitCode = -1, stopping: Promise<void> | undefined\n let lineWork = Promise.resolve()\n const closed = new Promise<void>(resolve => {\n child.once('close', code => { exited = true; exitCode = code ?? -1; resolve() })\n })\n const stop = () => stopping ??= (async () => {\n if (exited) return\n const pid = child.pid\n if (pid && process.platform === 'win32') {\n await new Promise<void>(resolve => {\n const killer = spawn('taskkill.exe', ['/PID', String(pid), '/T', '/F'], { windowsHide: true, shell: false, stdio: 'ignore' })\n let timer: ReturnType<typeof setTimeout>\n const done = () => { clearTimeout(timer); resolve() }\n killer.once('error', () => { child.kill(); done() })\n killer.once('close', done)\n timer = setTimeout(() => { killer.kill(); child.kill(); resolve() }, 5_000)\n })\n } else if (pid) {\n try { process.kill(-pid, 'SIGTERM') } catch { child.kill() }\n } else child.kill()\n let timer: ReturnType<typeof setTimeout> | undefined\n await Promise.race([closed, new Promise<void>(resolve => { timer = setTimeout(resolve, 2_000) })])\n if (timer) clearTimeout(timer)\n if (!exited) {\n try { pid && process.platform !== 'win32' ? process.kill(-pid, 'SIGKILL') : child.kill('SIGKILL') } catch { /* already exited */ }\n }\n await closed\n })()\n const fail = (error: unknown) => { failure ??= error; void stop() }\n const abort = () => fail(new CliProcessError(timed.aborted && !options.signal.aborted ? 'timeout' : 'cancelled', timed.aborted && !options.signal.aborted ? 'The CLI request timed out.' : 'The CLI request was cancelled.'))\n signal.addEventListener('abort', abort, { once: true })\n child.on('error', () => fail(new CliProcessError('unavailable', 'Unable to start the official CLI.')))\n child.stdin.on('error', () => { if (!exited) fail(new CliProcessError('process-io', 'The CLI input stream closed unexpectedly.')) })\n child.stdout.on('error', () => fail(new CliProcessError('process-io', 'The CLI output stream failed.')))\n child.stderr.on('error', () => fail(new CliProcessError('process-io', 'The CLI error stream failed.')))\n const count = (chunk: Buffer) => {\n total += chunk.length\n if (total > maxBytes) { fail(new CliProcessError('output-limit', 'The CLI output exceeded its limit.')); return false }\n return true\n }\n const acceptLine = (line: string) => {\n if (!options.onLine || !line.trim()) return\n child.stdout.pause()\n lineWork = lineWork.then(async () => { if (!failure) await options.onLine!(line) }).catch(fail).finally(() => child.stdout.resume())\n }\n child.stdout.on('data', (chunk: Buffer) => {\n if (failure || !count(chunk)) return\n if (!options.onLine) { stdout += stdoutDecoder.write(chunk); return }\n pending = Buffer.concat([pending, chunk])\n for (;;) {\n const end = pending.indexOf(10)\n if (end < 0) break\n if (end > maxLine) { fail(new CliProcessError('output-limit', 'A CLI protocol frame exceeded its limit.')); return }\n acceptLine(pending.subarray(0, end).toString('utf8'))\n pending = pending.subarray(end + 1)\n }\n if (pending.length > maxLine) fail(new CliProcessError('output-limit', 'A CLI protocol frame exceeded its limit.'))\n })\n child.stderr.on('data', (chunk: Buffer) => { if (count(chunk)) stderr = (stderr + stderrDecoder.write(chunk)).slice(-16_384) })\n try {\n if (signal.aborted) abort()\n if (!failure) child.stdin.end(options.input ?? '')\n await closed\n stdout += stdoutDecoder.end(); stderr += stderrDecoder.end()\n if (pending.length && !failure) acceptLine(pending.toString('utf8'))\n await lineWork\n if (failure) throw failure\n signal.throwIfAborted()\n return { stdout, stderr, exitCode }\n } finally {\n signal.removeEventListener('abort', abort)\n if (!exited) await stop()\n if (stopping) await stopping\n }\n}\n"],"mappings":"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"}
|
package/lib/contracts.d.ts
CHANGED
|
@@ -17,110 +17,6 @@ declare module '@deepseek-ai/dsh-llm' {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
declare function producerMessageSource(plugin: string): ProducerMessageSource;
|
|
20
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
21
|
-
type ModelRole = 'normal' | 'weak' | 'strong' | 'fantasy';
|
|
22
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
23
|
-
type ModelTarget = {
|
|
24
|
-
kind: 'role';
|
|
25
|
-
role: ModelRole;
|
|
26
|
-
} | {
|
|
27
|
-
kind: 'session';
|
|
28
|
-
} | ({
|
|
29
|
-
kind: 'model';
|
|
30
|
-
} & ModelRoute);
|
|
31
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
32
|
-
interface AiPolicy {
|
|
33
|
-
revision: number;
|
|
34
|
-
roles: Partial<Record<ModelRole, ModelRoute>>;
|
|
35
|
-
purposes: Record<string, ModelTarget>;
|
|
36
|
-
limits: {
|
|
37
|
-
concurrency: number;
|
|
38
|
-
timeoutMs: number;
|
|
39
|
-
maxInputChars: number;
|
|
40
|
-
maxOutputTokens: number;
|
|
41
|
-
maxAttempts: number;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
45
|
-
interface PurposeSpec {
|
|
46
|
-
id: string;
|
|
47
|
-
label: string;
|
|
48
|
-
defaultTarget: ModelTarget;
|
|
49
|
-
maxOutputTokens?: number;
|
|
50
|
-
maxInputChars?: number;
|
|
51
|
-
timeoutMs?: number;
|
|
52
|
-
}
|
|
53
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
54
|
-
interface ResolvedRoute extends ModelRoute {
|
|
55
|
-
source: 'override' | 'purpose' | 'default';
|
|
56
|
-
target: ModelTarget;
|
|
57
|
-
policyRevision: number;
|
|
58
|
-
inheritedRole?: ModelRole;
|
|
59
|
-
}
|
|
60
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
61
|
-
interface AuxiliaryRequest {
|
|
62
|
-
purpose: string;
|
|
63
|
-
sessionId?: string;
|
|
64
|
-
input: string;
|
|
65
|
-
system: string;
|
|
66
|
-
sourceVersion: string;
|
|
67
|
-
contractVersion?: number;
|
|
68
|
-
promptVersion?: string;
|
|
69
|
-
schemaVersion?: string;
|
|
70
|
-
override?: ModelTarget;
|
|
71
|
-
signal?: AbortSignal;
|
|
72
|
-
isCurrent?: () => boolean;
|
|
73
|
-
priority?: 'interactive' | 'background';
|
|
74
|
-
insert?: {
|
|
75
|
-
maxChars: number;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
79
|
-
interface UsageReceipt {
|
|
80
|
-
id: string;
|
|
81
|
-
plugin: string;
|
|
82
|
-
purpose: string;
|
|
83
|
-
sessionId?: string;
|
|
84
|
-
sourceVersion: string;
|
|
85
|
-
contractVersion?: number;
|
|
86
|
-
promptVersion?: string;
|
|
87
|
-
schemaVersion?: string;
|
|
88
|
-
route?: ResolvedRoute;
|
|
89
|
-
status: 'success' | 'failed' | 'cancelled' | 'superseded' | 'skipped';
|
|
90
|
-
attempts: number;
|
|
91
|
-
inputTokens?: number;
|
|
92
|
-
outputTokens?: number;
|
|
93
|
-
cost: number | null;
|
|
94
|
-
startedAt: number;
|
|
95
|
-
finishedAt: number;
|
|
96
|
-
error?: string;
|
|
97
|
-
}
|
|
98
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
99
|
-
interface AuxiliaryResult {
|
|
100
|
-
text: string;
|
|
101
|
-
receipt: UsageReceipt;
|
|
102
|
-
}
|
|
103
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
104
|
-
interface AiFeatureScope {
|
|
105
|
-
readonly plugin: string;
|
|
106
|
-
readonly signal: AbortSignal;
|
|
107
|
-
readonly active: boolean;
|
|
108
|
-
registerPurpose(spec: PurposeSpec): () => void;
|
|
109
|
-
run(request: AuxiliaryRequest): Promise<AuxiliaryResult>;
|
|
110
|
-
dispose(): void;
|
|
111
|
-
}
|
|
112
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
113
|
-
interface AiServices {
|
|
114
|
-
activate(plugin: string): AiFeatureScope;
|
|
115
|
-
importPurposes(migrationId: string, defaults: Record<string, ModelTarget>, roles?: AiPolicy['roles']): Promise<AiPolicy>;
|
|
116
|
-
getPolicy(): AiPolicy;
|
|
117
|
-
updatePolicy(policy: Omit<AiPolicy, 'revision'>, expectedRevision: number): Promise<AiPolicy>;
|
|
118
|
-
resolve(purpose: string, sessionId?: string, override?: ModelTarget): Promise<ResolvedRoute>;
|
|
119
|
-
purposes(): Array<PurposeSpec & {
|
|
120
|
-
plugin: string;
|
|
121
|
-
}>;
|
|
122
|
-
usage(): UsageReceipt[];
|
|
123
|
-
}
|
|
124
20
|
type RpcResult<T = unknown> = {
|
|
125
21
|
ok: true;
|
|
126
22
|
value: T;
|
|
@@ -271,6 +167,69 @@ interface SubagentSessionLike {
|
|
|
271
167
|
declare function isSubagentSession(session: unknown): boolean;
|
|
272
168
|
/** Project keys come only from the host-validated session directory, never client input. */
|
|
273
169
|
declare function projectIdFromCwd(cwd: string | undefined): string | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Remote contribution for model-settings change notifications.
|
|
172
|
+
* Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);
|
|
173
|
+
* subscribers degrade silently when that package is not installed.
|
|
174
|
+
* The stream carries invalidations only — `ready` is never a settings snapshot.
|
|
175
|
+
*/
|
|
176
|
+
declare const MODEL_SETTINGS_CHANGES_REMOTE: {
|
|
177
|
+
package: string;
|
|
178
|
+
descriptors: {
|
|
179
|
+
id: string;
|
|
180
|
+
service: string;
|
|
181
|
+
namespace: string;
|
|
182
|
+
method: string;
|
|
183
|
+
mode: "stream";
|
|
184
|
+
invocation: {
|
|
185
|
+
kind: "direct";
|
|
186
|
+
};
|
|
187
|
+
parameters: readonly [];
|
|
188
|
+
cancellation: {
|
|
189
|
+
parameter: "signal";
|
|
190
|
+
};
|
|
191
|
+
}[];
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Repository extension: a native web provider announces that its local
|
|
195
|
+
* availability changed. Kind is `search` or `fetch`; id is the provider id.
|
|
196
|
+
* Listeners refresh management state and must not treat this as a network probe.
|
|
197
|
+
*/
|
|
198
|
+
declare const WEB_PROVIDER_AVAILABILITY_EVENT: "web/provider-availability-updated";
|
|
199
|
+
/**
|
|
200
|
+
* Optional display metadata a provider may attach when it registers directly
|
|
201
|
+
* with `ctx.web`. Execution and credentials stay with the provider; this object
|
|
202
|
+
* is display-only and must never carry secret values.
|
|
203
|
+
*/
|
|
204
|
+
interface DshWebManagementDescriptor {
|
|
205
|
+
readonly id: string;
|
|
206
|
+
readonly label: string;
|
|
207
|
+
readonly description: string;
|
|
208
|
+
readonly defaultBaseURL?: string;
|
|
209
|
+
readonly credentialRef?: string;
|
|
210
|
+
/** True when the key is shared with another surface. The page must not delete it. */
|
|
211
|
+
readonly credentialShared?: boolean;
|
|
212
|
+
/** Optional notice naming the settings surface that owns a shared credential. */
|
|
213
|
+
readonly credentialHint?: string;
|
|
214
|
+
readonly billing: 'request' | 'model-and-tools' | 'none' | 'unknown';
|
|
215
|
+
/** Optional provider-supplied billing summary. */
|
|
216
|
+
readonly pricing?: string;
|
|
217
|
+
/** Public HTTPS page explaining provider charges. */
|
|
218
|
+
readonly pricingUrl?: string;
|
|
219
|
+
/** Public HTTPS page where the user can create an API key. */
|
|
220
|
+
readonly signupUrl?: string;
|
|
221
|
+
/** Plugin that owns configuration. Native providers keep their settings there. */
|
|
222
|
+
readonly configurationOwner?: string;
|
|
223
|
+
/** Explicit settings link supplied by the provider, never inferred from its id. */
|
|
224
|
+
readonly configurationUrl?: string;
|
|
225
|
+
/** Fixed legacy route migrated by a bundle-owned adapter; not used by native plugins. */
|
|
226
|
+
readonly legacyId?: string;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Host Creator agent-preset id (`agentPresets.composedPreset`).
|
|
230
|
+
* Plugins that gate Creator-only tools or prompt sections compare against this.
|
|
231
|
+
*/
|
|
232
|
+
declare const CREATOR_PRESET_ID = "cordis";
|
|
274
233
|
//#endregion
|
|
275
|
-
export {
|
|
234
|
+
export { CHAT_EVENTS_SLOT, CREATOR_PRESET_ID, ContextKnowledge, DshWebManagementDescriptor, EvidenceRef, KnowledgeKind, KnowledgeScope, MODEL_SETTINGS_CHANGES_REMOTE, MemoryMutationOptions, MemoryQuery, MemoryRecord, MemoryService, type ModelRoute, NewMemoryRecord, ProcedureKnowledge, ProducerMessageSource, ProducerSourceKind, RpcResult, SubagentSessionLike, TaskCheckpoint, TaskContract, WEB_PROVIDER_AVAILABILITY_EVENT, isSubagentSession, producerMessageSource, projectIdFromCwd };
|
|
276
235
|
//# sourceMappingURL=contracts.d.ts.map
|
package/lib/contracts.js
CHANGED
|
@@ -29,7 +29,37 @@ function projectIdFromCwd(cwd) {
|
|
|
29
29
|
if (!normalized) return void 0;
|
|
30
30
|
return normalized === "/" || /^[A-Za-z]:\/$/.test(normalized) ? normalized : normalized.replace(/\/+$/, "");
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Remote contribution for model-settings change notifications.
|
|
34
|
+
* Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);
|
|
35
|
+
* subscribers degrade silently when that package is not installed.
|
|
36
|
+
* The stream carries invalidations only — `ready` is never a settings snapshot.
|
|
37
|
+
*/
|
|
38
|
+
const MODEL_SETTINGS_CHANGES_REMOTE = {
|
|
39
|
+
package: "@klarkxy/dsh-model-hub",
|
|
40
|
+
descriptors: [{
|
|
41
|
+
id: "modelSettingsChanges/watch",
|
|
42
|
+
service: "modelSettingsChangesController",
|
|
43
|
+
namespace: "modelSettingsChanges",
|
|
44
|
+
method: "watch",
|
|
45
|
+
mode: "stream",
|
|
46
|
+
invocation: { kind: "direct" },
|
|
47
|
+
parameters: [],
|
|
48
|
+
cancellation: { parameter: "signal" }
|
|
49
|
+
}]
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Repository extension: a native web provider announces that its local
|
|
53
|
+
* availability changed. Kind is `search` or `fetch`; id is the provider id.
|
|
54
|
+
* Listeners refresh management state and must not treat this as a network probe.
|
|
55
|
+
*/
|
|
56
|
+
const WEB_PROVIDER_AVAILABILITY_EVENT = "web/provider-availability-updated";
|
|
57
|
+
/**
|
|
58
|
+
* Host Creator agent-preset id (`agentPresets.composedPreset`).
|
|
59
|
+
* Plugins that gate Creator-only tools or prompt sections compare against this.
|
|
60
|
+
*/
|
|
61
|
+
const CREATOR_PRESET_ID = "cordis";
|
|
32
62
|
//#endregion
|
|
33
|
-
export { CHAT_EVENTS_SLOT, isSubagentSession, producerMessageSource, projectIdFromCwd };
|
|
63
|
+
export { CHAT_EVENTS_SLOT, CREATOR_PRESET_ID, MODEL_SETTINGS_CHANGES_REMOTE, WEB_PROVIDER_AVAILABILITY_EVENT, isSubagentSession, producerMessageSource, projectIdFromCwd };
|
|
34
64
|
|
|
35
65
|
//# sourceMappingURL=contracts.js.map
|
package/lib/contracts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["import type { ContextFormed } from '@deepseek-ai/dsh-llm'\nimport type { ModelRoute } from '@klarkxy/dsh-model-route'\n\n/**\n * The canonical home is @klarkxy/dsh-model-route; re-exported here so existing\n * plugin-kit consumers keep compiling.\n */\nexport type { ModelRoute }\n\n/** Public, browser-safe contracts. Host implementations stay in their owning packages. */\nexport type ProducerSourceKind = `plugin:${string}`\nexport type ProducerMessageSource = { kind: ProducerSourceKind; plugin: string } & ContextFormed\n\n/**\n * V4 sessions persist the producing extension in `kind`; the retired generic\n * `plugin` wrapper is not admitted. Each feature plugin declares its own kind.\n */\ndeclare module '@deepseek-ai/dsh-llm' {\n interface MessageSourceMap {\n [kind: ProducerSourceKind]: ProducerMessageSource\n }\n}\n\nexport function producerMessageSource(plugin: string): ProducerMessageSource {\n return { kind: `plugin:${plugin}`, plugin }\n}\n\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport type ModelRole = 'normal' | 'weak' | 'strong' | 'fantasy'\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport type ModelTarget = { kind: 'role'; role: ModelRole } | { kind: 'session' } | ({ kind: 'model' } & ModelRoute)\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface AiPolicy {\n revision: number\n roles: Partial<Record<ModelRole, ModelRoute>>\n purposes: Record<string, ModelTarget>\n limits: { concurrency: number; timeoutMs: number; maxInputChars: number; maxOutputTokens: number; maxAttempts: number }\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface PurposeSpec {\n id: string\n label: string\n defaultTarget: ModelTarget\n maxOutputTokens?: number\n maxInputChars?: number\n timeoutMs?: number\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface ResolvedRoute extends ModelRoute {\n source: 'override' | 'purpose' | 'default'\n target: ModelTarget\n policyRevision: number\n inheritedRole?: ModelRole\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface AuxiliaryRequest {\n purpose: string\n sessionId?: string\n input: string\n system: string\n sourceVersion: string\n contractVersion?: number\n promptVersion?: string\n schemaVersion?: string\n override?: ModelTarget\n signal?: AbortSignal\n isCurrent?: () => boolean\n priority?: 'interactive' | 'background'\n insert?: { maxChars: number }\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface UsageReceipt {\n id: string\n plugin: string\n purpose: string\n sessionId?: string\n sourceVersion: string\n contractVersion?: number\n promptVersion?: string\n schemaVersion?: string\n route?: ResolvedRoute\n status: 'success' | 'failed' | 'cancelled' | 'superseded' | 'skipped'\n attempts: number\n inputTokens?: number\n outputTokens?: number\n cost: number | null\n startedAt: number\n finishedAt: number\n error?: string\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface AuxiliaryResult { text: string; receipt: UsageReceipt }\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface AiFeatureScope {\n readonly plugin: string\n readonly signal: AbortSignal\n readonly active: boolean\n registerPurpose(spec: PurposeSpec): () => void\n run(request: AuxiliaryRequest): Promise<AuxiliaryResult>\n dispose(): void\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface AiServices {\n activate(plugin: string): AiFeatureScope\n importPurposes(migrationId: string, defaults: Record<string, ModelTarget>, roles?: AiPolicy['roles']): Promise<AiPolicy>\n getPolicy(): AiPolicy\n updatePolicy(policy: Omit<AiPolicy, 'revision'>, expectedRevision: number): Promise<AiPolicy>\n resolve(purpose: string, sessionId?: string, override?: ModelTarget): Promise<ResolvedRoute>\n purposes(): Array<PurposeSpec & { plugin: string }>\n usage(): UsageReceipt[]\n}\n\nexport type RpcResult<T = unknown> = { ok: true; value: T } | { ok: false; error: { code: string; message: string } }\nexport const CHAT_EVENTS_SLOT = 'dsh-editor.chat.events'\nexport interface EvidenceRef { sessionId: string; seq: number; kind: 'user' | 'tool' | 'turn' | 'manual'; excerpt?: string }\nexport interface TaskContract {\n id: string\n sessionId: string\n sourceVersion: string\n revision: number\n goal: string\n deliverables: string[]\n inScope: string[]\n outOfScope: string[]\n constraints: string[]\n acceptance: string[]\n assumptions: string[]\n questions: string[]\n evidence: EvidenceRef[]\n readiness: 'pending' | 'clear-request' | 'user-confirmed' | 'disclosed-assumptions' | 'cancelled' | 'stale'\n updatedAt: number\n}\nexport interface TaskCheckpoint {\n id: string\n sessionId: string\n sourceVersion: string\n contractVersion?: number\n fromSeq: number\n toSeq: number\n revision: number\n status: 'running' | 'completed' | 'failed' | 'cancelled'\n items: Array<{ label: string; state: 'pending' | 'verified' | 'failed'; evidence: EvidenceRef[] }>\n constraints: string[]\n nextAction: string\n createdAt: number\n}\nexport type KnowledgeScope = { kind: 'global' } | { kind: 'project'; projectId: string }\nexport type KnowledgeKind = 'preference' | 'project-fact' | 'decision' | 'vocabulary' | 'activity' | 'lesson'\n/** Descriptive context, never an executable instruction or authorization. */\nexport interface ContextKnowledge {\n subject: string\n domain: string\n key: string\n aliases: string[]\n observedAt: number\n /** The user's own time expression, not an inferred completion date. */\n eventTime?: string\n activityStatus?: 'planned' | 'in-progress' | 'blocked' | 'paused' | 'completed' | 'cancelled' | 'unknown'\n}\n/** A scoped method. Observations remain candidates until explicitly accepted. */\nexport interface ProcedureKnowledge {\n origin: 'instruction' | 'observation'\n goal: string\n when: string[]\n steps: string[]\n avoid: string[]\n verify: string[]\n}\nexport interface MemoryRecord {\n id: string\n revision: number\n scope: KnowledgeScope\n kind: KnowledgeKind\n status: 'candidate' | 'active' | 'rejected' | 'superseded' | 'revoked' | 'deleted'\n title: string\n content: string\n tags: string[]\n evidence: EvidenceRef[]\n exceptions: string[]\n source: 'user' | 'memory' | 'dream' | 'self-improvement'\n context?: ContextKnowledge\n procedure?: ProcedureKnowledge\n createdAt: number\n updatedAt: number\n expiresAt?: number\n supersedes?: string[]\n /** Exact record revisions behind a derived candidate; revalidated on adoption. */\n basis?: Array<{ id: string; revision: number }>\n}\nexport type NewMemoryRecord = Omit<MemoryRecord, 'id' | 'revision' | 'createdAt' | 'updatedAt'>\nexport interface MemoryQuery { scope: KnowledgeScope; query?: string; kinds?: KnowledgeKind[]; statuses?: MemoryRecord['status'][]; limit?: number }\nexport interface MemoryMutationOptions { signal?: AbortSignal; isCurrent?: () => boolean }\nexport interface MemoryService {\n list(query: MemoryQuery): Promise<MemoryRecord[]>\n create(record: NewMemoryRecord, options?: MemoryMutationOptions): Promise<MemoryRecord>\n update(id: string, patch: Partial<Pick<MemoryRecord, 'title' | 'content' | 'tags' | 'exceptions' | 'status' | 'expiresAt'>>, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n /** Explicit user promotion, persisted atomically on the same record; preserves expiry and evidence. */\n promoteToGlobal(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n remove(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<void>\n recall(query: MemoryQuery): Promise<MemoryRecord[]>\n}\n\n/**\n * The session creation metadata the host snapshots when it creates a delegated child\n * thread. Mirrors `CreateAgentOptions.meta` in `@deepseek-ai/dsh-agent`; it is durable\n * session data, so it survives a restart and needs no ancestry walk.\n */\nexport interface SubagentSessionLike {\n readonly header?: {\n readonly parentSession?: unknown\n readonly origin?: unknown\n readonly delegationDepth?: unknown\n readonly isSeeded?: unknown\n readonly classification?: unknown\n }\n}\n\n/**\n * True when `session` is a delegated child thread rather than a human-driven top-level\n * conversation.\n *\n * A message's `source.kind === 'user'` cannot answer this: a lead agent's dispatch prompt\n * is delivered as the child's own user message, so delegated work is indistinguishable\n * from a person typing once you only look at the message. Plugins use this to keep\n * automatic writes and derived model calls on top-level sessions.\n */\nexport function isSubagentSession(session: unknown): boolean {\n const header = (session as SubagentSessionLike | null | undefined)?.header\n if (!header || typeof header !== 'object') return false\n if (header.origin === 'subagent' || Number(header.delegationDepth ?? 0) > 0) return true\n // Native human forks also persist parentSession for history lineage. A fork's\n // seeded header distinguishes it from legacy children whose parent is the\n // only marker. Native rc.2 forks omit depth; patched hosts may persist zero.\n // Explicit delegation and unknown metadata still keep the child guard.\n const humanFork = header.isSeeded === true\n && (header.delegationDepth === undefined || header.delegationDepth === 0)\n && header.origin === undefined\n && (header.classification === undefined || header.classification === 'ordinary' || header.classification === 'free')\n return Boolean(header.parentSession) && !humanFork\n}\n\n/** Project keys come only from the host-validated session directory, never client input. */\nexport function projectIdFromCwd(cwd: string | undefined): string | undefined {\n if (typeof cwd !== 'string') return undefined\n const normalized = cwd.trim().replaceAll('\\\\', '/')\n if (!normalized) return undefined\n return normalized === '/' || /^[A-Za-z]:\\/$/.test(normalized) ? normalized : normalized.replace(/\\/+$/, '')\n}\n"],"mappings":";AAuBA,SAAgB,sBAAsB,QAAuC;CAC3E,OAAO;EAAE,MAAM,UAAU;EAAU;CAAO;AAC5C;AAwFA,MAAa,mBAAmB;;;;;;;;;;AAiHhC,SAAgB,kBAAkB,SAA2B;CAC3D,MAAM,SAAU,SAAoD;CACpE,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO;CAClD,IAAI,OAAO,WAAW,cAAc,OAAO,OAAO,mBAAmB,CAAC,IAAI,GAAG,OAAO;CAKpF,MAAM,YAAY,OAAO,aAAa,SAChC,OAAO,oBAAoB,KAAA,KAAa,OAAO,oBAAoB,MACpE,OAAO,WAAW,KAAA,MACjB,OAAO,mBAAmB,KAAA,KAAa,OAAO,mBAAmB,cAAc,OAAO,mBAAmB;CAC/G,OAAO,QAAQ,OAAO,aAAa,KAAK,CAAC;AAC3C;;AAGA,SAAgB,iBAAiB,KAA6C;CAC5E,IAAI,OAAO,QAAQ,UAAU,OAAO,KAAA;CACpC,MAAM,aAAa,IAAI,KAAK,CAAC,CAAC,WAAW,MAAM,GAAG;CAClD,IAAI,CAAC,YAAY,OAAO,KAAA;CACxB,OAAO,eAAe,OAAO,gBAAgB,KAAK,UAAU,IAAI,aAAa,WAAW,QAAQ,QAAQ,EAAE;AAC5G"}
|
|
1
|
+
{"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["import type { ContextFormed } from '@deepseek-ai/dsh-llm'\nimport type { ModelRoute } from '@klarkxy/dsh-model-route'\n\n/**\n * The canonical home is @klarkxy/dsh-model-route; re-exported here so existing\n * plugin-kit consumers keep compiling.\n */\nexport type { ModelRoute }\n\n/** Public, browser-safe contracts. Host implementations stay in their owning packages. */\nexport type ProducerSourceKind = `plugin:${string}`\nexport type ProducerMessageSource = { kind: ProducerSourceKind; plugin: string } & ContextFormed\n\n/**\n * V4 sessions persist the producing extension in `kind`; the retired generic\n * `plugin` wrapper is not admitted. Each feature plugin declares its own kind.\n */\ndeclare module '@deepseek-ai/dsh-llm' {\n interface MessageSourceMap {\n [kind: ProducerSourceKind]: ProducerMessageSource\n }\n}\n\nexport function producerMessageSource(plugin: string): ProducerMessageSource {\n return { kind: `plugin:${plugin}`, plugin }\n}\n\nexport type RpcResult<T = unknown> = { ok: true; value: T } | { ok: false; error: { code: string; message: string } }\nexport const CHAT_EVENTS_SLOT = 'dsh-editor.chat.events'\nexport interface EvidenceRef { sessionId: string; seq: number; kind: 'user' | 'tool' | 'turn' | 'manual'; excerpt?: string }\nexport interface TaskContract {\n id: string\n sessionId: string\n sourceVersion: string\n revision: number\n goal: string\n deliverables: string[]\n inScope: string[]\n outOfScope: string[]\n constraints: string[]\n acceptance: string[]\n assumptions: string[]\n questions: string[]\n evidence: EvidenceRef[]\n readiness: 'pending' | 'clear-request' | 'user-confirmed' | 'disclosed-assumptions' | 'cancelled' | 'stale'\n updatedAt: number\n}\nexport interface TaskCheckpoint {\n id: string\n sessionId: string\n sourceVersion: string\n contractVersion?: number\n fromSeq: number\n toSeq: number\n revision: number\n status: 'running' | 'completed' | 'failed' | 'cancelled'\n items: Array<{ label: string; state: 'pending' | 'verified' | 'failed'; evidence: EvidenceRef[] }>\n constraints: string[]\n nextAction: string\n createdAt: number\n}\nexport type KnowledgeScope = { kind: 'global' } | { kind: 'project'; projectId: string }\nexport type KnowledgeKind = 'preference' | 'project-fact' | 'decision' | 'vocabulary' | 'activity' | 'lesson'\n/** Descriptive context, never an executable instruction or authorization. */\nexport interface ContextKnowledge {\n subject: string\n domain: string\n key: string\n aliases: string[]\n observedAt: number\n /** The user's own time expression, not an inferred completion date. */\n eventTime?: string\n activityStatus?: 'planned' | 'in-progress' | 'blocked' | 'paused' | 'completed' | 'cancelled' | 'unknown'\n}\n/** A scoped method. Observations remain candidates until explicitly accepted. */\nexport interface ProcedureKnowledge {\n origin: 'instruction' | 'observation'\n goal: string\n when: string[]\n steps: string[]\n avoid: string[]\n verify: string[]\n}\nexport interface MemoryRecord {\n id: string\n revision: number\n scope: KnowledgeScope\n kind: KnowledgeKind\n status: 'candidate' | 'active' | 'rejected' | 'superseded' | 'revoked' | 'deleted'\n title: string\n content: string\n tags: string[]\n evidence: EvidenceRef[]\n exceptions: string[]\n source: 'user' | 'memory' | 'dream' | 'self-improvement'\n context?: ContextKnowledge\n procedure?: ProcedureKnowledge\n createdAt: number\n updatedAt: number\n expiresAt?: number\n supersedes?: string[]\n /** Exact record revisions behind a derived candidate; revalidated on adoption. */\n basis?: Array<{ id: string; revision: number }>\n}\nexport type NewMemoryRecord = Omit<MemoryRecord, 'id' | 'revision' | 'createdAt' | 'updatedAt'>\nexport interface MemoryQuery { scope: KnowledgeScope; query?: string; kinds?: KnowledgeKind[]; statuses?: MemoryRecord['status'][]; limit?: number }\nexport interface MemoryMutationOptions { signal?: AbortSignal; isCurrent?: () => boolean }\nexport interface MemoryService {\n list(query: MemoryQuery): Promise<MemoryRecord[]>\n create(record: NewMemoryRecord, options?: MemoryMutationOptions): Promise<MemoryRecord>\n update(id: string, patch: Partial<Pick<MemoryRecord, 'title' | 'content' | 'tags' | 'exceptions' | 'status' | 'expiresAt'>>, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n /** Explicit user promotion, persisted atomically on the same record; preserves expiry and evidence. */\n promoteToGlobal(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n remove(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<void>\n recall(query: MemoryQuery): Promise<MemoryRecord[]>\n}\n\n/**\n * The session creation metadata the host snapshots when it creates a delegated child\n * thread. Mirrors `CreateAgentOptions.meta` in `@deepseek-ai/dsh-agent`; it is durable\n * session data, so it survives a restart and needs no ancestry walk.\n */\nexport interface SubagentSessionLike {\n readonly header?: {\n readonly parentSession?: unknown\n readonly origin?: unknown\n readonly delegationDepth?: unknown\n readonly isSeeded?: unknown\n readonly classification?: unknown\n }\n}\n\n/**\n * True when `session` is a delegated child thread rather than a human-driven top-level\n * conversation.\n *\n * A message's `source.kind === 'user'` cannot answer this: a lead agent's dispatch prompt\n * is delivered as the child's own user message, so delegated work is indistinguishable\n * from a person typing once you only look at the message. Plugins use this to keep\n * automatic writes and derived model calls on top-level sessions.\n */\nexport function isSubagentSession(session: unknown): boolean {\n const header = (session as SubagentSessionLike | null | undefined)?.header\n if (!header || typeof header !== 'object') return false\n if (header.origin === 'subagent' || Number(header.delegationDepth ?? 0) > 0) return true\n // Native human forks also persist parentSession for history lineage. A fork's\n // seeded header distinguishes it from legacy children whose parent is the\n // only marker. Native rc.2 forks omit depth; patched hosts may persist zero.\n // Explicit delegation and unknown metadata still keep the child guard.\n const humanFork = header.isSeeded === true\n && (header.delegationDepth === undefined || header.delegationDepth === 0)\n && header.origin === undefined\n && (header.classification === undefined || header.classification === 'ordinary' || header.classification === 'free')\n return Boolean(header.parentSession) && !humanFork\n}\n\n/** Project keys come only from the host-validated session directory, never client input. */\nexport function projectIdFromCwd(cwd: string | undefined): string | undefined {\n if (typeof cwd !== 'string') return undefined\n const normalized = cwd.trim().replaceAll('\\\\', '/')\n if (!normalized) return undefined\n return normalized === '/' || /^[A-Za-z]:\\/$/.test(normalized) ? normalized : normalized.replace(/\\/+$/, '')\n}\n\n/**\n * Remote contribution for model-settings change notifications.\n * Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);\n * subscribers degrade silently when that package is not installed.\n * The stream carries invalidations only — `ready` is never a settings snapshot.\n */\nexport const MODEL_SETTINGS_CHANGES_REMOTE = {\n package: '@klarkxy/dsh-model-hub',\n descriptors: [{\n id: 'modelSettingsChanges/watch',\n service: 'modelSettingsChangesController',\n namespace: 'modelSettingsChanges',\n method: 'watch',\n mode: 'stream' as const,\n invocation: { kind: 'direct' as const },\n parameters: [] as const,\n cancellation: { parameter: 'signal' as const },\n }],\n}\n\n/**\n * Repository extension: a native web provider announces that its local\n * availability changed. Kind is `search` or `fetch`; id is the provider id.\n * Listeners refresh management state and must not treat this as a network probe.\n */\nexport const WEB_PROVIDER_AVAILABILITY_EVENT = 'web/provider-availability-updated' as const\n\n/**\n * Optional display metadata a provider may attach when it registers directly\n * with `ctx.web`. Execution and credentials stay with the provider; this object\n * is display-only and must never carry secret values.\n */\nexport interface DshWebManagementDescriptor {\n readonly id: string\n readonly label: string\n readonly description: string\n readonly defaultBaseURL?: string\n readonly credentialRef?: string\n /** True when the key is shared with another surface. The page must not delete it. */\n readonly credentialShared?: boolean\n /** Optional notice naming the settings surface that owns a shared credential. */\n readonly credentialHint?: string\n readonly billing: 'request' | 'model-and-tools' | 'none' | 'unknown'\n /** Optional provider-supplied billing summary. */\n readonly pricing?: string\n /** Public HTTPS page explaining provider charges. */\n readonly pricingUrl?: string\n /** Public HTTPS page where the user can create an API key. */\n readonly signupUrl?: string\n /** Plugin that owns configuration. Native providers keep their settings there. */\n readonly configurationOwner?: string\n /** Explicit settings link supplied by the provider, never inferred from its id. */\n readonly configurationUrl?: string\n /** Fixed legacy route migrated by a bundle-owned adapter; not used by native plugins. */\n readonly legacyId?: string\n}\n\n/**\n * Host Creator agent-preset id (`agentPresets.composedPreset`).\n * Plugins that gate Creator-only tools or prompt sections compare against this.\n */\nexport const CREATOR_PRESET_ID = 'cordis'\n"],"mappings":";AAuBA,SAAgB,sBAAsB,QAAuC;CAC3E,OAAO;EAAE,MAAM,UAAU;EAAU;CAAO;AAC5C;AAGA,MAAa,mBAAmB;;;;;;;;;;AAiHhC,SAAgB,kBAAkB,SAA2B;CAC3D,MAAM,SAAU,SAAoD;CACpE,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO;CAClD,IAAI,OAAO,WAAW,cAAc,OAAO,OAAO,mBAAmB,CAAC,IAAI,GAAG,OAAO;CAKpF,MAAM,YAAY,OAAO,aAAa,SAChC,OAAO,oBAAoB,KAAA,KAAa,OAAO,oBAAoB,MACpE,OAAO,WAAW,KAAA,MACjB,OAAO,mBAAmB,KAAA,KAAa,OAAO,mBAAmB,cAAc,OAAO,mBAAmB;CAC/G,OAAO,QAAQ,OAAO,aAAa,KAAK,CAAC;AAC3C;;AAGA,SAAgB,iBAAiB,KAA6C;CAC5E,IAAI,OAAO,QAAQ,UAAU,OAAO,KAAA;CACpC,MAAM,aAAa,IAAI,KAAK,CAAC,CAAC,WAAW,MAAM,GAAG;CAClD,IAAI,CAAC,YAAY,OAAO,KAAA;CACxB,OAAO,eAAe,OAAO,gBAAgB,KAAK,UAAU,IAAI,aAAa,WAAW,QAAQ,QAAQ,EAAE;AAC5G;;;;;;;AAQA,MAAa,gCAAgC;CAC3C,SAAS;CACT,aAAa,CAAC;EACZ,IAAI;EACJ,SAAS;EACT,WAAW;EACX,QAAQ;EACR,MAAM;EACN,YAAY,EAAE,MAAM,SAAkB;EACtC,YAAY,CAAC;EACb,cAAc,EAAE,WAAW,SAAkB;CAC/C,CAAC;AACH;;;;;;AAOA,MAAa,kCAAkC;;;;;AAoC/C,MAAa,oBAAoB"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CHAT_EVENTS_SLOT, CREATOR_PRESET_ID, ContextKnowledge, DshWebManagementDescriptor, EvidenceRef, KnowledgeKind, KnowledgeScope, MODEL_SETTINGS_CHANGES_REMOTE, MemoryMutationOptions, MemoryQuery, MemoryRecord, MemoryService, ModelRoute, NewMemoryRecord, ProcedureKnowledge, ProducerMessageSource, ProducerSourceKind, RpcResult, SubagentSessionLike, TaskCheckpoint, TaskContract, WEB_PROVIDER_AVAILABILITY_EVENT, isSubagentSession, producerMessageSource, projectIdFromCwd } from "./contracts.js";
|
|
2
2
|
import { openCompatibleDomain } from "./domain.js";
|
|
3
3
|
import { HostRpcContext, registerHostRpc } from "./host-rpc.js";
|
|
4
4
|
import { FeatureModelRoute, LlmTextCaller, callLlmText, resolveFeatureModel } from "./llm-call.js";
|
|
5
|
-
export {
|
|
5
|
+
export { CHAT_EVENTS_SLOT, type CREATOR_PRESET_ID, type ContextKnowledge, type DshWebManagementDescriptor, type EvidenceRef, type FeatureModelRoute, type HostRpcContext, type KnowledgeKind, type KnowledgeScope, type LlmTextCaller, type MODEL_SETTINGS_CHANGES_REMOTE, type MemoryMutationOptions, type MemoryQuery, type MemoryRecord, type MemoryService, type ModelRoute, type NewMemoryRecord, type ProcedureKnowledge, type ProducerMessageSource, type ProducerSourceKind, type RpcResult, type SubagentSessionLike, type TaskCheckpoint, type TaskContract, type WEB_PROVIDER_AVAILABILITY_EVENT, callLlmText, type isSubagentSession, openCompatibleDomain, producerMessageSource, projectIdFromCwd, registerHostRpc, resolveFeatureModel };
|
package/lib/model-menu.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { ModelCatalogClient, ModelCatalogClient as ModelCatalogClient$1, ModelMenuChoice, ModelMenuChoice as ModelMenuChoice$1, ModelMenuGroup, ModelRoute, groupModelMenuChoices, knownModelFromChoices, modelMenuEffortOptions, modelMenuShortName, modelRouteKey as modelMenuChoiceKey, modelRouteOverride as modelMenuOverride, normalizeModelRoute as normalizeModelMenuRoute, parseModelMenuChoices, parseModelRouteKey as parseModelMenuChoiceKey } from "@klarkxy/dsh-model-route";
|
|
1
|
+
import { ModelCatalogClient, ModelCatalogClient as ModelCatalogClient$1, ModelMenuChoice, ModelMenuChoice as ModelMenuChoice$1, ModelMenuGroup, ModelRoute, groupModelMenuChoices, knownModelFromChoices, modelMenuEffortLabel, modelMenuEffortOptions, modelMenuShortName, modelRouteKey as modelMenuChoiceKey, modelRouteOverride as modelMenuOverride, normalizeModelRoute as normalizeModelMenuRoute, parseModelMenuChoices, parseModelRouteKey as parseModelMenuChoiceKey } from "@klarkxy/dsh-model-route";
|
|
2
2
|
//#region src/model-menu.d.ts
|
|
3
3
|
/** The stored selection. Empty provider/model means "follow the default". */
|
|
4
4
|
type ModelMenuRoute = ModelRoute;
|
|
5
5
|
/** Load the host catalog once per mount and republish whenever it answers. */
|
|
6
6
|
declare function useModelMenuChoices(client: ModelCatalogClient$1, bound?: ModelMenuRoute): ModelMenuChoice$1[];
|
|
7
7
|
//#endregion
|
|
8
|
-
export { type ModelCatalogClient, type ModelMenuChoice, type ModelMenuGroup, ModelMenuRoute, groupModelMenuChoices, knownModelFromChoices, modelMenuChoiceKey, modelMenuEffortOptions, modelMenuOverride, modelMenuShortName, normalizeModelMenuRoute, parseModelMenuChoiceKey, parseModelMenuChoices, useModelMenuChoices };
|
|
8
|
+
export { type ModelCatalogClient, type ModelMenuChoice, type ModelMenuGroup, ModelMenuRoute, groupModelMenuChoices, knownModelFromChoices, modelMenuChoiceKey, modelMenuEffortLabel, modelMenuEffortOptions, modelMenuOverride, modelMenuShortName, normalizeModelMenuRoute, parseModelMenuChoiceKey, parseModelMenuChoices, useModelMenuChoices };
|
|
9
9
|
//# sourceMappingURL=model-menu.d.ts.map
|
package/lib/model-menu.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
-
import { groupModelMenuChoices, knownModelFromChoices, modelMenuEffortOptions, modelMenuShortName, modelRouteKey as modelMenuChoiceKey, modelRouteOverride as modelMenuOverride, normalizeModelRoute as normalizeModelMenuRoute, parseModelMenuChoices, parseModelMenuChoices as parseModelMenuChoices$1, parseModelRouteKey as parseModelMenuChoiceKey } from "@klarkxy/dsh-model-route";
|
|
2
|
+
import { groupModelMenuChoices, knownModelFromChoices, modelMenuEffortLabel, modelMenuEffortOptions, modelMenuShortName, modelRouteKey as modelMenuChoiceKey, modelRouteOverride as modelMenuOverride, normalizeModelRoute as normalizeModelMenuRoute, parseModelMenuChoices, parseModelMenuChoices as parseModelMenuChoices$1, parseModelRouteKey as parseModelMenuChoiceKey } from "@klarkxy/dsh-model-route";
|
|
3
3
|
//#region src/model-menu.ts
|
|
4
4
|
/**
|
|
5
5
|
* Shared plugin-page model menu.
|
|
@@ -10,8 +10,8 @@ import { groupModelMenuChoices, knownModelFromChoices, modelMenuEffortOptions, m
|
|
|
10
10
|
* re-exports the core so existing plugin-kit consumers keep working, and adds
|
|
11
11
|
* the React catalog hook on top. It must stay free of any ui-primitives
|
|
12
12
|
* import so host-side and node-test consumers never load the component
|
|
13
|
-
* bundle — the popover component and its styles
|
|
14
|
-
*
|
|
13
|
+
* bundle — the popover component and its styles live in
|
|
14
|
+
* `@klarkxy/dsh-model-route/ui`.
|
|
15
15
|
*/
|
|
16
16
|
/** Load the host catalog once per mount and republish whenever it answers. */
|
|
17
17
|
function useModelMenuChoices(client, bound) {
|
|
@@ -38,6 +38,6 @@ function useModelMenuChoices(client, bound) {
|
|
|
38
38
|
return choices;
|
|
39
39
|
}
|
|
40
40
|
//#endregion
|
|
41
|
-
export { groupModelMenuChoices, knownModelFromChoices, modelMenuChoiceKey, modelMenuEffortOptions, modelMenuOverride, modelMenuShortName, normalizeModelMenuRoute, parseModelMenuChoiceKey, parseModelMenuChoices, useModelMenuChoices };
|
|
41
|
+
export { groupModelMenuChoices, knownModelFromChoices, modelMenuChoiceKey, modelMenuEffortLabel, modelMenuEffortOptions, modelMenuOverride, modelMenuShortName, normalizeModelMenuRoute, parseModelMenuChoiceKey, parseModelMenuChoices, useModelMenuChoices };
|
|
42
42
|
|
|
43
43
|
//# sourceMappingURL=model-menu.js.map
|
package/lib/model-menu.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-menu.js","names":["parseModelMenuChoices"],"sources":["../src/model-menu.ts"],"sourcesContent":["/**\n * Shared plugin-page model menu.\n *\n * Everything lives in @klarkxy/dsh-model-route: the pure contract (route\n * values, schema marker, catalog parsing, menu grouping) in its core entry,\n * and the ModelMenu popover component in its `./ui` browser entry. This entry\n * re-exports the core so existing plugin-kit consumers keep working, and adds\n * the React catalog hook on top. It must stay free of any ui-primitives\n * import so host-side and node-test consumers never load the component\n * bundle — the popover component and its styles
|
|
1
|
+
{"version":3,"file":"model-menu.js","names":["parseModelMenuChoices"],"sources":["../src/model-menu.ts"],"sourcesContent":["/**\n * Shared plugin-page model menu.\n *\n * Everything lives in @klarkxy/dsh-model-route: the pure contract (route\n * values, schema marker, catalog parsing, menu grouping) in its core entry,\n * and the ModelMenu popover component in its `./ui` browser entry. This entry\n * re-exports the core so existing plugin-kit consumers keep working, and adds\n * the React catalog hook on top. It must stay free of any ui-primitives\n * import so host-side and node-test consumers never load the component\n * bundle — the popover component and its styles live in\n * `@klarkxy/dsh-model-route/ui`.\n */\nimport { useEffect, useState } from 'react'\nimport { parseModelMenuChoices, type ModelCatalogClient, type ModelMenuChoice, type ModelRoute } from '@klarkxy/dsh-model-route'\n\nexport type { ModelCatalogClient, ModelMenuChoice, ModelMenuGroup } from '@klarkxy/dsh-model-route'\nexport {\n groupModelMenuChoices,\n knownModelFromChoices,\n modelMenuEffortOptions,\n modelMenuEffortLabel,\n modelMenuShortName,\n modelRouteKey as modelMenuChoiceKey,\n modelRouteOverride as modelMenuOverride,\n normalizeModelRoute as normalizeModelMenuRoute,\n parseModelMenuChoices,\n parseModelRouteKey as parseModelMenuChoiceKey,\n} from '@klarkxy/dsh-model-route'\n\n/** The stored selection. Empty provider/model means \"follow the default\". */\nexport type ModelMenuRoute = ModelRoute\n\n/** Load the host catalog once per mount and republish whenever it answers. */\nexport function useModelMenuChoices(client: ModelCatalogClient, bound?: ModelMenuRoute): ModelMenuChoice[] {\n const [choices, setChoices] = useState<ModelMenuChoice[]>([])\n useEffect(() => {\n if (typeof document === 'undefined') return\n let live = true\n const load = () => {\n void client.remote?.session?.modelCatalog?.()\n ?.then(value => { if (live) setChoices(parseModelMenuChoices(value, bound)) })\n .catch(() => { if (live) setChoices([]) })\n }\n load()\n return () => { live = false }\n }, [client, bound?.provider, bound?.model])\n return choices\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiCA,SAAgB,oBAAoB,QAA4B,OAA2C;CACzG,MAAM,CAAC,SAAS,cAAc,SAA4B,CAAC,CAAC;CAC5D,gBAAgB;EACd,IAAI,OAAO,aAAa,aAAa;EACrC,IAAI,OAAO;EACX,MAAM,aAAa;GACjB,OAAY,QAAQ,SAAS,eAAe,CAAC,EACzC,MAAK,UAAS;IAAE,IAAI,MAAM,WAAWA,wBAAsB,OAAO,KAAK,CAAC;GAAE,CAAC,CAAC,CAC7E,YAAY;IAAE,IAAI,MAAM,WAAW,CAAC,CAAC;GAAE,CAAC;EAC7C;EACA,KAAK;EACL,aAAa;GAAE,OAAO;EAAM;CAC9B,GAAG;EAAC;EAAQ,OAAO;EAAU,OAAO;CAAK,CAAC;CAC1C,OAAO;AACT"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { TypertRemoteContribution } from "@deepseek-ai/dsh-typert-protocol";
|
|
2
|
+
//#region src/model-settings-client.d.ts
|
|
3
|
+
type ModelSettingsChangeFrame = {
|
|
4
|
+
kind: 'ready';
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'changed';
|
|
7
|
+
ns: string;
|
|
8
|
+
revision: number;
|
|
9
|
+
};
|
|
10
|
+
/** This stream carries invalidations only; ready is never a settings snapshot. */
|
|
11
|
+
declare function parseModelSettingsChangeFrame(value: unknown): ModelSettingsChangeFrame;
|
|
12
|
+
/** One contribution mounted by Model Hub; consumers only inject its service. */
|
|
13
|
+
declare const modelSettingsChangesRemote: TypertRemoteContribution;
|
|
14
|
+
interface StreamItem {
|
|
15
|
+
generation: number;
|
|
16
|
+
value: unknown;
|
|
17
|
+
signal: AbortSignal;
|
|
18
|
+
accept(): void;
|
|
19
|
+
}
|
|
20
|
+
interface ChangeStream extends AsyncIterable<StreamItem> {
|
|
21
|
+
dispose(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
interface ModelSettingsSubscriberScope {
|
|
24
|
+
remote: {
|
|
25
|
+
modelSettingsChanges: {
|
|
26
|
+
watch(signal: AbortSignal): AsyncIterable<unknown>;
|
|
27
|
+
};
|
|
28
|
+
$stream(options: {
|
|
29
|
+
name: string;
|
|
30
|
+
open(signal: AbortSignal): AsyncIterable<unknown>;
|
|
31
|
+
ended(accepted: boolean): Error;
|
|
32
|
+
}): ChangeStream;
|
|
33
|
+
};
|
|
34
|
+
effect(callback: () => () => void | Promise<void>): unknown;
|
|
35
|
+
}
|
|
36
|
+
interface ModelSettingsSubscriberContext {
|
|
37
|
+
inject(dependencies: string[], callback: (scope: unknown) => void): unknown;
|
|
38
|
+
}
|
|
39
|
+
interface ModelSettingsSubscriber {
|
|
40
|
+
subscribe(onChange: () => void, onError?: (message: string) => void): () => void;
|
|
41
|
+
}
|
|
42
|
+
/** Native RemoteStream owns carrier reconnects; no timers or browser transport.
|
|
43
|
+
* A factory shares one logical stream while at least one subscriber remains. */
|
|
44
|
+
declare function createModelSettingsSubscriber(ctx: ModelSettingsSubscriberContext, namespace: string): ModelSettingsSubscriber;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { ModelSettingsChangeFrame, ModelSettingsSubscriber, ModelSettingsSubscriberContext, ModelSettingsSubscriberScope, createModelSettingsSubscriber, modelSettingsChangesRemote, parseModelSettingsChangeFrame };
|
|
47
|
+
//# sourceMappingURL=model-settings-client.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { MODEL_SETTINGS_CHANGES_REMOTE } from "./contracts.js";
|
|
2
|
+
//#region src/model-settings-client.ts
|
|
3
|
+
/** This stream carries invalidations only; ready is never a settings snapshot. */
|
|
4
|
+
function parseModelSettingsChangeFrame(value) {
|
|
5
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid model settings change frame");
|
|
6
|
+
const frame = value;
|
|
7
|
+
if (frame.kind === "ready" && Object.keys(frame).length === 1) return { kind: "ready" };
|
|
8
|
+
if (frame.kind === "changed" && Object.keys(frame).length === 3 && typeof frame.ns === "string" && frame.ns.length > 0 && typeof frame.revision === "number" && Number.isSafeInteger(frame.revision) && frame.revision >= 0) return {
|
|
9
|
+
kind: "changed",
|
|
10
|
+
ns: frame.ns,
|
|
11
|
+
revision: frame.revision
|
|
12
|
+
};
|
|
13
|
+
throw new Error("Invalid model settings change frame");
|
|
14
|
+
}
|
|
15
|
+
/** One contribution mounted by Model Hub; consumers only inject its service. */
|
|
16
|
+
const modelSettingsChangesRemote = {
|
|
17
|
+
package: MODEL_SETTINGS_CHANGES_REMOTE.package,
|
|
18
|
+
descriptors: MODEL_SETTINGS_CHANGES_REMOTE.descriptors.map((descriptor) => ({
|
|
19
|
+
...descriptor,
|
|
20
|
+
result: {
|
|
21
|
+
mode: "strict",
|
|
22
|
+
typeSymbol: "ModelSettings.ChangeFrame",
|
|
23
|
+
create: () => ({ parse: parseModelSettingsChangeFrame })
|
|
24
|
+
}
|
|
25
|
+
}))
|
|
26
|
+
};
|
|
27
|
+
/** Native RemoteStream owns carrier reconnects; no timers or browser transport.
|
|
28
|
+
* A factory shares one logical stream while at least one subscriber remains. */
|
|
29
|
+
function createModelSettingsSubscriber(ctx, namespace) {
|
|
30
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
31
|
+
let injection;
|
|
32
|
+
let current;
|
|
33
|
+
let lifecycle = 0;
|
|
34
|
+
let failureMessage;
|
|
35
|
+
const report = (message) => {
|
|
36
|
+
failureMessage = message;
|
|
37
|
+
for (const listener of [...listeners]) try {
|
|
38
|
+
listener.error?.(message);
|
|
39
|
+
} catch {}
|
|
40
|
+
};
|
|
41
|
+
const invalidate = () => {
|
|
42
|
+
for (const listener of [...listeners]) {
|
|
43
|
+
if (!listeners.has(listener)) continue;
|
|
44
|
+
try {
|
|
45
|
+
listener.change();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
try {
|
|
48
|
+
listener.error?.(error instanceof Error ? error.message : String(error));
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const start = () => {
|
|
54
|
+
const life = ++lifecycle;
|
|
55
|
+
injection = ctx.inject(["remote", "remote.modelSettingsChanges"], (injected) => {
|
|
56
|
+
if (life !== lifecycle || !listeners.size) return;
|
|
57
|
+
const scope = injected;
|
|
58
|
+
const stream = scope.remote.$stream({
|
|
59
|
+
name: "Model settings sync",
|
|
60
|
+
open: (signal) => scope.remote.modelSettingsChanges.watch(signal),
|
|
61
|
+
ended: (accepted) => /* @__PURE__ */ new Error(`Model settings sync stream ended ${accepted ? "after" : "before"} ready; refresh or reopen the page`)
|
|
62
|
+
});
|
|
63
|
+
const session = {
|
|
64
|
+
stream,
|
|
65
|
+
active: true
|
|
66
|
+
};
|
|
67
|
+
current = session;
|
|
68
|
+
scope.effect(() => () => {
|
|
69
|
+
session.active = false;
|
|
70
|
+
if (current === session) {
|
|
71
|
+
current = void 0;
|
|
72
|
+
if (life === lifecycle && listeners.size) report("Model settings sync service unavailable");
|
|
73
|
+
}
|
|
74
|
+
return stream.dispose();
|
|
75
|
+
});
|
|
76
|
+
(async () => {
|
|
77
|
+
let generation;
|
|
78
|
+
let ready = false;
|
|
79
|
+
try {
|
|
80
|
+
for await (const item of stream) {
|
|
81
|
+
if (!session.active || life !== lifecycle || !listeners.size) break;
|
|
82
|
+
if (item.signal.aborted) continue;
|
|
83
|
+
if (generation !== item.generation) {
|
|
84
|
+
generation = item.generation;
|
|
85
|
+
ready = false;
|
|
86
|
+
}
|
|
87
|
+
const frame = parseModelSettingsChangeFrame(item.value);
|
|
88
|
+
if (frame.kind === "ready") {
|
|
89
|
+
ready = true;
|
|
90
|
+
failureMessage = void 0;
|
|
91
|
+
item.accept();
|
|
92
|
+
invalidate();
|
|
93
|
+
} else {
|
|
94
|
+
if (!ready) throw new Error("Model settings sync change arrived before ready");
|
|
95
|
+
if (namespace === "*" || frame.ns === namespace) invalidate();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (session.active && life === lifecycle) report(error instanceof Error ? error.message : String(error));
|
|
100
|
+
} finally {
|
|
101
|
+
if (current === session) current = void 0;
|
|
102
|
+
session.active = false;
|
|
103
|
+
await stream.dispose().catch(() => {});
|
|
104
|
+
}
|
|
105
|
+
})();
|
|
106
|
+
});
|
|
107
|
+
queueMicrotask(() => {
|
|
108
|
+
if (life === lifecycle && listeners.size && !current) report("Model settings sync service unavailable");
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
return Object.freeze({ subscribe(onChange, onError) {
|
|
112
|
+
const listener = {
|
|
113
|
+
change: onChange,
|
|
114
|
+
error: onError
|
|
115
|
+
};
|
|
116
|
+
listeners.add(listener);
|
|
117
|
+
if (!injection) start();
|
|
118
|
+
else if (failureMessage) queueMicrotask(() => {
|
|
119
|
+
if (listeners.has(listener) && failureMessage) try {
|
|
120
|
+
listener.error?.(failureMessage);
|
|
121
|
+
} catch {}
|
|
122
|
+
});
|
|
123
|
+
return () => {
|
|
124
|
+
if (!listeners.delete(listener) || listeners.size) return;
|
|
125
|
+
lifecycle++;
|
|
126
|
+
if (current) {
|
|
127
|
+
current.active = false;
|
|
128
|
+
current.stream.dispose().catch(() => {});
|
|
129
|
+
}
|
|
130
|
+
current = void 0;
|
|
131
|
+
failureMessage = void 0;
|
|
132
|
+
const previous = injection;
|
|
133
|
+
injection = void 0;
|
|
134
|
+
try {
|
|
135
|
+
Promise.resolve(previous?.dispose()).catch(() => {});
|
|
136
|
+
} catch {}
|
|
137
|
+
};
|
|
138
|
+
} });
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { createModelSettingsSubscriber, modelSettingsChangesRemote, parseModelSettingsChangeFrame };
|
|
142
|
+
|
|
143
|
+
//# sourceMappingURL=model-settings-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-settings-client.js","names":[],"sources":["../src/model-settings-client.ts"],"sourcesContent":["import type { TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol'\nimport { MODEL_SETTINGS_CHANGES_REMOTE } from './contracts.ts'\n\nexport type ModelSettingsChangeFrame = { kind: 'ready' } | { kind: 'changed'; ns: string; revision: number }\n\n/** This stream carries invalidations only; ready is never a settings snapshot. */\nexport function parseModelSettingsChangeFrame(value: unknown): ModelSettingsChangeFrame {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid model settings change frame')\n const frame = value as Record<string, unknown>\n if (frame.kind === 'ready' && Object.keys(frame).length === 1) return { kind: 'ready' }\n if (frame.kind === 'changed' && Object.keys(frame).length === 3 && typeof frame.ns === 'string' && frame.ns.length > 0\n && typeof frame.revision === 'number' && Number.isSafeInteger(frame.revision) && frame.revision >= 0) {\n return { kind: 'changed', ns: frame.ns, revision: frame.revision }\n }\n throw new Error('Invalid model settings change frame')\n}\n\n/** One contribution mounted by Model Hub; consumers only inject its service. */\nexport const modelSettingsChangesRemote: TypertRemoteContribution = {\n package: MODEL_SETTINGS_CHANGES_REMOTE.package,\n descriptors: MODEL_SETTINGS_CHANGES_REMOTE.descriptors.map(descriptor => ({\n ...descriptor,\n result: { mode: 'strict' as const, typeSymbol: 'ModelSettings.ChangeFrame', create: () => ({ parse: parseModelSettingsChangeFrame }) },\n })),\n}\n\ninterface StreamItem {\n generation: number\n value: unknown\n signal: AbortSignal\n accept(): void\n}\ninterface ChangeStream extends AsyncIterable<StreamItem> { dispose(): Promise<void> }\nexport interface ModelSettingsSubscriberScope {\n remote: {\n modelSettingsChanges: { watch(signal: AbortSignal): AsyncIterable<unknown> }\n $stream(options: {\n name: string\n open(signal: AbortSignal): AsyncIterable<unknown>\n ended(accepted: boolean): Error\n }): ChangeStream\n }\n effect(callback: () => () => void | Promise<void>): unknown\n}\nexport interface ModelSettingsSubscriberContext {\n inject(dependencies: string[], callback: (scope: unknown) => void): unknown\n}\nexport interface ModelSettingsSubscriber {\n subscribe(onChange: () => void, onError?: (message: string) => void): () => void\n}\n\n/** Native RemoteStream owns carrier reconnects; no timers or browser transport.\n * A factory shares one logical stream while at least one subscriber remains. */\nexport function createModelSettingsSubscriber(ctx: ModelSettingsSubscriberContext, namespace: string): ModelSettingsSubscriber {\n const listeners = new Set<{ change: () => void; error?: (message: string) => void }>()\n let injection: { dispose(): void | Promise<void> } | undefined\n let current: { stream: ChangeStream; active: boolean } | undefined\n let lifecycle = 0\n let failureMessage: string | undefined\n const report = (message: string) => {\n failureMessage = message\n for (const listener of [...listeners]) {\n try { listener.error?.(message) } catch { /* Isolate page callbacks. */ }\n }\n }\n const invalidate = () => {\n for (const listener of [...listeners]) {\n if (!listeners.has(listener)) continue\n try { listener.change() } catch (error) {\n try { listener.error?.(error instanceof Error ? error.message : String(error)) } catch { /* Isolate callbacks. */ }\n }\n }\n }\n const start = () => {\n const life = ++lifecycle\n injection = ctx.inject(['remote', 'remote.modelSettingsChanges'], injected => {\n if (life !== lifecycle || !listeners.size) return\n const scope = injected as ModelSettingsSubscriberScope\n const stream = scope.remote.$stream({\n name: 'Model settings sync',\n open: signal => scope.remote.modelSettingsChanges.watch(signal),\n ended: accepted => new Error(`Model settings sync stream ended ${accepted ? 'after' : 'before'} ready; refresh or reopen the page`),\n })\n const session = { stream, active: true }\n current = session\n scope.effect(() => () => {\n session.active = false\n if (current === session) {\n current = undefined\n if (life === lifecycle && listeners.size) report('Model settings sync service unavailable')\n }\n return stream.dispose()\n })\n void (async () => {\n let generation: number | undefined\n let ready = false\n try {\n for await (const item of stream) {\n if (!session.active || life !== lifecycle || !listeners.size) break\n if (item.signal.aborted) continue\n if (generation !== item.generation) { generation = item.generation; ready = false }\n const frame = parseModelSettingsChangeFrame(item.value)\n if (frame.kind === 'ready') {\n ready = true\n failureMessage = undefined\n // Accept the validated invalidation baseline, not fetched data.\n // Every current listener queues its own latest settings read.\n item.accept()\n invalidate()\n } else {\n if (!ready) throw new Error('Model settings sync change arrived before ready')\n if (namespace === '*' || frame.ns === namespace) invalidate()\n }\n }\n } catch (error) {\n if (session.active && life === lifecycle) report(error instanceof Error ? error.message : String(error))\n } finally {\n if (current === session) current = undefined\n session.active = false\n await stream.dispose().catch(() => {})\n }\n })()\n }) as { dispose(): void | Promise<void> }\n queueMicrotask(() => {\n if (life === lifecycle && listeners.size && !current) report('Model settings sync service unavailable')\n })\n }\n return Object.freeze({ subscribe(onChange: () => void, onError?: (message: string) => void) {\n const listener = { change: onChange, error: onError }\n listeners.add(listener)\n if (!injection) start()\n else if (failureMessage) queueMicrotask(() => {\n if (listeners.has(listener) && failureMessage) {\n try { listener.error?.(failureMessage) } catch { /* Isolate callbacks. */ }\n }\n })\n return () => {\n if (!listeners.delete(listener) || listeners.size) return\n lifecycle++\n if (current) { current.active = false; void current.stream.dispose().catch(() => {}) }\n current = undefined\n failureMessage = undefined\n const previous = injection\n injection = undefined\n try { void Promise.resolve(previous?.dispose()).catch(() => {}) } catch { /* Already unloaded. */ }\n }\n } })\n}\n"],"mappings":";;;AAMA,SAAgB,8BAA8B,OAA0C;CACtF,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,MAAM,IAAI,MAAM,qCAAqC;CACtH,MAAM,QAAQ;CACd,IAAI,MAAM,SAAS,WAAW,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG,OAAO,EAAE,MAAM,QAAQ;CACtF,IAAI,MAAM,SAAS,aAAa,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,KAAK,OAAO,MAAM,OAAO,YAAY,MAAM,GAAG,SAAS,KAChH,OAAO,MAAM,aAAa,YAAY,OAAO,cAAc,MAAM,QAAQ,KAAK,MAAM,YAAY,GACnG,OAAO;EAAE,MAAM;EAAW,IAAI,MAAM;EAAI,UAAU,MAAM;CAAS;CAEnE,MAAM,IAAI,MAAM,qCAAqC;AACvD;;AAGA,MAAa,6BAAuD;CAClE,SAAS,8BAA8B;CACvC,aAAa,8BAA8B,YAAY,KAAI,gBAAe;EACxE,GAAG;EACH,QAAQ;GAAE,MAAM;GAAmB,YAAY;GAA6B,eAAe,EAAE,OAAO,8BAA8B;EAAG;CACvI,EAAE;AACJ;;;AA6BA,SAAgB,8BAA8B,KAAqC,WAA4C;CAC7H,MAAM,4BAAY,IAAI,IAA+D;CACrF,IAAI;CACJ,IAAI;CACJ,IAAI,YAAY;CAChB,IAAI;CACJ,MAAM,UAAU,YAAoB;EAClC,iBAAiB;EACjB,KAAK,MAAM,YAAY,CAAC,GAAG,SAAS,GAClC,IAAI;GAAE,SAAS,QAAQ,OAAO;EAAE,QAAQ,CAAgC;CAE5E;CACA,MAAM,mBAAmB;EACvB,KAAK,MAAM,YAAY,CAAC,GAAG,SAAS,GAAG;GACrC,IAAI,CAAC,UAAU,IAAI,QAAQ,GAAG;GAC9B,IAAI;IAAE,SAAS,OAAO;GAAE,SAAS,OAAO;IACtC,IAAI;KAAE,SAAS,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;IAAE,QAAQ,CAA2B;GACpH;EACF;CACF;CACA,MAAM,cAAc;EAClB,MAAM,OAAO,EAAE;EACf,YAAY,IAAI,OAAO,CAAC,UAAU,6BAA6B,IAAG,aAAY;GAC5E,IAAI,SAAS,aAAa,CAAC,UAAU,MAAM;GAC3C,MAAM,QAAQ;GACd,MAAM,SAAS,MAAM,OAAO,QAAQ;IAClC,MAAM;IACN,OAAM,WAAU,MAAM,OAAO,qBAAqB,MAAM,MAAM;IAC9D,QAAO,6BAAY,IAAI,MAAM,oCAAoC,WAAW,UAAU,SAAS,mCAAmC;GACpI,CAAC;GACD,MAAM,UAAU;IAAE;IAAQ,QAAQ;GAAK;GACvC,UAAU;GACV,MAAM,mBAAmB;IACvB,QAAQ,SAAS;IACjB,IAAI,YAAY,SAAS;KACvB,UAAU,KAAA;KACV,IAAI,SAAS,aAAa,UAAU,MAAM,OAAO,yCAAyC;IAC5F;IACA,OAAO,OAAO,QAAQ;GACxB,CAAC;GACD,CAAM,YAAY;IAChB,IAAI;IACJ,IAAI,QAAQ;IACZ,IAAI;KACF,WAAW,MAAM,QAAQ,QAAQ;MAC/B,IAAI,CAAC,QAAQ,UAAU,SAAS,aAAa,CAAC,UAAU,MAAM;MAC9D,IAAI,KAAK,OAAO,SAAS;MACzB,IAAI,eAAe,KAAK,YAAY;OAAE,aAAa,KAAK;OAAY,QAAQ;MAAM;MAClF,MAAM,QAAQ,8BAA8B,KAAK,KAAK;MACtD,IAAI,MAAM,SAAS,SAAS;OAC1B,QAAQ;OACR,iBAAiB,KAAA;OAGjB,KAAK,OAAO;OACZ,WAAW;MACb,OAAO;OACL,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,iDAAiD;OAC7E,IAAI,cAAc,OAAO,MAAM,OAAO,WAAW,WAAW;MAC9D;KACF;IACF,SAAS,OAAO;KACd,IAAI,QAAQ,UAAU,SAAS,WAAW,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;IACzG,UAAU;KACR,IAAI,YAAY,SAAS,UAAU,KAAA;KACnC,QAAQ,SAAS;KACjB,MAAM,OAAO,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC;IACvC;GACF,EAAA,CAAG;EACL,CAAC;EACD,qBAAqB;GACnB,IAAI,SAAS,aAAa,UAAU,QAAQ,CAAC,SAAS,OAAO,yCAAyC;EACxG,CAAC;CACH;CACA,OAAO,OAAO,OAAO,EAAE,UAAU,UAAsB,SAAqC;EAC1F,MAAM,WAAW;GAAE,QAAQ;GAAU,OAAO;EAAQ;EACpD,UAAU,IAAI,QAAQ;EACtB,IAAI,CAAC,WAAW,MAAM;OACjB,IAAI,gBAAgB,qBAAqB;GAC5C,IAAI,UAAU,IAAI,QAAQ,KAAK,gBAC7B,IAAI;IAAE,SAAS,QAAQ,cAAc;GAAE,QAAQ,CAA2B;EAE9E,CAAC;EACD,aAAa;GACX,IAAI,CAAC,UAAU,OAAO,QAAQ,KAAK,UAAU,MAAM;GACnD;GACA,IAAI,SAAS;IAAE,QAAQ,SAAS;IAAO,QAAa,OAAO,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC;GAAE;GACrF,UAAU,KAAA;GACV,iBAAiB,KAAA;GACjB,MAAM,WAAW;GACjB,YAAY,KAAA;GACZ,IAAI;IAAE,QAAa,QAAQ,UAAU,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;GAAE,QAAQ,CAA0B;EACpG;CACF,EAAE,CAAC;AACL"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ModelRoute } from "@klarkxy/dsh-model-route";
|
|
2
|
+
//#region src/model-settings.d.ts
|
|
3
|
+
/** Built-in display copy; plain strings preserve third-party and user-provided text. */
|
|
4
|
+
type ModelSettingsText = string | {
|
|
5
|
+
zh: string;
|
|
6
|
+
en: string;
|
|
7
|
+
};
|
|
8
|
+
/** Plugin-owned model settings, independent of native volatile Config forms. */
|
|
9
|
+
interface ModelSettingsField {
|
|
10
|
+
path: string[];
|
|
11
|
+
label: ModelSettingsText;
|
|
12
|
+
description?: ModelSettingsText;
|
|
13
|
+
current?: ModelRoute;
|
|
14
|
+
editable?: false;
|
|
15
|
+
allowFollow?: boolean;
|
|
16
|
+
followLabel?: ModelSettingsText;
|
|
17
|
+
}
|
|
18
|
+
interface ModelSettingsSnapshot {
|
|
19
|
+
revision: number;
|
|
20
|
+
fields: ModelSettingsField[];
|
|
21
|
+
/** A separate model domain, e.g. external Codex; never use the DSH catalog here. */
|
|
22
|
+
catalog?: unknown;
|
|
23
|
+
catalogError?: ModelSettingsText;
|
|
24
|
+
}
|
|
25
|
+
interface ModelSettingsProvider {
|
|
26
|
+
ns: string;
|
|
27
|
+
title: ModelSettingsText;
|
|
28
|
+
instanceId?: string;
|
|
29
|
+
read(signal: AbortSignal): ModelSettingsSnapshot | Promise<ModelSettingsSnapshot>;
|
|
30
|
+
write(path: readonly string[], route: ModelRoute, expectedRevision: number, signal: AbortSignal): Promise<unknown>;
|
|
31
|
+
}
|
|
32
|
+
declare const MODEL_SETTINGS_EVENT = "model-settings/collect";
|
|
33
|
+
declare const MODEL_SETTINGS_CHANGED_EVENT = "model-settings/changed";
|
|
34
|
+
interface ModelSettingsChange {
|
|
35
|
+
ns: string;
|
|
36
|
+
revision: number;
|
|
37
|
+
}
|
|
38
|
+
/** Publish only after the owner's save commits. A failed observer must never
|
|
39
|
+
* turn an already committed write into an apparent save failure. */
|
|
40
|
+
declare function notifyModelSettingsChanged(ctx: {
|
|
41
|
+
parallel(name: typeof MODEL_SETTINGS_CHANGED_EVENT, change: ModelSettingsChange): Promise<void>;
|
|
42
|
+
}, ns: string, revision: number): void;
|
|
43
|
+
type Collector = (provider: ModelSettingsProvider) => void;
|
|
44
|
+
interface ModelSettingsEvents {
|
|
45
|
+
on(name: typeof MODEL_SETTINGS_EVENT, callback: (collect: Collector) => void): () => void;
|
|
46
|
+
}
|
|
47
|
+
interface ModelSettingsDiscovery {
|
|
48
|
+
parallel(name: typeof MODEL_SETTINGS_EVENT, collect: Collector): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
/** Cordis disposes the listener with its fiber. In-flight references also fail closed. */
|
|
51
|
+
declare function registerModelSettings(ctx: ModelSettingsEvents, provider: ModelSettingsProvider): () => void;
|
|
52
|
+
declare function collectModelSettings(ctx: ModelSettingsDiscovery): Promise<ModelSettingsProvider[]>;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { MODEL_SETTINGS_CHANGED_EVENT, MODEL_SETTINGS_EVENT, ModelSettingsChange, ModelSettingsDiscovery, ModelSettingsEvents, ModelSettingsField, ModelSettingsProvider, ModelSettingsSnapshot, ModelSettingsText, collectModelSettings, notifyModelSettingsChanged, registerModelSettings };
|
|
55
|
+
//# sourceMappingURL=model-settings.d.ts.map
|