@toddzheng024/dscode-bundle 0.7.26 → 0.7.28
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 +1 -1
- package/THIRD_PARTY_NOTICES.md +10 -10
- package/bootstrap.mjs +1 -1
- package/cordis.patch.yml +418 -45
- package/package.json +279 -245
- package/plugins/auto-review/index.mjs +8 -5
- package/plugins/code-review/index.mjs +1 -1
- package/plugins/computer-use/index.mjs +37 -0
- package/plugins/cordis-config/plain.mjs +31 -0
- package/plugins/exec/cli.mjs +1 -1
- package/plugins/grok/index.mjs +28 -24
- package/plugins/memory/index.mjs +1 -1
- package/plugins/message-source/kind.mjs +20 -0
- package/plugins/openrouter/adapter.mjs +29 -13
- package/plugins/openrouter/index.mjs +31 -28
- package/plugins/session-bridge/communication.mjs +6 -3
- package/plugins/session-bridge/server.mjs +3 -2
- package/plugins/session-bridge/tasks.mjs +3 -1
- package/plugins/session-cards/index.mjs +1 -1
- package/plugins/time-marks/index.mjs +3 -2
- package/plugins/time-marks/marks.mjs +6 -5
- package/plugins/triggers/config.mjs +1 -1
- package/plugins/triggers/tools.mjs +1 -1
- package/plugins/tui-tools/doctor.mjs +1 -1
- package/plugins/tui-tools/index.mjs +16 -4
- package/plugins/ultra/policy.mjs +16 -0
- package/vendor/bash/index.js +455 -186
- package/vendor/bash/types/background.d.ts +38 -7
- package/vendor/bash/types/index.d.ts +20 -4
- package/vendor/bash/types/render.d.ts +28 -9
- package/vendor/command-goal/index.js +3 -1
- package/vendor/deepseek/index.js +1378 -1246
- package/vendor/deepseek/types/adapter.d.ts +16 -155
- package/vendor/deepseek/types/config.d.ts +127 -0
- package/vendor/deepseek/types/defaults.d.ts +24 -0
- package/vendor/deepseek/types/file-store.d.ts +5 -3
- package/vendor/deepseek/types/files-api.d.ts +12 -9
- package/vendor/deepseek/types/image-tokens.d.ts +24 -9
- package/vendor/deepseek/types/images.d.ts +35 -0
- package/vendor/deepseek/types/index.d.ts +10 -94
- package/vendor/deepseek/types/messages-api.d.ts +10 -0
- package/vendor/deepseek/types/model-info.d.ts +16 -0
- package/vendor/deepseek/types/models.d.ts +4 -0
- package/vendor/deepseek/types/replay.d.ts +27 -0
- package/vendor/deepseek/types/request-extensions.d.ts +16 -0
- package/vendor/deepseek/types/request-files.d.ts +49 -0
- package/vendor/deepseek/types/request-pricing.d.ts +32 -20
- package/vendor/deepseek/types/serialize.d.ts +17 -82
- package/vendor/deepseek/types/sse.d.ts +6 -22
- package/vendor/deepseek/types/translate.d.ts +13 -36
- package/vendor/deepseek/types/transport.d.ts +15 -0
- package/vendor/deepseek/types/types.d.ts +106 -167
- package/vendor/deepseek/types/wire-types.d.ts +62 -0
- package/vendor/persistent/index.js +9 -3
- package/vendor/subagent/index.js +11 -8
- package/vendor/subagent/model-selection-settings.js +12 -38
- package/vendor/subagent/types/index.d.ts +4 -3
- package/vendor/subagent/types/model-selection-settings.d.ts +12 -10
- package/vendor/subagent-core/index.js +401 -264
- package/vendor/subagent-core/typert.host.js +103 -113
- package/vendor/subagent-core/typert.remote-client.d.ts +1 -3
- package/vendor/subagent-core/typert.remote-client.js +23 -69
- package/vendor/subagent-core/types/archive-admission.d.ts +16 -0
- package/vendor/subagent-core/types/archive-admission.js +151 -0
- package/vendor/subagent-core/types/assistant-output.d.ts +2 -2
- package/vendor/subagent-core/types/catalog.d.ts +17 -10
- package/vendor/subagent-core/types/catalog.js +12 -6
- package/vendor/subagent-core/types/child-agent.d.ts +8 -5
- package/vendor/subagent-core/types/child-agent.js +11 -5
- package/vendor/subagent-core/types/continuation-activation.d.ts +17 -1
- package/vendor/subagent-core/types/continuation-activation.js +41 -4
- package/vendor/subagent-core/types/continuation-messages.d.ts +1 -1
- package/vendor/subagent-core/types/continuation-messages.js +7 -3
- package/vendor/subagent-core/types/continuation.d.ts +1 -1
- package/vendor/subagent-core/types/continuation.js +2 -2
- package/vendor/subagent-core/types/control-types.d.ts +29 -36
- package/vendor/subagent-core/types/control-types.js +2 -3
- package/vendor/subagent-core/types/control.d.ts +2 -28
- package/vendor/subagent-core/types/control.js +3 -40
- package/vendor/subagent-core/types/index.d.ts +45 -41
- package/vendor/subagent-core/types/index.js +46 -82
- package/vendor/subagent-core/types/internal.d.ts +2 -2
- package/vendor/subagent-core/types/internal.js +2 -2
- package/vendor/subagent-core/types/lifecycle.d.ts +1 -1
- package/vendor/subagent-core/types/lifecycle.js +5 -3
- package/vendor/subagent-core/types/list-children.d.ts +15 -32
- package/vendor/subagent-core/types/list-children.js +45 -49
- package/vendor/subagent-core/types/out-of-process.d.ts +1 -1
- package/vendor/subagent-core/types/projection-types.d.ts +9 -1
- package/vendor/subagent-core/types/projection.d.ts +5 -0
- package/vendor/subagent-core/types/projection.js +10 -4
- package/vendor/subagent-core/types/run-settlement.js +1 -1
- package/vendor/subagent-core/types/types.d.ts +2 -2
- package/vendor/terminal/index.js +94 -30
- package/vendor/tui/lib/index.mjs +2 -1
- package/vendor/tui/lib/kernel-panels.mjs +1 -1
- package/vendor/tui/lib/render/projection.mjs +50 -36
- package/presets/dscode/agent.cordis.yml +0 -321
- package/presets/dscode/preset.yml +0 -3
|
@@ -24,6 +24,9 @@ export function apply(ctx, config) {
|
|
|
24
24
|
const calls = new WeakMap();
|
|
25
25
|
const budgets = new WeakMap();
|
|
26
26
|
const mode = agent => ctx.permissionPresets.current(agent.session);
|
|
27
|
+
// The preset this plugin reviews for. DSH 0.1.7 reserved `auto` for its own
|
|
28
|
+
// integration, so the reviewed preset carries DSCODE's own name.
|
|
29
|
+
const REVIEWED = 'auto-review';
|
|
27
30
|
const stateFor = agent => {
|
|
28
31
|
const events = agent.session.snapshotEvents();
|
|
29
32
|
const turn = events.findLast(e => e.type === 'turn/start')?.seq;
|
|
@@ -54,7 +57,7 @@ export function apply(ctx, config) {
|
|
|
54
57
|
});
|
|
55
58
|
|
|
56
59
|
const announce = (agent, text) => agent.inject(createUserMessage({
|
|
57
|
-
content: [{ type: 'text', text }], source: { kind:
|
|
60
|
+
content: [{ type: 'text', text }], source: { kind: name },
|
|
58
61
|
}));
|
|
59
62
|
const record = (req, data) => {
|
|
60
63
|
audit.append(req.agent.session.id, { sessionSeq: req.agent.session.seq, callId: req.callId ?? null, toolName: req.toolName, ...data });
|
|
@@ -64,7 +67,7 @@ export function apply(ctx, config) {
|
|
|
64
67
|
async function review(req, next, state) {
|
|
65
68
|
if (req.signal?.aborted) return 'cancelled';
|
|
66
69
|
// A user can switch to ask while a prior review waits in the queue.
|
|
67
|
-
if (mode(req.agent) !==
|
|
70
|
+
if (mode(req.agent) !== REVIEWED) return ctx.approval?.effectivePolicy?.(req.agent.session) === 'never' ? 'rejected' : next();
|
|
68
71
|
const exec = calls.get(req.agent)?.get(req.callId);
|
|
69
72
|
const fallback = async (reason, details = {}) => {
|
|
70
73
|
record(req, { decision: 'human', reason, ...details });
|
|
@@ -82,7 +85,7 @@ export function apply(ctx, config) {
|
|
|
82
85
|
record(req, { decision: 'cancelled', reason: 'Caller cancelled review.', ...details });
|
|
83
86
|
return 'cancelled';
|
|
84
87
|
}
|
|
85
|
-
if (mode(req.agent) !==
|
|
88
|
+
if (mode(req.agent) !== REVIEWED) return fallback('Permission mode changed while review was pending.', details);
|
|
86
89
|
if (decision.decision === 'human') return fallback(decision.reason, details);
|
|
87
90
|
// Bind approval to the still-pending immutable invocation; no grant cache.
|
|
88
91
|
if (calls.get(req.agent)?.get(req.callId) !== exec || fingerprint(action) !== actionHash) return fallback('Pending action changed during review.', details);
|
|
@@ -174,7 +177,7 @@ export function apply(ctx, config) {
|
|
|
174
177
|
...(effort === undefined ? {} : { reasoningEffort: effort }),
|
|
175
178
|
messages: [createSystemMessage(REVIEW_POLICY, name), createUserMessage({
|
|
176
179
|
content: [{ type: 'text', text: JSON.stringify({ action, context }) }],
|
|
177
|
-
source: { kind:
|
|
180
|
+
source: { kind: name },
|
|
178
181
|
})],
|
|
179
182
|
maxTokens: config.maxOutputTokens, signal,
|
|
180
183
|
})) {
|
|
@@ -231,7 +234,7 @@ export function apply(ctx, config) {
|
|
|
231
234
|
}
|
|
232
235
|
announce(req.agent, `Escalation budget reached (${grantBudget} per turn); asking the user.`);
|
|
233
236
|
}
|
|
234
|
-
if (mode(req.agent) !==
|
|
237
|
+
if (mode(req.agent) !== REVIEWED) return next();
|
|
235
238
|
const state = stateFor(req.agent);
|
|
236
239
|
// Serialize per agent so simultaneous calls cannot race the denial budget.
|
|
237
240
|
const pending = state.tail.then(() => review(req, next, state));
|
|
@@ -141,7 +141,7 @@ export async function independentReview(ctx, agent, options = {}, signal, collec
|
|
|
141
141
|
for await (const chunk of ctx.llm.stream({
|
|
142
142
|
provider: route.provider, model: route.model, reasoningEffort, purpose: 'review',
|
|
143
143
|
maxTokens: tokens,
|
|
144
|
-
system: POLICY, messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(request) }], source: { kind:
|
|
144
|
+
system: POLICY, messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(request) }], source: { kind: name } })], signal: deadline,
|
|
145
145
|
})) {
|
|
146
146
|
deadline.throwIfAborted();
|
|
147
147
|
assembler.push(chunk);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computer Use, mounted through DSCODE so a retired settings API still resolves.
|
|
3
|
+
*
|
|
4
|
+
* `@anionex/dsh-computer-use` reads its configuration with `ctx.settings.register`, an API
|
|
5
|
+
* DSH removed in 0.1.7: Settings now projects forms from the profile entry itself, and the
|
|
6
|
+
* missing method made the whole Computer Use bundle fail to activate. The package is
|
|
7
|
+
* pinned at 0.3.2 with no newer release, so this row restores the method — as a read-only
|
|
8
|
+
* view over the composition config, which is where DSCODE configures Computer Use anyway —
|
|
9
|
+
* and then mounts the upstream bundle as its child, which is what makes the order
|
|
10
|
+
* dependable: the shim is installed before the provider's constructor runs.
|
|
11
|
+
*
|
|
12
|
+
* Retire this wrapper when the package reads volatile configuration, and mount
|
|
13
|
+
* `@anionex/dsh-computer-use` directly again.
|
|
14
|
+
*
|
|
15
|
+
* @module dscode-computer-use
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import ComputerUseBundle from '@anionex/dsh-computer-use';
|
|
19
|
+
|
|
20
|
+
export const name = 'dscode-computer-use';
|
|
21
|
+
export const inject = ['settings'];
|
|
22
|
+
|
|
23
|
+
/** Section handle the 0.3.x provider expects: one read of the composition config, no live writes. */
|
|
24
|
+
function staticSection(base) {
|
|
25
|
+
return { get: () => base, watch: () => () => {} };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function apply(ctx, config = {}) {
|
|
29
|
+
const settings = ctx.settings;
|
|
30
|
+
// Only ever adds what upstream no longer defines; a Settings service that carries its
|
|
31
|
+
// own `register` again is left untouched, and the shim is dropped with this row.
|
|
32
|
+
if (typeof settings.register !== 'function') {
|
|
33
|
+
settings.register = (_namespace, _schema, options = {}) => staticSection(options.base ?? {});
|
|
34
|
+
ctx.effect(() => () => { delete settings.register; });
|
|
35
|
+
}
|
|
36
|
+
ctx.plugin(ComputerUseBundle, config);
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One plain snapshot of a Cordis plugin configuration.
|
|
3
|
+
*
|
|
4
|
+
* Two shapes make a foreign entry's `config` unusable as a plain object. DSH 0.1.7 hands
|
|
5
|
+
* a `.volatile()` field to its own plugin as a reference rather than a value, and the
|
|
6
|
+
* Loader keeps a YAML `!!js` scalar as an unevaluated `{ __jsExpr }` node until the entry
|
|
7
|
+
* activates — which a reader inspecting another entry's row (the skills conflict report
|
|
8
|
+
* walks every `skill-filesystem` entry, and a preset's rows carry such expressions) sees
|
|
9
|
+
* instead of the value. References are read; an expression cannot be evaluated outside the
|
|
10
|
+
* Loader's own scope, so it reads as absent and the caller applies its own fallback.
|
|
11
|
+
*
|
|
12
|
+
* @param config - a configuration object whose fields may be references or expressions.
|
|
13
|
+
* @returns a shallow copy carrying only plainly readable values.
|
|
14
|
+
*/
|
|
15
|
+
export function plainConfig(config) {
|
|
16
|
+
if (config === null || typeof config !== 'object') return {};
|
|
17
|
+
const entries = Object.entries(config)
|
|
18
|
+
.map(([key, value]) => [key, isReference(value) ? value.get() : value])
|
|
19
|
+
.filter(([, value]) => !isExpression(value));
|
|
20
|
+
return Object.fromEntries(entries);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Whether a configuration field is a Cordis value reference rather than a plain value. */
|
|
24
|
+
function isReference(value) {
|
|
25
|
+
return value !== null && typeof value === 'object' && typeof value.get === 'function';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Whether a configuration field is still an unevaluated Loader expression node. */
|
|
29
|
+
function isExpression(value) {
|
|
30
|
+
return value !== null && typeof value === 'object' && '__jsExpr' in value;
|
|
31
|
+
}
|
package/plugins/exec/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@ Options:
|
|
|
9
9
|
--cwd DIR workspace for the agent (default: current directory)
|
|
10
10
|
--model PROVIDER/ID model route (default: the saved default model)
|
|
11
11
|
--effort LEVEL reasoning effort the model offers: off, minimal, low, medium, high, xhigh, max or ultra
|
|
12
|
-
--permission PRESET permission preset: auto, ask, workspace-write, read-only, danger-full-access
|
|
12
|
+
--permission PRESET permission preset: auto-review, ask, workspace-write, read-only, danger-full-access
|
|
13
13
|
--approve-all answer every approval request with allow (no human is present)
|
|
14
14
|
--resume SESSION_ID continue an existing session instead of starting a new one
|
|
15
15
|
--json emit JSON lines (session, text, tool, result) instead of plain text
|
package/plugins/grok/index.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { RetryPolicySchema, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
4
|
+
import { deepEqualJson } from "@deepseek-ai/dsh-util-values";
|
|
5
|
+
import { plainConfig } from "../cordis-config/plain.mjs";
|
|
4
6
|
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
5
7
|
import { GrokAdapter, PROVIDER } from "./adapter.mjs";
|
|
6
8
|
import { ensureGrokModels } from "./models.mjs";
|
|
@@ -19,14 +21,18 @@ const DEFAULT_BASE_URL = "https://api.x.ai/v1";
|
|
|
19
21
|
/** The client version the CLI proxy is asked with; xAI refuses requests without one. */
|
|
20
22
|
const DEFAULT_CLIENT_VERSION = "1.0.34";
|
|
21
23
|
|
|
24
|
+
// DSH 0.1.7 replaced settings sections with volatile configuration: each `.volatile()`
|
|
25
|
+
// field arrives as a reference the Settings form and a profile edit both write, so the
|
|
26
|
+
// reads below always see the current value.
|
|
22
27
|
export const Config = z.object({
|
|
23
|
-
apiKeyEnv: z.string().role("credential-ref").default(GROK_TOKEN_REF),
|
|
24
|
-
baseURL: z.string().default(DEFAULT_BASE_URL),
|
|
25
|
-
clientVersion: z.string().default(DEFAULT_CLIENT_VERSION),
|
|
26
|
-
streamIdleTimeoutMs: z.number().min(1).default(300000),
|
|
27
|
-
retryPolicy: RetryPolicySchema,
|
|
28
|
+
apiKeyEnv: z.string().role("credential-ref").default(GROK_TOKEN_REF).volatile(),
|
|
29
|
+
baseURL: z.string().default(DEFAULT_BASE_URL).volatile(),
|
|
30
|
+
clientVersion: z.string().default(DEFAULT_CLIENT_VERSION).volatile(),
|
|
31
|
+
streamIdleTimeoutMs: z.number().min(1).default(300000).volatile(),
|
|
32
|
+
retryPolicy: RetryPolicySchema.volatile(),
|
|
28
33
|
});
|
|
29
34
|
|
|
35
|
+
|
|
30
36
|
/** Validated connection facts from one config snapshot. */
|
|
31
37
|
export function resolveOptions(config = {}) {
|
|
32
38
|
const streamIdleTimeoutMs = config.streamIdleTimeoutMs ?? 300000;
|
|
@@ -50,18 +56,18 @@ export function loginHint(state) {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
export function apply(ctx, config = {}) {
|
|
53
|
-
|
|
59
|
+
// The TUI renders this route's own page, so Settings must not generate a form for it.
|
|
60
|
+
ctx.inject(["settings"], settingsCtx => {
|
|
61
|
+
settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, ctx.fiber));
|
|
62
|
+
});
|
|
63
|
+
let lastGood;
|
|
54
64
|
const options = () => {
|
|
55
|
-
const raw = current();
|
|
56
|
-
if (raw === lastRaw && lastGood !== undefined) return lastGood;
|
|
57
65
|
try {
|
|
58
|
-
lastGood = resolveOptions(
|
|
59
|
-
lastRaw = raw;
|
|
66
|
+
lastGood = resolveOptions(plainConfig(config));
|
|
60
67
|
return lastGood;
|
|
61
68
|
} catch (error) {
|
|
62
69
|
if (lastGood === undefined) throw error;
|
|
63
|
-
|
|
64
|
-
ctx.logger.error(name + ": keeping the last good configuration after an invalid settings section");
|
|
70
|
+
ctx.logger.error(name + ": keeping the last good configuration after an invalid edit");
|
|
65
71
|
return lastGood;
|
|
66
72
|
}
|
|
67
73
|
};
|
|
@@ -89,19 +95,17 @@ export function apply(ctx, config = {}) {
|
|
|
89
95
|
await ensureGrokModels({ home, token, version: options().clientVersion });
|
|
90
96
|
};
|
|
91
97
|
const adapter = new GrokAdapter({ options, ensureModels, resolveToken });
|
|
92
|
-
ctx.llm.registerConfigurableProviders([{ provider: PROVIDER, displayName: "Grok", settingsNs: NS, settingsPath: [] }]);
|
|
98
|
+
ctx.llm.registerConfigurableProviders([{ provider: PROVIDER, displayName: "Grok", settingsNs: ctx.fiber.entry?.options.id ?? NS, settingsPath: [] }]);
|
|
93
99
|
const registration = ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
},
|
|
104
|
-
});
|
|
100
|
+
// The retry policy is captured at registration, and a reference carries no change
|
|
101
|
+
// callback: re-register the route when an edit lands on a different policy.
|
|
102
|
+
let registeredPolicy = options().retryPolicy;
|
|
103
|
+
ctx.on("loader/volatile-update", () => {
|
|
104
|
+
let policy;
|
|
105
|
+
try { policy = options().retryPolicy; } catch (error) { ctx.logger.warn(error); return; }
|
|
106
|
+
if (deepEqualJson(policy, registeredPolicy)) return;
|
|
107
|
+
registration.replace([PROVIDER]);
|
|
108
|
+
registeredPolicy = policy;
|
|
105
109
|
});
|
|
106
110
|
// Warm the catalog and the weekly window for a user who already ran `grok login`, then keep
|
|
107
111
|
// the window fresh at the interval the official CLI itself watches (subscription_watch_interval_secs).
|
package/plugins/memory/index.mjs
CHANGED
|
@@ -48,7 +48,7 @@ export function apply(ctx, options = {}) {
|
|
|
48
48
|
await chargeTo(live.has(lastSession) ? lastSession : undefined, 'memory', async () => {
|
|
49
49
|
for await (const chunk of ctx.llm.stream({
|
|
50
50
|
...target, ...(reasoningEffort ? { reasoningEffort } : {}),
|
|
51
|
-
system, messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(input) }], source: { kind:
|
|
51
|
+
system, messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(input) }], source: { kind: name } })],
|
|
52
52
|
maxTokens: 12000, signal: deadline,
|
|
53
53
|
})) {
|
|
54
54
|
deadline.throwIfAborted(); assembler.push(chunk);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Producer identity of one message source.
|
|
3
|
+
*
|
|
4
|
+
* DSH 0.1.7 retired the shared `plugin` kind: every producer now declares its own
|
|
5
|
+
* `kind`, and DSCODE's plugins name themselves the same way. Durable history carries two
|
|
6
|
+
* older shapes — the raw `{ kind: 'plugin', plugin: '<producer>' }` of a session written
|
|
7
|
+
* before the upgrade, and the `plugin:<producer>` kind the session format's v3-to-v4
|
|
8
|
+
* migration writes for a producer it does not know — so every reader of history resolves
|
|
9
|
+
* all three through this one function.
|
|
10
|
+
*
|
|
11
|
+
* @param source - a message source from any release.
|
|
12
|
+
* @returns the producing subsystem's name, or `''` when the source names none.
|
|
13
|
+
*/
|
|
14
|
+
export function producerKind(source) {
|
|
15
|
+
if (source === null || typeof source !== 'object') return '';
|
|
16
|
+
const { kind, plugin } = source;
|
|
17
|
+
if (kind === 'plugin') return typeof plugin === 'string' ? plugin : '';
|
|
18
|
+
if (typeof kind !== 'string') return '';
|
|
19
|
+
return kind.startsWith('plugin:') ? kind.slice('plugin:'.length) : kind;
|
|
20
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LlmAdapter, LlmError, ReasoningEffortId, attributionHeaders, contentHasImage,
|
|
1
|
+
import { IMAGE_OFFLOAD_REQUIRED_CODE, LlmAdapter, LlmError, ReasoningEffortId, attributionHeaders, contentHasImage, offloadedImageText, projectOffloadedImages, requiredImageOffload } from '@deepseek-ai/dsh-llm';
|
|
2
2
|
import { listOpenRouterModels, openRouterModel } from './models.mjs';
|
|
3
3
|
import { PROVIDER, effortInfo, errorCode, errorMessage, modelReasoning, requestBody, retryAfterMs, sseData, translate } from './wire.mjs';
|
|
4
4
|
|
|
@@ -157,22 +157,38 @@ export class OpenRouterAdapter extends LlmAdapter {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
/**
|
|
160
|
+
/**
|
|
161
|
+
* Read request versions of every retained image.
|
|
162
|
+
*
|
|
163
|
+
* DSH 0.1.7 made the offloaded set a durable surface fact: a route projects what the
|
|
164
|
+
* surface already offloaded into text and reports how much more must go with
|
|
165
|
+
* `IMAGE_OFFLOAD_REQUIRED`, instead of dropping the oldest occurrences itself. The
|
|
166
|
+
* `compaction-image-offload` executor records that choice and retries, so the decision
|
|
167
|
+
* survives a route change, resume and replay rather than being remade per request.
|
|
168
|
+
*/
|
|
161
169
|
async prepareImages(options, entry, connection, signal) {
|
|
162
|
-
if (!options.messages.some(message => contentHasImage(message.content))) return undefined;
|
|
163
|
-
if (!entry?.inputModalities?.includes('image')) throw new LlmError(`OpenRouter model "${options.model}" does not accept image input.`, 'UNSUPPORTED_CONTENT');
|
|
164
170
|
const attachments = this.config.resolveAttachments?.();
|
|
171
|
+
const access = ref => (attachments === undefined ? undefined : this.config.resolveImageAccess?.(attachments, ref));
|
|
172
|
+
const messages = projectOffloadedImages(options.messages, ref => offloadedImageText(ref, access(ref)));
|
|
173
|
+
const withImages = messages.some(message => contentHasImage(message.content));
|
|
174
|
+
if (!withImages) return messages === options.messages ? undefined : { options: { ...options, messages: [...messages] }, versions: new Map(), access };
|
|
175
|
+
if (!entry?.inputModalities?.includes('image')) throw new LlmError(`OpenRouter model "${options.model}" does not accept image input.`, 'UNSUPPORTED_CONTENT');
|
|
165
176
|
if (attachments === undefined) throw new LlmError('OpenRouter image input requires the durable attachment service.', 'UNSUPPORTED_CONTENT');
|
|
166
|
-
const access = ref => this.config.resolveImageAccess?.(attachments, ref);
|
|
167
|
-
const bounded = policyBytes => offloadRequestImagesWithPolicy(policyBytes.messages, {
|
|
168
|
-
representation: 'base64', maxBytes: connection.maxRequestImageBytes, byteQuantum: 1,
|
|
169
|
-
byteLength: policyBytes.byteLength, placeholder: ref => offloadedImageText(ref, access(ref)),
|
|
170
|
-
});
|
|
171
|
-
const estimated = bounded({ messages: options.messages, byteLength: ref => Math.min(ref.bytes, IMAGE_POLICY.maxBytes) });
|
|
172
177
|
const refs = new Map();
|
|
173
|
-
for (const message of
|
|
178
|
+
for (const message of messages) collectImages(message.content, refs);
|
|
174
179
|
const versions = new Map(await Promise.all([...refs.values()].map(async ref => [ref.attachmentId, await attachments.readImageRequest(ref, IMAGE_POLICY, signal)])));
|
|
175
|
-
const
|
|
176
|
-
|
|
180
|
+
const offloadImages = requiredImageOffload(
|
|
181
|
+
messages,
|
|
182
|
+
{ representation: 'base64', maxBytes: connection.maxRequestImageBytes, byteQuantum: 1 },
|
|
183
|
+
block => versions.get(block.attachment.attachmentId).bytes,
|
|
184
|
+
);
|
|
185
|
+
if (offloadImages > 0) {
|
|
186
|
+
throw new LlmError(
|
|
187
|
+
`OpenRouter request images exceed ${connection.maxRequestImageBytes} bytes; ${offloadImages} more oldest occurrence(s) must be offloaded.`,
|
|
188
|
+
IMAGE_OFFLOAD_REQUIRED_CODE,
|
|
189
|
+
{ offloadImages },
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
return { options: { ...options, messages: [...messages] }, versions, access };
|
|
177
193
|
}
|
|
178
194
|
}
|
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
import z from '@deepseek-ai/schemastery';
|
|
2
2
|
import { LlmError, RetryPolicySchema, assertUsableApiKey, resolveImageAttachmentAccess, resolveRetryPolicy } from '@deepseek-ai/dsh-llm';
|
|
3
3
|
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
4
|
+
import { deepEqualJson } from '@deepseek-ai/dsh-util-values';
|
|
5
|
+
import { plainConfig } from '../cordis-config/plain.mjs';
|
|
4
6
|
import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment';
|
|
5
7
|
import { OpenRouterAdapter, PROVIDER } from './adapter.mjs';
|
|
6
8
|
import { ensureOpenRouterModels } from './models.mjs';
|
|
7
9
|
import { OpenRouterSearchProvider, RoutedSearchProvider } from './search.mjs';
|
|
8
10
|
|
|
9
11
|
// The `openrouter` route: DSCODE's own OpenRouter adapter over its live model
|
|
10
|
-
// listing, configured by
|
|
11
|
-
//
|
|
12
|
+
// listing, configured by this entry's own profile row, plus web search that
|
|
13
|
+
// follows the session's route.
|
|
12
14
|
export const name = 'dscode-openrouter';
|
|
13
15
|
export const inject = ['llm'];
|
|
14
16
|
const NS = 'llm-openrouter';
|
|
15
17
|
const DEFAULT_BASE_URL = 'https://openrouter.ai/api/v1';
|
|
16
18
|
|
|
19
|
+
// DSH 0.1.7 replaced settings sections with volatile configuration: a field marked
|
|
20
|
+
// `.volatile()` reaches the plugin as a reference the Settings form and a profile edit
|
|
21
|
+
// both write, so each read below sees the current value without a change callback.
|
|
17
22
|
export const Config = z.object({
|
|
18
|
-
apiKeyEnv: z.string().role('credential-ref').default('OPENROUTER_API_KEY'),
|
|
19
|
-
baseURL: z.string().default(DEFAULT_BASE_URL),
|
|
20
|
-
streamIdleTimeoutMs: z.number().min(1).default(300000),
|
|
21
|
-
maxRequestImageBytes: z.number().step(1).min(1).default(20 * 1024 * 1024),
|
|
22
|
-
searchModel: z.string().default('deepseek/deepseek-v4-flash'),
|
|
23
|
-
retryPolicy: RetryPolicySchema,
|
|
23
|
+
apiKeyEnv: z.string().role('credential-ref').default('OPENROUTER_API_KEY').volatile(),
|
|
24
|
+
baseURL: z.string().default(DEFAULT_BASE_URL).volatile(),
|
|
25
|
+
streamIdleTimeoutMs: z.number().min(1).default(300000).volatile(),
|
|
26
|
+
maxRequestImageBytes: z.number().step(1).min(1).default(20 * 1024 * 1024).volatile(),
|
|
27
|
+
searchModel: z.string().default('deepseek/deepseek-v4-flash').volatile(),
|
|
28
|
+
retryPolicy: RetryPolicySchema.volatile(),
|
|
24
29
|
});
|
|
25
30
|
|
|
31
|
+
|
|
26
32
|
/** Validated connection facts from one config snapshot. */
|
|
27
33
|
export function resolveOptions(config = {}) {
|
|
28
34
|
const streamIdleTimeoutMs = config.streamIdleTimeoutMs ?? 300000;
|
|
@@ -40,18 +46,18 @@ export function resolveOptions(config = {}) {
|
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
export function apply(ctx, config = {}) {
|
|
43
|
-
|
|
49
|
+
// The TUI renders this route's own page, so Settings must not generate a form for it.
|
|
50
|
+
ctx.inject(['settings'], settingsCtx => {
|
|
51
|
+
settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, ctx.fiber));
|
|
52
|
+
});
|
|
53
|
+
let lastGood;
|
|
44
54
|
const options = () => {
|
|
45
|
-
const raw = current();
|
|
46
|
-
if (raw === lastRaw && lastGood !== undefined) return lastGood;
|
|
47
55
|
try {
|
|
48
|
-
lastGood = resolveOptions(
|
|
49
|
-
lastRaw = raw;
|
|
56
|
+
lastGood = resolveOptions(plainConfig(config));
|
|
50
57
|
return lastGood;
|
|
51
58
|
} catch (error) {
|
|
52
59
|
if (lastGood === undefined) throw error;
|
|
53
|
-
|
|
54
|
-
ctx.logger.error(`${name}: keeping the last good configuration after an invalid settings section`);
|
|
60
|
+
ctx.logger.error(`${name}: keeping the last good configuration after an invalid edit`);
|
|
55
61
|
ctx.logger.error(error);
|
|
56
62
|
return lastGood;
|
|
57
63
|
}
|
|
@@ -75,20 +81,17 @@ export function apply(ctx, config = {}) {
|
|
|
75
81
|
resolveAttachments: () => ctx.get('attachments'),
|
|
76
82
|
resolveImageAccess: (attachments, ref) => resolveImageAttachmentAccess(attachments, hostPath => ctx.get('fs')?.processPathFromHostPath(hostPath), ref),
|
|
77
83
|
});
|
|
78
|
-
ctx.llm.registerConfigurableProviders([{ provider: PROVIDER, displayName: 'OpenRouter', settingsNs: NS, settingsPath: [] }]);
|
|
84
|
+
ctx.llm.registerConfigurableProviders([{ provider: PROVIDER, displayName: 'OpenRouter', settingsNs: ctx.fiber.entry?.options.id ?? NS, settingsPath: [] }]);
|
|
79
85
|
const registration = ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
registeredPolicy = policy;
|
|
90
|
-
},
|
|
91
|
-
});
|
|
86
|
+
// The retry policy is captured at registration, and a reference carries no change
|
|
87
|
+
// callback: re-register the route when an edit lands on a different policy.
|
|
88
|
+
let registeredPolicy = options().retryPolicy;
|
|
89
|
+
ctx.on('loader/volatile-update', () => {
|
|
90
|
+
let policy;
|
|
91
|
+
try { policy = options().retryPolicy; } catch (error) { ctx.logger.warn(error); return; }
|
|
92
|
+
if (deepEqualJson(policy, registeredPolicy)) return;
|
|
93
|
+
registration.replace([PROVIDER]);
|
|
94
|
+
registeredPolicy = policy;
|
|
92
95
|
});
|
|
93
96
|
ctx.inject(['web'], webCtx => {
|
|
94
97
|
const openrouter = new OpenRouterSearchProvider(() => {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createUserMessage, freezeMessage } from '@deepseek-ai/dsh-llm';
|
|
2
2
|
import { Mailbox, fail } from './mailbox.mjs';
|
|
3
|
+
import { producerKind } from '../message-source/kind.mjs';
|
|
3
4
|
import { discover, request as socketRequest } from './client.mjs';
|
|
4
5
|
|
|
5
6
|
const plugin = 'dscode-session-bridge';
|
|
6
|
-
export const communicationId = m => m?.source
|
|
7
|
+
export const communicationId = m => producerKind(m?.source) === plugin ? m.source.communicationId : undefined;
|
|
7
8
|
export const isHuman = m => ['user', 'human'].includes(m?.source?.kind);
|
|
8
9
|
const eligible = a => a?.session.header.agentPreset === 'dscode' || a?.session.header.origin === 'subagent';
|
|
9
10
|
|
|
@@ -17,7 +18,9 @@ export class CommunicationService {
|
|
|
17
18
|
this.pending = new Set();
|
|
18
19
|
this.closed = false;
|
|
19
20
|
this.disposers = [
|
|
20
|
-
|
|
21
|
+
// DSH 0.1.7 retired `agent/session-start`: `agent/created` is awaited at the same
|
|
22
|
+
// point, once the session is entered and announced.
|
|
23
|
+
ctx.on('agent/created', ({ agent }) => this.start(agent)),
|
|
21
24
|
ctx.on('agent/pre-step', (payload, next) => this.preStep(payload, next)),
|
|
22
25
|
ctx.on('session/event', (session, event) => this.observe(session, event)),
|
|
23
26
|
ctx.on('agent/inbox/discarded', ({ agent, message }) => {
|
|
@@ -102,7 +105,7 @@ export class CommunicationService {
|
|
|
102
105
|
// Identity must survive retries even when the prior native inbox insertion did not persist.
|
|
103
106
|
return freezeMessage({ ...createUserMessage({ content: [{ type: 'text', text:
|
|
104
107
|
`[External source: ${e.from.kind === 'session' ? `session:${e.from.sessionId}` : e.from.source}] [${e.kind}/${e.mode}]\nMessage ID: ${e.messageId}${e.inReplyTo ? `; reply to: ${e.inReplyTo}` : ''}\n${e.text}` }],
|
|
105
|
-
source: { kind:
|
|
108
|
+
source: { kind: plugin, form: 'relay', communicationId: e.messageId, requestId: e.idempotencyKey,
|
|
106
109
|
label: e.from.kind === 'session' ? `session:${e.from.sessionId}` : e.from.source, mode: e.mode, composedAt: e.createdAt } }), id: e.messageId });
|
|
107
110
|
}
|
|
108
111
|
async confirm(state) {
|
|
@@ -5,6 +5,7 @@ import { randomUUID, createHash } from 'node:crypto';
|
|
|
5
5
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
6
6
|
import { normalizeSessionTitle } from '@deepseek-ai/dsh-session-title';
|
|
7
7
|
import { ensureSocketDirectory } from './paths.mjs';
|
|
8
|
+
import { producerKind } from '../message-source/kind.mjs';
|
|
8
9
|
|
|
9
10
|
export const BRIDGE_SOURCE = 'dscode-session-bridge';
|
|
10
11
|
const MAX_REQUEST = 128 * 1024, MAX_BUFFER = 8 * 1024 * 1024;
|
|
@@ -51,7 +52,7 @@ export class SessionBridge {
|
|
|
51
52
|
if (!cache) { cache = { seq: 0, values: new Map() }; this.receipts.set(session, cache); }
|
|
52
53
|
for (const event of session.snapshotEvents(cache.seq)) {
|
|
53
54
|
const messages = event.type === 'agent/inbox/spliced' ? event.data.inserted : event.type === 'user/message' ? [event.data] : [];
|
|
54
|
-
for (const message of messages) if (message.source
|
|
55
|
+
for (const message of messages) if (producerKind(message.source) === BRIDGE_SOURCE && message.source.requestId) {
|
|
55
56
|
cache.values.set(message.source.requestId, { digest: message.source.digest, messageId: message.id });
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -82,7 +83,7 @@ export class SessionBridge {
|
|
|
82
83
|
let messageId = previous?.messageId;
|
|
83
84
|
if (!previous) {
|
|
84
85
|
const message = createUserMessage({ content: [{ type: 'text', text: `[External source: ${source}]\n${text}` }],
|
|
85
|
-
source: { kind:
|
|
86
|
+
source: { kind: BRIDGE_SOURCE, form: 'relay', requestId, digest, label: source, mode } });
|
|
86
87
|
// Explicit naming uses the native title service, including sanitization,
|
|
87
88
|
// persistence, UI events and cancellation of stale automatic title work.
|
|
88
89
|
// A duplicate request must never undo a newer title.
|
|
@@ -21,6 +21,8 @@ const MAX_WAITING_REQUESTS = 16
|
|
|
21
21
|
const MAX_PREVIEW_CHARS = 72
|
|
22
22
|
|
|
23
23
|
/** The plugin name the session bridge stamps on its relayed messages. */
|
|
24
|
+
import { producerKind } from '../message-source/kind.mjs'
|
|
25
|
+
|
|
24
26
|
const BRIDGE_PLUGIN = 'dscode-session-bridge'
|
|
25
27
|
|
|
26
28
|
/** Tool names that address another session. */
|
|
@@ -67,7 +69,7 @@ function relayFields(data) {
|
|
|
67
69
|
if (data === null || typeof data !== 'object') return undefined
|
|
68
70
|
const source = data.source
|
|
69
71
|
if (source === null || typeof source !== 'object') return undefined
|
|
70
|
-
if (source
|
|
72
|
+
if (producerKind(source) !== BRIDGE_PLUGIN || source.form !== 'relay') return undefined
|
|
71
73
|
const label = typeof source.label === 'string' && source.label !== '' ? source.label : undefined
|
|
72
74
|
const id = typeof source.communicationId === 'string' && source.communicationId !== '' ? source.communicationId : undefined
|
|
73
75
|
if (label === undefined && id === undefined) return undefined
|
|
@@ -16,7 +16,7 @@ export function apply(ctx, config = {}) {
|
|
|
16
16
|
const reasoningEffort = await effortFor(ctx.llm, base, 'low', signal);
|
|
17
17
|
await chargeTo(sessionId, 'session-card', async () => {
|
|
18
18
|
for await (const chunk of ctx.llm.stream({ ...base, ...(reasoningEffort ? { reasoningEffort } : {}), system: TOPIC_PROMPT,
|
|
19
|
-
messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(input) }], source: { kind:
|
|
19
|
+
messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(input) }], source: { kind: name } })],
|
|
20
20
|
maxTokens: 2000, signal })) {
|
|
21
21
|
signal.throwIfAborted(); assembler.push(chunk);
|
|
22
22
|
if (chunk.type === 'finish') finished = true;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import z from '@deepseek-ai/schemastery';
|
|
13
13
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
14
14
|
import { arrivalMark, resolveTimeZone, turnEndMark } from './marks.mjs';
|
|
15
|
+
import { producerKind } from '../message-source/kind.mjs';
|
|
15
16
|
export const name = 'dscode-time-marks';
|
|
16
17
|
export const Config = z.object({
|
|
17
18
|
/** Fallback display zone; empty uses the host zone. */
|
|
@@ -42,7 +43,7 @@ export function apply(ctx, config = {}) {
|
|
|
42
43
|
for (const message of payload.messages) {
|
|
43
44
|
// Marks are injected context, never an inbox arrival; skipping our own
|
|
44
45
|
// producer keeps a future delivery path from marking itself.
|
|
45
|
-
if (message.source
|
|
46
|
+
if (producerKind(message.source) === name) continue;
|
|
46
47
|
const line = arrivalMark(message, at, timeZone);
|
|
47
48
|
if (line !== null && lines.length < MARK_LIMIT) lines.push(line);
|
|
48
49
|
}
|
|
@@ -60,7 +61,7 @@ export function apply(ctx, config = {}) {
|
|
|
60
61
|
// the prompt there, not this mark.
|
|
61
62
|
messages: [createUserMessage({
|
|
62
63
|
content: [{ type: 'text', text: lines.join('\n') }],
|
|
63
|
-
source: { kind:
|
|
64
|
+
source: { kind: name, form: 'snapshot' },
|
|
64
65
|
}), ...decision.messages],
|
|
65
66
|
};
|
|
66
67
|
}, { prepend: true });
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* @module dscode-time-marks/marks
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { producerKind } from '../message-source/kind.mjs'
|
|
11
|
+
|
|
10
12
|
/** Shortest queue wait worth naming; below it the message simply arrived. */
|
|
11
13
|
const WAIT_FLOOR_MS = 1_000
|
|
12
14
|
|
|
@@ -108,15 +110,14 @@ export function elapsedText(ms) {
|
|
|
108
110
|
* @returns the attribution clause.
|
|
109
111
|
*/
|
|
110
112
|
export function describeSource(source) {
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
if (
|
|
114
|
-
if (source.form === 'relay') {
|
|
113
|
+
const producer = producerKind(source)
|
|
114
|
+
if (producer === 'user') return 'user message'
|
|
115
|
+
if (source?.form === 'relay') {
|
|
115
116
|
const mode = plain(source.mode) || 'queue'
|
|
116
117
|
const label = plain(source.label) || 'another session'
|
|
117
118
|
return `${mode} relay from ${label}`
|
|
118
119
|
}
|
|
119
|
-
return `message from ${
|
|
120
|
+
return producer === '' ? 'message' : `message from ${producer}`
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
/**
|
|
@@ -34,7 +34,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
/** Permission presets an unattended run may use; `ask` can never complete. */
|
|
37
|
-
const UNATTENDED_PERMISSIONS = Object.freeze(['auto', 'workspace-write', 'read-only', 'danger-full-access']);
|
|
37
|
+
const UNATTENDED_PERMISSIONS = Object.freeze(['auto-review', 'auto', 'workspace-write', 'read-only', 'danger-full-access']);
|
|
38
38
|
|
|
39
39
|
const ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/u;
|
|
40
40
|
|
|
@@ -12,7 +12,7 @@ export function mutationProblem(ctx, agent) {
|
|
|
12
12
|
const plan = ctx.get('planMode')?.get(agent);
|
|
13
13
|
if (plan?.active || plan?.pending) return 'Schedule management is unavailable in plan mode';
|
|
14
14
|
const permission = ctx.permissionPresets.current(agent.session);
|
|
15
|
-
if (!['auto', 'ask', 'workspace-write', 'danger-full-access'].includes(permission)) return 'Schedule management requires a writable permission preset';
|
|
15
|
+
if (!['auto-review', 'auto', 'ask', 'workspace-write', 'danger-full-access'].includes(permission)) return 'Schedule management requires a writable permission preset';
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function registerTriggerTools(ctx, { home, ...deps }) {
|
|
@@ -131,7 +131,7 @@ export async function analyzeDoctorEvidence(ctx, evidence, route, signal, { mode
|
|
|
131
131
|
const reasoningEffort = await effortFor(ctx.llm, route, 'low', deadline);
|
|
132
132
|
await chargeTo(sessionId, 'doctor', async () => {
|
|
133
133
|
for await (const chunk of ctx.llm.stream({ provider: route.provider, model: route.model, ...(reasoningEffort ? { reasoningEffort } : {}), maxTokens: 4096, system: SYSTEM,
|
|
134
|
-
messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(evidence) }], source: { kind: '
|
|
134
|
+
messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(evidence) }], source: { kind: 'dscode-doctor' } })], signal: deadline })) {
|
|
135
135
|
deadline.throwIfAborted(); assembler.push(chunk);
|
|
136
136
|
if (chunk.type === 'finish') finished = true;
|
|
137
137
|
}
|