lody 0.90.0 → 0.91.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/dist/chunks/{index-Bag7dEYc.js → index-BNIS69wD.js} +3055 -994
- package/dist/chunks/{profile-CPDWaQXE.js → profile-DLuYzIs8.js} +14 -37
- package/dist/chunks/{review-viewer-CGdWKpLa.js → review-viewer-BxVOWMjT.js} +2 -2
- package/dist/codex-acp.js +1 -1
- package/dist/deepseek-acp.js +438 -153
- package/dist/index.js +204 -145
- package/package.json +1 -1
package/dist/deepseek-acp.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID, createHash } from "node:crypto";
|
|
|
2
2
|
import { isAbsolute } from "node:path";
|
|
3
3
|
import { Readable, Writable } from "node:stream";
|
|
4
4
|
import { n as ndJsonStream, A as AgentSideConnection, R as RequestError, P as PROTOCOL_VERSION } from "./chunks/acp-DBqkNziA.js";
|
|
5
|
-
import {
|
|
5
|
+
import { j as DEEPSEEK_HARNESS_BASE_URL_ENV, k as DEEPSEEK_HARNESS_API_KEY_ENV, b as ACP_EXTENSION_DSH_VERSION, a as DEEPSEEK_HARNESS_AGENT_PRESETS } from "./chunks/profile-DLuYzIs8.js";
|
|
6
6
|
import "./chunks/schemas-Du3qLZPS.js";
|
|
7
7
|
const name = "acp-extension-dsh";
|
|
8
8
|
const inject = [
|
|
@@ -22,10 +22,15 @@ const MODEL_CONFIG_ID = "model";
|
|
|
22
22
|
const MODE_CONFIG_ID = "mode";
|
|
23
23
|
const REASONING_EFFORT_CONFIG_ID = "reasoning_effort";
|
|
24
24
|
const AGENT_PRESET_CONFIG_ID = "agent_preset";
|
|
25
|
+
const PERMISSION_EVENT_TYPES = /* @__PURE__ */ new Set(["permission/preset", "sandbox/mode", "approval/policy"]);
|
|
25
26
|
const MCP_CLIENT_PACKAGE = "@deepseek-ai/dsh-mcp-client";
|
|
26
27
|
const MCP_TOOL_CALL_TIMEOUT_MS = 6e4;
|
|
27
28
|
const MCP_SERVER_NAME_MAX_LENGTH = 32;
|
|
28
29
|
const MCP_SERVER_NAME_HASH_LENGTH = 8;
|
|
30
|
+
const MODEL_DISCOVERY_TIMEOUT_MS = 15e3;
|
|
31
|
+
const MODEL_DISCOVERY_MAX_BYTES = 1024 * 1024;
|
|
32
|
+
const MODEL_DISCOVERY_MAX_MODELS = 1e3;
|
|
33
|
+
const MODEL_ID_MAX_LENGTH = 512;
|
|
29
34
|
const INVALID_MCP_SERVER_NAME_CHARS = /[^A-Za-z0-9_-]/gu;
|
|
30
35
|
const IMAGE_MEDIA_TYPES = [
|
|
31
36
|
"image/png",
|
|
@@ -45,8 +50,6 @@ const IMAGE_ADMISSION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
45
50
|
"IMAGE_TOO_MANY_PIXELS",
|
|
46
51
|
"IMAGE_DIMENSION_TOO_LARGE"
|
|
47
52
|
]);
|
|
48
|
-
const PERMISSION_MODE_IDS = new Set(DEEPSEEK_HARNESS_PERMISSION_MODES.map((mode) => mode.id));
|
|
49
|
-
const REASONING_EFFORT_IDS = new Set(DEEPSEEK_HARNESS_REASONING_OPTIONS.map((effort) => effort.value));
|
|
50
53
|
function invalidParams(detail) {
|
|
51
54
|
return RequestError.invalidParams(void 0, detail);
|
|
52
55
|
}
|
|
@@ -59,23 +62,19 @@ function nonEmptyString(value, fallback) {
|
|
|
59
62
|
function resolveAdapterConfig(config) {
|
|
60
63
|
const provider = nonEmptyString(config?.provider, "deepseek-official");
|
|
61
64
|
const model = nonEmptyString(config?.model, "deepseek-v4-pro");
|
|
62
|
-
const reasoningEffort = config?.reasoningEffort ?? "max";
|
|
63
|
-
if (!REASONING_EFFORT_IDS.has(reasoningEffort)) {
|
|
64
|
-
throw new Error(`acp-extension-dsh: unsupported reasoning effort ${JSON.stringify(reasoningEffort)}`);
|
|
65
|
-
}
|
|
66
65
|
return {
|
|
67
66
|
provider,
|
|
68
67
|
model,
|
|
69
|
-
reasoningEffort,
|
|
68
|
+
...config?.reasoningEffort ? { reasoningEffort: config.reasoningEffort } : {},
|
|
70
69
|
...config?.stream ? { stream: config.stream } : {}
|
|
71
70
|
};
|
|
72
71
|
}
|
|
73
|
-
async function loadHarnessModels(
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
72
|
+
async function loadHarnessModels(llm, provider, configuredModel, endpoint) {
|
|
73
|
+
const listed = endpoint ? (await discoverDeepSeekModelIds(endpoint.baseUrl, endpoint.apiKey)).map((id) => ({
|
|
74
|
+
provider,
|
|
75
|
+
id
|
|
76
|
+
})) : await llm.listModels(provider);
|
|
77
|
+
const modelIds = [];
|
|
79
78
|
const ids = /* @__PURE__ */ new Set();
|
|
80
79
|
for (const model of listed) {
|
|
81
80
|
if (model.provider !== provider || !model.id.trim())
|
|
@@ -84,12 +83,101 @@ async function loadHarnessModels(ctx, provider) {
|
|
|
84
83
|
throw new Error(`acp-extension-dsh: duplicate model ${JSON.stringify(model.id)} for provider ${JSON.stringify(provider)}`);
|
|
85
84
|
}
|
|
86
85
|
ids.add(model.id);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
modelIds.push(model.id);
|
|
87
|
+
}
|
|
88
|
+
if (endpoint && modelIds.length === 0) {
|
|
89
|
+
throw new Error("the endpoint returned no usable models");
|
|
90
|
+
}
|
|
91
|
+
if (!endpoint && !ids.has(configuredModel))
|
|
92
|
+
modelIds.unshift(configuredModel);
|
|
93
|
+
const initialModel = endpoint ? modelIds[0] : configuredModel;
|
|
94
|
+
return {
|
|
95
|
+
models: await Promise.all(modelIds.map((modelId) => resolveHarnessModel(llm, provider, modelId))),
|
|
96
|
+
initialModel
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function modelDiscoveryUrl(baseUrl) {
|
|
100
|
+
const url = new URL(baseUrl);
|
|
101
|
+
url.pathname = `${url.pathname.replace(/\/+$/u, "")}/models`;
|
|
102
|
+
url.hash = "";
|
|
103
|
+
return url;
|
|
104
|
+
}
|
|
105
|
+
async function readLimitedResponse(response) {
|
|
106
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
107
|
+
if (Number.isFinite(declaredLength) && declaredLength > MODEL_DISCOVERY_MAX_BYTES) {
|
|
108
|
+
throw new Error("model discovery response is too large");
|
|
109
|
+
}
|
|
110
|
+
if (!response.body)
|
|
111
|
+
return "";
|
|
112
|
+
const reader = response.body.getReader();
|
|
113
|
+
const decoder = new TextDecoder();
|
|
114
|
+
let text = "";
|
|
115
|
+
let bytes = 0;
|
|
116
|
+
while (true) {
|
|
117
|
+
const { done, value } = await reader.read();
|
|
118
|
+
if (done)
|
|
119
|
+
break;
|
|
120
|
+
bytes += value.byteLength;
|
|
121
|
+
if (bytes > MODEL_DISCOVERY_MAX_BYTES) {
|
|
122
|
+
await reader.cancel();
|
|
123
|
+
throw new Error("model discovery response is too large");
|
|
124
|
+
}
|
|
125
|
+
text += decoder.decode(value, { stream: true });
|
|
126
|
+
}
|
|
127
|
+
return text + decoder.decode();
|
|
128
|
+
}
|
|
129
|
+
async function discoverDeepSeekModelIds(baseUrl, apiKey) {
|
|
130
|
+
const headers = new Headers({ accept: "application/json" });
|
|
131
|
+
if (apiKey)
|
|
132
|
+
headers.set("authorization", `Bearer ${apiKey}`);
|
|
133
|
+
let response;
|
|
134
|
+
try {
|
|
135
|
+
response = await fetch(modelDiscoveryUrl(baseUrl), {
|
|
136
|
+
method: "GET",
|
|
137
|
+
headers,
|
|
138
|
+
redirect: "error",
|
|
139
|
+
signal: AbortSignal.timeout(MODEL_DISCOVERY_TIMEOUT_MS)
|
|
140
|
+
});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
throw new Error(`unable to request the endpoint model list: ${errorChain(error)}`, {
|
|
143
|
+
cause: error
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
throw new Error(`model discovery failed with HTTP ${response.status}`);
|
|
148
|
+
}
|
|
149
|
+
let body;
|
|
150
|
+
try {
|
|
151
|
+
body = JSON.parse(await readLimitedResponse(response));
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw new Error(`model discovery returned invalid JSON: ${errorChain(error)}`, {
|
|
154
|
+
cause: error
|
|
90
155
|
});
|
|
91
156
|
}
|
|
92
|
-
|
|
157
|
+
if (typeof body !== "object" || body === null || !("data" in body) || !Array.isArray(body.data) || body.data.length > MODEL_DISCOVERY_MAX_MODELS) {
|
|
158
|
+
throw new Error("model discovery returned an invalid OpenAI-compatible response");
|
|
159
|
+
}
|
|
160
|
+
const ids = [];
|
|
161
|
+
const seen = /* @__PURE__ */ new Set();
|
|
162
|
+
for (const entry of body.data) {
|
|
163
|
+
if (typeof entry !== "object" || entry === null || !("id" in entry))
|
|
164
|
+
continue;
|
|
165
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
166
|
+
if (!id || id.length > MODEL_ID_MAX_LENGTH || seen.has(id))
|
|
167
|
+
continue;
|
|
168
|
+
seen.add(id);
|
|
169
|
+
ids.push(id);
|
|
170
|
+
}
|
|
171
|
+
return ids;
|
|
172
|
+
}
|
|
173
|
+
async function resolveHarnessModel(llm, provider, modelId, signal) {
|
|
174
|
+
if (!modelId.trim())
|
|
175
|
+
throw invalidParams("model id must not be empty");
|
|
176
|
+
const model = await llm.resolveModelInfo(provider, modelId, signal);
|
|
177
|
+
if (model.provider !== provider || model.id !== modelId || !model.name?.trim()) {
|
|
178
|
+
throw new Error(`acp-extension-dsh: invalid exact model metadata for ${JSON.stringify(provider)} / ${JSON.stringify(modelId)}`);
|
|
179
|
+
}
|
|
180
|
+
return model;
|
|
93
181
|
}
|
|
94
182
|
async function loadMcpClientPlugin(agentContext) {
|
|
95
183
|
const module = agentContext.loader.unwrapExports(await agentContext.loader.import(MCP_CLIENT_PACKAGE));
|
|
@@ -175,12 +263,9 @@ async function mountMcpServers(agentContext, servers, serverNames, cwd) {
|
|
|
175
263
|
});
|
|
176
264
|
await Promise.all(handles.map((handle) => handle.await()));
|
|
177
265
|
}
|
|
178
|
-
function cloneSelection(selection) {
|
|
179
|
-
return { ...selection };
|
|
180
|
-
}
|
|
181
266
|
function installModelSelection(agentContext, selection) {
|
|
182
267
|
agentContext.on("system-prompt/assemble", async (_assembly, _context, next) => {
|
|
183
|
-
const selected =
|
|
268
|
+
const selected = { ...selection.current };
|
|
184
269
|
const assembled = await next();
|
|
185
270
|
selection.assembled = selected;
|
|
186
271
|
return {
|
|
@@ -200,12 +285,12 @@ function installModelSelection(agentContext, selection) {
|
|
|
200
285
|
...withoutInheritedEffort,
|
|
201
286
|
provider: selected.provider,
|
|
202
287
|
model: selected.model,
|
|
203
|
-
reasoningEffort: selected.reasoningEffort
|
|
288
|
+
...selected.reasoningEffort ? { reasoningEffort: selected.reasoningEffort } : {}
|
|
204
289
|
};
|
|
205
290
|
});
|
|
206
291
|
}
|
|
207
292
|
function configOptions(record) {
|
|
208
|
-
|
|
293
|
+
const options = [
|
|
209
294
|
{
|
|
210
295
|
id: MODE_CONFIG_ID,
|
|
211
296
|
name: "Permission",
|
|
@@ -213,8 +298,8 @@ function configOptions(record) {
|
|
|
213
298
|
category: "mode",
|
|
214
299
|
type: "select",
|
|
215
300
|
currentValue: record.permissionMode,
|
|
216
|
-
options:
|
|
217
|
-
value: mode.
|
|
301
|
+
options: record.permissionOptions.map((mode) => ({
|
|
302
|
+
value: mode.value,
|
|
218
303
|
name: mode.name,
|
|
219
304
|
description: mode.description ?? null
|
|
220
305
|
}))
|
|
@@ -242,39 +327,81 @@ function configOptions(record) {
|
|
|
242
327
|
category: "model",
|
|
243
328
|
type: "select",
|
|
244
329
|
currentValue: record.selection.current.model,
|
|
245
|
-
options: record.models.map((
|
|
246
|
-
value:
|
|
247
|
-
name:
|
|
248
|
-
description:
|
|
330
|
+
options: record.models.map((model2) => ({
|
|
331
|
+
value: model2.id,
|
|
332
|
+
name: model2.name,
|
|
333
|
+
description: model2.description ?? null
|
|
249
334
|
}))
|
|
250
|
-
}
|
|
251
|
-
|
|
335
|
+
}
|
|
336
|
+
];
|
|
337
|
+
const model = record.models.find((candidate) => candidate.id === record.selection.current.model);
|
|
338
|
+
if (model?.reasoning && record.selection.current.reasoningEffort) {
|
|
339
|
+
options.push({
|
|
252
340
|
id: REASONING_EFFORT_CONFIG_ID,
|
|
253
341
|
name: "Reasoning effort",
|
|
254
342
|
description: "How much reasoning effort the model should use",
|
|
255
343
|
category: "thought_level",
|
|
256
344
|
type: "select",
|
|
257
345
|
currentValue: record.selection.current.reasoningEffort,
|
|
258
|
-
options:
|
|
259
|
-
value: effort.
|
|
346
|
+
options: model.reasoning.efforts.map((effort) => ({
|
|
347
|
+
value: effort.id,
|
|
260
348
|
name: effort.name,
|
|
261
349
|
description: effort.description ?? null
|
|
262
350
|
}))
|
|
263
|
-
}
|
|
264
|
-
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return options;
|
|
354
|
+
}
|
|
355
|
+
function legacyModels(record) {
|
|
356
|
+
return {
|
|
357
|
+
currentModelId: record.selection.current.model,
|
|
358
|
+
availableModels: record.models.flatMap((model) => [
|
|
359
|
+
{
|
|
360
|
+
modelId: model.id,
|
|
361
|
+
name: model.name,
|
|
362
|
+
description: model.description ?? null
|
|
363
|
+
},
|
|
364
|
+
...model.reasoning?.efforts.map((effort) => ({
|
|
365
|
+
modelId: `${model.id}[${effort.id}]`,
|
|
366
|
+
name: `${model.name} (${effort.name})`,
|
|
367
|
+
description: model.description ?? null
|
|
368
|
+
})) ?? []
|
|
369
|
+
])
|
|
370
|
+
};
|
|
265
371
|
}
|
|
266
372
|
function modeState(record) {
|
|
267
373
|
return {
|
|
268
374
|
currentModeId: record.permissionMode,
|
|
269
|
-
availableModes:
|
|
270
|
-
id: mode.
|
|
375
|
+
availableModes: record.permissionOptions.map((mode) => ({
|
|
376
|
+
id: mode.value,
|
|
271
377
|
name: mode.name,
|
|
272
378
|
description: mode.description ?? null
|
|
273
379
|
}))
|
|
274
380
|
};
|
|
275
381
|
}
|
|
276
|
-
function
|
|
277
|
-
return
|
|
382
|
+
function supportsAcpImagePrompts(attachments, models) {
|
|
383
|
+
return attachments !== void 0 && attachments.imageLimits.mediaTypes.some((mediaType) => IMAGE_MEDIA_TYPES.includes(mediaType)) && models.some((model) => model.inputModalities?.includes("image"));
|
|
384
|
+
}
|
|
385
|
+
function resolveReasoningEffort(model, requested) {
|
|
386
|
+
const reasoning = model.reasoning;
|
|
387
|
+
if (!reasoning) {
|
|
388
|
+
if (requested) {
|
|
389
|
+
throw invalidParams(`model ${JSON.stringify(model.id)} does not support reasoning effort ${JSON.stringify(requested)}`);
|
|
390
|
+
}
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
const effort = requested ?? reasoning.defaultEffort;
|
|
394
|
+
if (!effort)
|
|
395
|
+
return void 0;
|
|
396
|
+
assertAllowed(effort, new Set(reasoning.efforts.map((candidate) => candidate.id)), `reasoning effort for model ${model.id}`);
|
|
397
|
+
return effort;
|
|
398
|
+
}
|
|
399
|
+
function permissionState(ctx, currentMode) {
|
|
400
|
+
const options = ctx.permissionPresets.names.map((mode) => ctx.permissionPresets.optionOf(mode));
|
|
401
|
+
if (!ctx.permissionPresets.names.includes(currentMode)) {
|
|
402
|
+
options.push(ctx.permissionPresets.optionOf(currentMode));
|
|
403
|
+
}
|
|
404
|
+
return options;
|
|
278
405
|
}
|
|
279
406
|
function imageMediaType(value) {
|
|
280
407
|
return IMAGE_MEDIA_TYPES.includes(value) ? value : void 0;
|
|
@@ -296,7 +423,7 @@ function decodePromptImage(block) {
|
|
|
296
423
|
function isImageAdmissionError(error) {
|
|
297
424
|
return error instanceof Error && "code" in error && typeof error.code === "string" && IMAGE_ADMISSION_ERROR_CODES.has(error.code);
|
|
298
425
|
}
|
|
299
|
-
async function admitAcpPrompt(prompt,
|
|
426
|
+
async function admitAcpPrompt(prompt, llm, selection, attachments, imagePromptEnabled, signal) {
|
|
300
427
|
const images = [];
|
|
301
428
|
for (const block of prompt) {
|
|
302
429
|
switch (block.type) {
|
|
@@ -304,8 +431,8 @@ async function admitAcpPrompt(prompt, models, modelId, attachments) {
|
|
|
304
431
|
case "resource_link":
|
|
305
432
|
break;
|
|
306
433
|
case "image":
|
|
307
|
-
if (!
|
|
308
|
-
throw invalidParams(
|
|
434
|
+
if (!imagePromptEnabled) {
|
|
435
|
+
throw invalidParams("inline image prompts were not advertised by this connection");
|
|
309
436
|
}
|
|
310
437
|
images.push(decodePromptImage(block));
|
|
311
438
|
break;
|
|
@@ -321,6 +448,17 @@ async function admitAcpPrompt(prompt, models, modelId, attachments) {
|
|
|
321
448
|
if (images.length > 0) {
|
|
322
449
|
if (!attachments)
|
|
323
450
|
throw internalError("no Harness attachment store is mounted");
|
|
451
|
+
signal.throwIfAborted();
|
|
452
|
+
let model;
|
|
453
|
+
try {
|
|
454
|
+
model = await resolveHarnessModel(llm, selection.provider, selection.model, signal);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
throw internalError(`unable to verify the current image model: ${errorChain(error)}`);
|
|
457
|
+
}
|
|
458
|
+
if (!model.inputModalities?.includes("image")) {
|
|
459
|
+
throw invalidParams(`model ${JSON.stringify(selection.model)} does not support image input`);
|
|
460
|
+
}
|
|
461
|
+
signal.throwIfAborted();
|
|
324
462
|
try {
|
|
325
463
|
refs = await attachments.saveImages(images);
|
|
326
464
|
} catch (error) {
|
|
@@ -328,6 +466,7 @@ async function admitAcpPrompt(prompt, models, modelId, attachments) {
|
|
|
328
466
|
throw invalidParams(error.message);
|
|
329
467
|
throw internalError("unable to persist the prompt image batch");
|
|
330
468
|
}
|
|
469
|
+
signal.throwIfAborted();
|
|
331
470
|
}
|
|
332
471
|
const content = [];
|
|
333
472
|
let pendingText = "";
|
|
@@ -359,6 +498,28 @@ async function admitAcpPrompt(prompt, models, modelId, attachments) {
|
|
|
359
498
|
}
|
|
360
499
|
return content;
|
|
361
500
|
}
|
|
501
|
+
async function assistantBlockToAcp(block, attachments) {
|
|
502
|
+
if (block.type === "text" && "text" in block) {
|
|
503
|
+
return block.text.length > 0 ? { type: "text", text: block.text } : void 0;
|
|
504
|
+
}
|
|
505
|
+
if (block.type !== "image" || !("attachment" in block))
|
|
506
|
+
return void 0;
|
|
507
|
+
if (!attachments)
|
|
508
|
+
throw new Error("cannot deliver assistant image: no attachment store is mounted");
|
|
509
|
+
let stored;
|
|
510
|
+
try {
|
|
511
|
+
stored = await attachments.readImage(block.attachment);
|
|
512
|
+
} catch (error) {
|
|
513
|
+
throw new Error("cannot deliver assistant image: the attachment is unavailable or corrupt", {
|
|
514
|
+
cause: error
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
type: "image",
|
|
519
|
+
data: Buffer.from(stored.data).toString("base64"),
|
|
520
|
+
mimeType: stored.ref.mediaType
|
|
521
|
+
};
|
|
522
|
+
}
|
|
362
523
|
function createUserMessage(id, content) {
|
|
363
524
|
return Object.freeze({
|
|
364
525
|
id,
|
|
@@ -367,22 +528,6 @@ function createUserMessage(id, content) {
|
|
|
367
528
|
source: Object.freeze({ kind: "user" })
|
|
368
529
|
});
|
|
369
530
|
}
|
|
370
|
-
function turnEndToStopReason(reason) {
|
|
371
|
-
switch (reason.kind) {
|
|
372
|
-
case "completed":
|
|
373
|
-
return "end_turn";
|
|
374
|
-
case "max-tokens":
|
|
375
|
-
return "max_tokens";
|
|
376
|
-
case "interrupted":
|
|
377
|
-
return "cancelled";
|
|
378
|
-
case "aborted":
|
|
379
|
-
case "blocked":
|
|
380
|
-
case "error":
|
|
381
|
-
return "end_turn";
|
|
382
|
-
default:
|
|
383
|
-
return "end_turn";
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
531
|
function errorChain(value) {
|
|
387
532
|
const seen = /* @__PURE__ */ new Set();
|
|
388
533
|
const render = (current) => {
|
|
@@ -431,11 +576,18 @@ function apply(ctx, rawConfig) {
|
|
|
431
576
|
const activeMcpServerNames = /* @__PURE__ */ new Set();
|
|
432
577
|
let closed = false;
|
|
433
578
|
let conn;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
579
|
+
let imagePromptEnabled = false;
|
|
580
|
+
if (ctx.permissionPresets.names.length === 0) {
|
|
581
|
+
throw new Error("acp-extension-dsh: no permission presets are composed");
|
|
438
582
|
}
|
|
583
|
+
const llm = ctx.get("llm");
|
|
584
|
+
if (!llm)
|
|
585
|
+
throw new Error("acp-extension-dsh: no Harness LLM catalog is mounted");
|
|
586
|
+
const attachments = ctx.get("attachments");
|
|
587
|
+
const baseUrl = process.env[DEEPSEEK_HARNESS_BASE_URL_ENV]?.trim();
|
|
588
|
+
const apiKey = process.env[DEEPSEEK_HARNESS_API_KEY_ENV]?.trim();
|
|
589
|
+
let modelCatalog;
|
|
590
|
+
const loadModelCatalog = () => modelCatalog ??= loadHarnessModels(llm, config.provider, config.model, baseUrl ? { baseUrl, ...apiKey ? { apiKey } : {} } : void 0);
|
|
439
591
|
const assertOpen = () => {
|
|
440
592
|
if (closed)
|
|
441
593
|
throw internalError("the ACP bridge has been disposed");
|
|
@@ -450,19 +602,96 @@ function apply(ctx, rawConfig) {
|
|
|
450
602
|
const record = sessions.get(agent.session.id);
|
|
451
603
|
return record?.agent === agent ? record : void 0;
|
|
452
604
|
};
|
|
453
|
-
const notify = (notification) => {
|
|
454
|
-
|
|
605
|
+
const notify = async (notification) => {
|
|
606
|
+
await conn.sessionUpdate(notification).catch((error) => {
|
|
455
607
|
ctx.logger.warn(`acp-extension-dsh: session/update failed: ${String(error)}`);
|
|
456
608
|
});
|
|
457
609
|
};
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
610
|
+
const enqueueOutput = (record, work, inflight) => {
|
|
611
|
+
record.outputTail = record.outputTail.then(work).catch((error) => {
|
|
612
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
613
|
+
if (inflight)
|
|
614
|
+
inflight.outputError ??= failure;
|
|
615
|
+
ctx.logger.warn(`acp-extension-dsh: assistant output failed: ${errorChain(error)}`);
|
|
616
|
+
});
|
|
617
|
+
};
|
|
618
|
+
const enqueueNotification = (record, notification, inflight) => enqueueOutput(record, () => notify(notification), inflight);
|
|
619
|
+
const settleAfterQuiescence = (record, inflight) => {
|
|
620
|
+
if (inflight.settlementStarted)
|
|
461
621
|
return;
|
|
462
|
-
|
|
463
|
-
|
|
622
|
+
inflight.settlementStarted = true;
|
|
623
|
+
void (async () => {
|
|
624
|
+
await inflight.admissionDone;
|
|
625
|
+
if (inflight.messageQueued) {
|
|
626
|
+
await record.agent.whenIdle();
|
|
627
|
+
await record.outputTail;
|
|
628
|
+
}
|
|
629
|
+
if (record.inflight !== inflight)
|
|
630
|
+
return;
|
|
631
|
+
record.inflight = void 0;
|
|
632
|
+
if (inflight.cancelRequested) {
|
|
633
|
+
inflight.resolve("cancelled");
|
|
634
|
+
} else if (inflight.outputError) {
|
|
635
|
+
inflight.reject(internalError(`assistant output delivery failed: ${inflight.outputError.message}`));
|
|
636
|
+
} else if (inflight.agentError) {
|
|
637
|
+
inflight.reject(internalError(`turn failed: ${inflight.agentError.message}`));
|
|
638
|
+
} else if (inflight.endReason?.kind === "error") {
|
|
639
|
+
inflight.reject(internalError(`turn failed: ${inflight.endReason.error.message}`));
|
|
640
|
+
} else {
|
|
641
|
+
const end = inflight.endReason;
|
|
642
|
+
inflight.resolve(end ? end.kind === "interrupted" ? "cancelled" : "end_turn" : "cancelled");
|
|
643
|
+
}
|
|
644
|
+
})().catch((error) => {
|
|
645
|
+
if (record.inflight !== inflight)
|
|
646
|
+
return;
|
|
647
|
+
record.inflight = void 0;
|
|
648
|
+
inflight.reject(internalError(`prompt settlement failed: ${errorChain(error)}`));
|
|
649
|
+
});
|
|
650
|
+
};
|
|
651
|
+
const refreshPermissionState = (record) => {
|
|
652
|
+
const permissionMode = ctx.permissionPresets.current(record.agent.session.events);
|
|
653
|
+
if (permissionMode === record.permissionMode)
|
|
654
|
+
return false;
|
|
655
|
+
record.permissionMode = permissionMode;
|
|
656
|
+
record.permissionOptions = permissionState(ctx, permissionMode);
|
|
657
|
+
return true;
|
|
658
|
+
};
|
|
659
|
+
const schedulePermissionSync = (record) => {
|
|
660
|
+
if (record.permissionSyncQueued)
|
|
661
|
+
return;
|
|
662
|
+
record.permissionSyncQueued = true;
|
|
663
|
+
queueMicrotask(() => {
|
|
664
|
+
record.permissionSyncQueued = false;
|
|
665
|
+
if (sessions.get(record.agent.session.id) !== record)
|
|
666
|
+
return;
|
|
667
|
+
try {
|
|
668
|
+
if (!refreshPermissionState(record))
|
|
669
|
+
return;
|
|
670
|
+
enqueueNotification(record, {
|
|
671
|
+
sessionId: record.agent.session.id,
|
|
672
|
+
update: {
|
|
673
|
+
sessionUpdate: "current_mode_update",
|
|
674
|
+
currentModeId: record.permissionMode
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
enqueueNotification(record, {
|
|
678
|
+
sessionId: record.agent.session.id,
|
|
679
|
+
update: {
|
|
680
|
+
sessionUpdate: "config_option_update",
|
|
681
|
+
configOptions: configOptions(record)
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
} catch (error) {
|
|
685
|
+
ctx.logger.warn(`acp-extension-dsh: failed to synchronize permission state: ${errorChain(error)}`);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
464
688
|
};
|
|
465
689
|
const disposeRecords = async (records) => {
|
|
690
|
+
await Promise.all(records.map(async (record) => {
|
|
691
|
+
await record.inflight?.admissionDone;
|
|
692
|
+
await record.agent.whenIdle();
|
|
693
|
+
await record.outputTail;
|
|
694
|
+
}));
|
|
466
695
|
const subagents = ctx.get("subagents");
|
|
467
696
|
if (subagents) {
|
|
468
697
|
try {
|
|
@@ -481,9 +710,11 @@ function apply(ctx, rawConfig) {
|
|
|
481
710
|
const record = sessions.get(session.header.id);
|
|
482
711
|
if (!record || record.agent.session !== session)
|
|
483
712
|
return;
|
|
713
|
+
if (PERMISSION_EVENT_TYPES.has(event.type))
|
|
714
|
+
schedulePermissionSync(record);
|
|
484
715
|
try {
|
|
485
716
|
if (event.type === "assistant/chunk" && event.data.chunk?.type === "reasoning-delta" && typeof event.data.chunk.text === "string" && event.data.chunk.text.length > 0) {
|
|
486
|
-
|
|
717
|
+
enqueueNotification(record, {
|
|
487
718
|
sessionId: record.agent.session.id,
|
|
488
719
|
update: {
|
|
489
720
|
sessionUpdate: "agent_thought_chunk",
|
|
@@ -491,7 +722,7 @@ function apply(ctx, rawConfig) {
|
|
|
491
722
|
}
|
|
492
723
|
});
|
|
493
724
|
} else if (event.type === "assistant/chunk" && event.data.chunk?.type === "block-end" && event.data.chunk.block?.type === "reasoning") {
|
|
494
|
-
|
|
725
|
+
enqueueNotification(record, {
|
|
495
726
|
sessionId: record.agent.session.id,
|
|
496
727
|
update: {
|
|
497
728
|
sessionUpdate: "agent_thought_chunk",
|
|
@@ -499,35 +730,25 @@ function apply(ctx, rawConfig) {
|
|
|
499
730
|
}
|
|
500
731
|
});
|
|
501
732
|
} else if (event.type === "assistant/message") {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
} else if (block.type === "image" && "attachment" in block) {
|
|
512
|
-
notify({
|
|
733
|
+
const inflight = record.inflight?.turn === event.data.turn ? record.inflight : void 0;
|
|
734
|
+
enqueueOutput(record, async () => {
|
|
735
|
+
for (const block of event.data.message?.content ?? []) {
|
|
736
|
+
const content = await assistantBlockToAcp(block, attachments);
|
|
737
|
+
if (!content)
|
|
738
|
+
continue;
|
|
739
|
+
await notify({
|
|
513
740
|
sessionId: record.agent.session.id,
|
|
514
|
-
update: {
|
|
515
|
-
sessionUpdate: "agent_message_chunk",
|
|
516
|
-
content: {
|
|
517
|
-
type: "text",
|
|
518
|
-
text: `[image attachment ${block.attachment.attachmentId}]`
|
|
519
|
-
}
|
|
520
|
-
}
|
|
741
|
+
update: { sessionUpdate: "agent_message_chunk", content }
|
|
521
742
|
});
|
|
522
743
|
}
|
|
523
|
-
}
|
|
744
|
+
}, inflight);
|
|
524
745
|
} else if (event.type === "compaction/start" && event.data.compactionId) {
|
|
525
746
|
const activity = {
|
|
526
747
|
version: 1,
|
|
527
748
|
kind: "context_compaction",
|
|
528
749
|
automatic: event.data.turn !== null
|
|
529
750
|
};
|
|
530
|
-
|
|
751
|
+
enqueueNotification(record, {
|
|
531
752
|
sessionId: record.agent.session.id,
|
|
532
753
|
update: {
|
|
533
754
|
sessionUpdate: "tool_call",
|
|
@@ -545,7 +766,7 @@ function apply(ctx, rawConfig) {
|
|
|
545
766
|
automatic: event.data.turn !== null,
|
|
546
767
|
...event.data.error ? { failureReason: event.data.error } : {}
|
|
547
768
|
};
|
|
548
|
-
|
|
769
|
+
enqueueNotification(record, {
|
|
549
770
|
sessionId: record.agent.session.id,
|
|
550
771
|
update: {
|
|
551
772
|
sessionUpdate: "tool_call_update",
|
|
@@ -559,12 +780,7 @@ function apply(ctx, rawConfig) {
|
|
|
559
780
|
} finally {
|
|
560
781
|
const inflight = record.inflight;
|
|
561
782
|
if (inflight && event.type === "turn/end" && inflight.turn === event.data.turn && event.data.reason) {
|
|
562
|
-
|
|
563
|
-
record.inflight = void 0;
|
|
564
|
-
inflight.reject(internalError(`turn failed: ${event.data.reason.error.message}`));
|
|
565
|
-
} else {
|
|
566
|
-
inflight.endReason = event.data.reason;
|
|
567
|
-
}
|
|
783
|
+
inflight.endReason = event.data.reason;
|
|
568
784
|
}
|
|
569
785
|
}
|
|
570
786
|
});
|
|
@@ -576,10 +792,10 @@ function apply(ctx, rawConfig) {
|
|
|
576
792
|
ctx.on("agent/error", ({ agent, turn, error }) => {
|
|
577
793
|
const record = ownedRecord(agent);
|
|
578
794
|
const inflight = record?.inflight;
|
|
579
|
-
if (!record || !inflight || inflight.
|
|
795
|
+
if (!record || !inflight || !inflight.messageQueued || inflight.turn !== turn)
|
|
580
796
|
return;
|
|
581
|
-
|
|
582
|
-
|
|
797
|
+
inflight.agentError = new Error(errorChain(error));
|
|
798
|
+
settleAfterQuiescence(record, inflight);
|
|
583
799
|
});
|
|
584
800
|
ctx.on("approval/request", (request, next) => {
|
|
585
801
|
const record = ownedRecord(request.agent);
|
|
@@ -599,9 +815,16 @@ function apply(ctx, rawConfig) {
|
|
|
599
815
|
});
|
|
600
816
|
});
|
|
601
817
|
const setPermissionMode = (record, modeId) => {
|
|
602
|
-
|
|
818
|
+
if (modeId === record.permissionMode)
|
|
819
|
+
return;
|
|
820
|
+
assertAllowed(modeId, new Set(ctx.permissionPresets.names), "permission mode");
|
|
603
821
|
ctx.permissionPresets.set(record.agent.session, modeId);
|
|
604
|
-
|
|
822
|
+
const permissionMode = ctx.permissionPresets.current(record.agent.session.events);
|
|
823
|
+
if (permissionMode !== modeId) {
|
|
824
|
+
throw internalError(`permission preset ${JSON.stringify(modeId)} did not become the effective mode`);
|
|
825
|
+
}
|
|
826
|
+
record.permissionMode = permissionMode;
|
|
827
|
+
record.permissionOptions = permissionState(ctx, permissionMode);
|
|
605
828
|
};
|
|
606
829
|
const setConfigOption = (record, params) => {
|
|
607
830
|
const value = requireSelectValue(params);
|
|
@@ -625,13 +848,35 @@ function apply(ctx, rawConfig) {
|
|
|
625
848
|
throw invalidParams(`failed to select agent preset ${JSON.stringify(value)}: ${errorChain(error)}`);
|
|
626
849
|
});
|
|
627
850
|
} else if (params.configId === MODEL_CONFIG_ID) {
|
|
628
|
-
|
|
629
|
-
|
|
851
|
+
return resolveHarnessModel(llm, record.selection.current.provider, value).then((model) => {
|
|
852
|
+
const index = record.models.findIndex((candidate) => candidate.id === value);
|
|
853
|
+
if (index === -1)
|
|
854
|
+
record.models.push(model);
|
|
855
|
+
else
|
|
856
|
+
record.models[index] = model;
|
|
857
|
+
const reasoningEffort = resolveReasoningEffort(model, record.selection.current.reasoningEffort && model.reasoning?.efforts.some((effort) => effort.id === record.selection.current.reasoningEffort) ? record.selection.current.reasoningEffort : void 0);
|
|
858
|
+
record.selection.current = {
|
|
859
|
+
provider: record.selection.current.provider,
|
|
860
|
+
model: value,
|
|
861
|
+
...reasoningEffort ? { reasoningEffort } : {}
|
|
862
|
+
};
|
|
863
|
+
return { configOptions: configOptions(record) };
|
|
864
|
+
}).catch((error) => {
|
|
865
|
+
if (error instanceof RequestError)
|
|
866
|
+
throw error;
|
|
867
|
+
throw invalidParams(`failed to resolve model ${JSON.stringify(value)}: ${errorChain(error)}`);
|
|
868
|
+
});
|
|
630
869
|
} else if (params.configId === REASONING_EFFORT_CONFIG_ID) {
|
|
631
|
-
|
|
870
|
+
const model = record.models.find((candidate) => candidate.id === record.selection.current.model);
|
|
871
|
+
if (!model)
|
|
872
|
+
throw internalError("selected model metadata is unavailable");
|
|
873
|
+
const reasoningEffort = resolveReasoningEffort(model, value);
|
|
874
|
+
if (!reasoningEffort) {
|
|
875
|
+
throw invalidParams(`model ${JSON.stringify(model.id)} has no reasoning selector`);
|
|
876
|
+
}
|
|
632
877
|
record.selection.current = {
|
|
633
878
|
...record.selection.current,
|
|
634
|
-
reasoningEffort
|
|
879
|
+
reasoningEffort
|
|
635
880
|
};
|
|
636
881
|
} else {
|
|
637
882
|
throw invalidParams(`unknown config option: ${params.configId}`);
|
|
@@ -642,13 +887,14 @@ function apply(ctx, rawConfig) {
|
|
|
642
887
|
conn = connection;
|
|
643
888
|
return {
|
|
644
889
|
async initialize(_params) {
|
|
645
|
-
const models = await
|
|
890
|
+
const { models } = await loadModelCatalog();
|
|
891
|
+
imagePromptEnabled = supportsAcpImagePrompts(attachments, models);
|
|
646
892
|
return {
|
|
647
893
|
protocolVersion: PROTOCOL_VERSION,
|
|
648
894
|
agentInfo: { name: "acp-extension-dsh", version: ACP_EXTENSION_DSH_VERSION },
|
|
649
895
|
agentCapabilities: {
|
|
650
896
|
promptCapabilities: {
|
|
651
|
-
image:
|
|
897
|
+
image: imagePromptEnabled,
|
|
652
898
|
audio: false,
|
|
653
899
|
embeddedContext: false
|
|
654
900
|
},
|
|
@@ -665,18 +911,23 @@ function apply(ctx, rawConfig) {
|
|
|
665
911
|
async newSession(params) {
|
|
666
912
|
assertOpen();
|
|
667
913
|
validateSessionParams(params);
|
|
668
|
-
let
|
|
914
|
+
let catalog;
|
|
669
915
|
try {
|
|
670
|
-
|
|
916
|
+
catalog = await loadModelCatalog();
|
|
671
917
|
} catch (error) {
|
|
672
|
-
throw internalError(`failed to
|
|
918
|
+
throw internalError(`failed to discover models: ${errorChain(error)}`);
|
|
673
919
|
}
|
|
920
|
+
const { models, initialModel: initialModelId } = catalog;
|
|
674
921
|
const sessionId = randomUUID();
|
|
922
|
+
const initialModel = models.find((model) => model.id === initialModelId);
|
|
923
|
+
if (!initialModel)
|
|
924
|
+
throw internalError("initial model metadata is unavailable");
|
|
925
|
+
const reasoningEffort = resolveReasoningEffort(initialModel, config.reasoningEffort);
|
|
675
926
|
const selection = {
|
|
676
927
|
current: {
|
|
677
928
|
provider: config.provider,
|
|
678
|
-
model:
|
|
679
|
-
reasoningEffort:
|
|
929
|
+
model: initialModelId,
|
|
930
|
+
...reasoningEffort ? { reasoningEffort } : {}
|
|
680
931
|
}
|
|
681
932
|
};
|
|
682
933
|
const agentPresetOptions = (await ctx.agentPresets.list()).filter((preset) => preset.broken === void 0);
|
|
@@ -691,7 +942,7 @@ function apply(ctx, rawConfig) {
|
|
|
691
942
|
handle = await ctx.agents.create({
|
|
692
943
|
sessionId,
|
|
693
944
|
meta: { cwd: params.cwd, agentPreset: requestedPreset },
|
|
694
|
-
agentOptions: { provider: config.provider, model:
|
|
945
|
+
agentOptions: { provider: config.provider, model: initialModelId },
|
|
695
946
|
setup: async (agentContext) => {
|
|
696
947
|
installModelSelection(agentContext, selection);
|
|
697
948
|
mountedPreset = (await ctx.agentPresets.mount(agentContext, requestedPreset)).id;
|
|
@@ -716,9 +967,10 @@ function apply(ctx, rawConfig) {
|
|
|
716
967
|
throw internalError("connection closed during session/new");
|
|
717
968
|
}
|
|
718
969
|
let permissionMode;
|
|
970
|
+
let permissionOptions;
|
|
719
971
|
try {
|
|
720
972
|
permissionMode = ctx.permissionPresets.current(handle.agent.session.events);
|
|
721
|
-
|
|
973
|
+
permissionOptions = permissionState(ctx, permissionMode);
|
|
722
974
|
} catch (error) {
|
|
723
975
|
await dispose();
|
|
724
976
|
throw error;
|
|
@@ -728,24 +980,19 @@ function apply(ctx, rawConfig) {
|
|
|
728
980
|
dispose,
|
|
729
981
|
selection,
|
|
730
982
|
permissionMode,
|
|
983
|
+
permissionOptions,
|
|
731
984
|
agentPreset: mountedPreset,
|
|
732
985
|
agentPresetOptions,
|
|
733
986
|
models,
|
|
734
|
-
started: false
|
|
987
|
+
started: false,
|
|
988
|
+
outputTail: Promise.resolve()
|
|
735
989
|
};
|
|
736
990
|
sessions.set(sessionId, record);
|
|
737
991
|
return {
|
|
738
992
|
sessionId,
|
|
739
993
|
modes: modeState(record),
|
|
740
994
|
configOptions: configOptions(record),
|
|
741
|
-
models:
|
|
742
|
-
currentModelId: record.selection.current.model,
|
|
743
|
-
availableModels: record.models.map((model) => ({
|
|
744
|
-
modelId: model.id,
|
|
745
|
-
name: model.name ?? model.id,
|
|
746
|
-
description: model.description ?? null
|
|
747
|
-
}))
|
|
748
|
-
}
|
|
995
|
+
models: legacyModels(record)
|
|
749
996
|
};
|
|
750
997
|
},
|
|
751
998
|
setSessionMode(params) {
|
|
@@ -763,60 +1010,92 @@ function apply(ctx, rawConfig) {
|
|
|
763
1010
|
if (ctx.agents.get(record.agent.id) !== record.agent) {
|
|
764
1011
|
throw internalError("prompt was not queued: the agent was disposed outside the bridge");
|
|
765
1012
|
}
|
|
766
|
-
const attachments = ctx.get("attachments");
|
|
767
1013
|
const messageId = randomUUID();
|
|
768
1014
|
let resolvePrompt;
|
|
769
1015
|
let rejectPrompt;
|
|
1016
|
+
let finishAdmission;
|
|
770
1017
|
const completion = new Promise((resolve, reject) => {
|
|
771
1018
|
resolvePrompt = resolve;
|
|
772
1019
|
rejectPrompt = reject;
|
|
773
1020
|
});
|
|
1021
|
+
const admissionDone = new Promise((resolve) => {
|
|
1022
|
+
finishAdmission = resolve;
|
|
1023
|
+
});
|
|
1024
|
+
const admissionController = new AbortController();
|
|
774
1025
|
const inflight = {
|
|
775
1026
|
resolve: resolvePrompt,
|
|
776
1027
|
reject: rejectPrompt,
|
|
777
|
-
|
|
1028
|
+
messageQueued: false,
|
|
1029
|
+
admissionDone,
|
|
1030
|
+
admissionController,
|
|
1031
|
+
cancelRequested: false,
|
|
1032
|
+
settlementStarted: false
|
|
778
1033
|
};
|
|
779
1034
|
record.inflight = inflight;
|
|
1035
|
+
let admissionError;
|
|
780
1036
|
try {
|
|
781
|
-
const content = await admitAcpPrompt(params.prompt,
|
|
782
|
-
if (
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1037
|
+
const content = await admitAcpPrompt(params.prompt, llm, record.selection.current, attachments, imagePromptEnabled, admissionController.signal);
|
|
1038
|
+
if (!inflight.cancelRequested) {
|
|
1039
|
+
if (ctx.agents.get(record.agent.id) !== record.agent) {
|
|
1040
|
+
throw internalError("prompt was not queued: the agent was disposed outside the bridge");
|
|
1041
|
+
}
|
|
1042
|
+
const message = createUserMessage(messageId, content);
|
|
1043
|
+
inflight.messageId = messageId;
|
|
1044
|
+
inflight.messageQueued = true;
|
|
1045
|
+
const wasStarted = record.started;
|
|
1046
|
+
try {
|
|
1047
|
+
record.agent.followup(message);
|
|
1048
|
+
record.started = true;
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
inflight.messageQueued = false;
|
|
1051
|
+
record.started = wasStarted;
|
|
1052
|
+
throw new Error(`prompt was not queued: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1053
|
+
}
|
|
792
1054
|
}
|
|
793
|
-
void record.agent.whenIdle().then(() => {
|
|
794
|
-
if (record.inflight !== inflight)
|
|
795
|
-
return;
|
|
796
|
-
record.inflight = void 0;
|
|
797
|
-
const end = inflight.endReason;
|
|
798
|
-
inflight.resolve(end ? end.kind === "max-tokens" ? "end_turn" : turnEndToStopReason(end) : "cancelled");
|
|
799
|
-
});
|
|
800
1055
|
} catch (error) {
|
|
1056
|
+
admissionError = error;
|
|
1057
|
+
} finally {
|
|
1058
|
+
finishAdmission();
|
|
1059
|
+
}
|
|
1060
|
+
if (inflight.cancelRequested) {
|
|
1061
|
+
settleAfterQuiescence(record, inflight);
|
|
1062
|
+
return { stopReason: await completion };
|
|
1063
|
+
}
|
|
1064
|
+
if (admissionError) {
|
|
801
1065
|
if (record.inflight === inflight)
|
|
802
1066
|
record.inflight = void 0;
|
|
803
|
-
|
|
1067
|
+
if (admissionError instanceof RequestError)
|
|
1068
|
+
throw admissionError;
|
|
1069
|
+
throw internalError(errorChain(admissionError));
|
|
804
1070
|
}
|
|
1071
|
+
settleAfterQuiescence(record, inflight);
|
|
805
1072
|
return { stopReason: await completion };
|
|
806
1073
|
},
|
|
807
1074
|
cancel(params) {
|
|
808
1075
|
const record = sessions.get(params.sessionId);
|
|
809
1076
|
if (!record)
|
|
810
1077
|
return Promise.resolve();
|
|
811
|
-
|
|
812
|
-
|
|
1078
|
+
const inflight = record.inflight;
|
|
1079
|
+
if (inflight) {
|
|
1080
|
+
inflight.cancelRequested = true;
|
|
1081
|
+
inflight.admissionController.abort(new Error("ACP prompt cancelled"));
|
|
1082
|
+
settleAfterQuiescence(record, inflight);
|
|
1083
|
+
}
|
|
1084
|
+
if (!inflight || inflight.messageQueued)
|
|
1085
|
+
record.agent.cancel({ kind: "user" });
|
|
813
1086
|
return Promise.resolve();
|
|
814
1087
|
},
|
|
815
1088
|
async closeSession(params) {
|
|
816
1089
|
const record = requireSession(params.sessionId);
|
|
817
1090
|
sessions.delete(params.sessionId);
|
|
818
|
-
|
|
819
|
-
|
|
1091
|
+
const inflight = record.inflight;
|
|
1092
|
+
if (inflight) {
|
|
1093
|
+
inflight.cancelRequested = true;
|
|
1094
|
+
inflight.admissionController.abort(new Error("ACP session closed"));
|
|
1095
|
+
settleAfterQuiescence(record, inflight);
|
|
1096
|
+
}
|
|
1097
|
+
if (!inflight || inflight.messageQueued)
|
|
1098
|
+
record.agent.cancel({ kind: "user" });
|
|
820
1099
|
await disposeRecords([record]);
|
|
821
1100
|
}
|
|
822
1101
|
};
|
|
@@ -831,8 +1110,14 @@ function apply(ctx, rawConfig) {
|
|
|
831
1110
|
const records = [...sessions.values()];
|
|
832
1111
|
sessions.clear();
|
|
833
1112
|
for (const record of records) {
|
|
834
|
-
|
|
835
|
-
|
|
1113
|
+
const inflight = record.inflight;
|
|
1114
|
+
if (inflight) {
|
|
1115
|
+
inflight.cancelRequested = true;
|
|
1116
|
+
inflight.admissionController.abort(new Error("ACP bridge disposed"));
|
|
1117
|
+
settleAfterQuiescence(record, inflight);
|
|
1118
|
+
}
|
|
1119
|
+
if (!inflight || inflight.messageQueued)
|
|
1120
|
+
record.agent.cancel({ kind: "user" });
|
|
836
1121
|
}
|
|
837
1122
|
quiescing = (async () => {
|
|
838
1123
|
await disposeRecords(records);
|