lody 0.102.0 → 0.104.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 +59 -1
- package/dist/baguette-worker.js +480 -0
- package/dist/chunks/{acp-Bsd2Lmd6.js → acp-C0LiROfB.js} +18 -4
- package/dist/chunks/{cloudflared-native-DbUToPHH.js → cloudflared-native-BWtpTE_5.js} +3 -3
- package/dist/chunks/{daemon-session-tools-BeXk1A1E.js → daemon-session-tools-C6AE_3JK.js} +12 -9
- package/dist/chunks/{directory-handle-CtdCjbNh.js → directory-handle-DgcESjFj.js} +28 -22
- package/dist/chunks/{index-C0n6fro1.js → index-Sv4ANSUY.js} +8894 -2561
- package/dist/chunks/ios-simulator-hTbW-tat.js +161 -0
- package/dist/chunks/{review-viewer-B6wZ9fN1.js → review-viewer-nQBrydLp.js} +13 -10
- package/dist/chunks/{schemas-WuyisFi9.js → schemas-D_XLKCXC.js} +8 -8
- package/dist/claude-acp.js +1 -1
- package/dist/cloudflared-worker.js +2 -2
- package/dist/codex-acp.js +1 -1
- package/dist/deepseek-acp.js +319 -162
- package/dist/devin-acp.js +1152 -0
- package/dist/file-index-scan-worker.js +2 -2
- package/dist/index.js +108936 -38214
- package/package.json +7 -5
- package/dist/chunks/index-DhSXbMsB.js +0 -742
package/dist/deepseek-acp.js
CHANGED
|
@@ -1,38 +1,82 @@
|
|
|
1
|
+
import { R as RequestError, j as DEEPSEEK_HARNESS_BASE_URL_ENV, l as DEEPSEEK_HARNESS_API_KEY_ENV, n as ndJsonStream, o as AgentSideConnection, a as DEEPSEEK_HARNESS_AGENT_PRESETS, p as ACP_EXTENSION_DSH_VERSION, P as PROTOCOL_VERSION, q as DEEPSEEK_HARNESS_DEFAULT_MODEL, r as DEEPSEEK_HARNESS_PROVIDER } from "./chunks/acp-C0LiROfB.js";
|
|
1
2
|
import { resolve, isAbsolute } from "node:path";
|
|
2
|
-
import { o as object, s as string,
|
|
3
|
+
import { o as object$1, s as string, c as boolean, b as array, l as literal, u as unknown, n as number, a as _enum, r as record } from "./chunks/schemas-D_XLKCXC.js";
|
|
3
4
|
import { S as SessionUsageAccumulator, L as LODY_EXTENSION_METHODS, e as supportsLodySubagentEvents, d as LODY_SUBAGENT_EVENT_METHOD } from "./chunks/subagent-events-BJ9JwZva.js";
|
|
4
5
|
import { randomUUID, createHash } from "node:crypto";
|
|
5
6
|
import { Readable, Writable } from "node:stream";
|
|
6
|
-
import { j as DEEPSEEK_HARNESS_BASE_URL_ENV, l as DEEPSEEK_HARNESS_API_KEY_ENV, n as ndJsonStream, o as AgentSideConnection, a as DEEPSEEK_HARNESS_AGENT_PRESETS, R as RequestError, p as ACP_EXTENSION_DSH_VERSION, P as PROTOCOL_VERSION, q as DEEPSEEK_HARNESS_DEFAULT_MODEL, r as DEEPSEEK_HARNESS_PROVIDER } from "./chunks/acp-Bsd2Lmd6.js";
|
|
7
7
|
import { c as createPlanModeConfigOption, L as LODY_PLAN_MODE_CONFIG_ID } from "./chunks/plan-mode-D0D5cnMs.js";
|
|
8
8
|
import { L as LodySubagentEmitter } from "./chunks/subagent-emitter-BsTuVPDw.js";
|
|
9
9
|
import "node:url";
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
function nativeTurnId(turn) {
|
|
11
|
+
return `dsh-turn:${turn}`;
|
|
12
|
+
}
|
|
13
|
+
function object(value) {
|
|
14
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
15
|
+
}
|
|
16
|
+
function forkTarget(meta2) {
|
|
17
|
+
const lody = object(object(meta2)?.lody);
|
|
18
|
+
if (!lody || !Object.hasOwn(lody, "forkAtTurn"))
|
|
19
|
+
return void 0;
|
|
20
|
+
const target = object(lody.forkAtTurn);
|
|
21
|
+
if (!target || target.version !== 1 || target.turnId !== void 0 && (typeof target.turnId !== "string" || !/^dsh-turn:(0|[1-9]\d*)$/.test(target.turnId) || !Number.isSafeInteger(Number(target.turnId.slice("dsh-turn:".length)))))
|
|
22
|
+
throw RequestError.invalidParams(void 0, "invalid _meta.lody.forkAtTurn");
|
|
23
|
+
return target?.turnId;
|
|
24
|
+
}
|
|
25
|
+
function forkSnapshot(source, target) {
|
|
26
|
+
const events = source.events;
|
|
27
|
+
const end = target === void 0 ? events.length - 1 : events.findIndex((event) => event.type === "turn/end" && typeof event.data.turn === "number" && nativeTurnId(event.data.turn) === target);
|
|
28
|
+
if (target !== void 0 && end === -1)
|
|
29
|
+
throw RequestError.invalidParams(void 0, "fork turn is unknown or has not ended");
|
|
30
|
+
const prefix = events.slice(0, end + 1);
|
|
31
|
+
const lastTurn = [...prefix].reverse().find((event) => event.type === "turn/start" || event.type === "turn/end");
|
|
32
|
+
if (lastTurn?.type === "turn/start")
|
|
33
|
+
throw RequestError.invalidParams(void 0, "cannot fork an unfinished turn; select an earlier completed turn");
|
|
34
|
+
let agentPreset = source.header.agentPreset;
|
|
35
|
+
let selection;
|
|
36
|
+
for (const [index, event] of prefix.entries()) {
|
|
37
|
+
if (event.seq !== index)
|
|
38
|
+
throw RequestError.invalidParams(void 0, "source session has a non-contiguous log");
|
|
39
|
+
if (event.type === "agent-preset/selected" && typeof event.data.agentPreset === "string")
|
|
40
|
+
agentPreset = event.data.agentPreset;
|
|
41
|
+
if (event.type === "request/header") {
|
|
42
|
+
const config = object(object(event.data.header)?.config);
|
|
43
|
+
if (!config || typeof config.provider !== "string" || typeof config.model !== "string")
|
|
44
|
+
throw RequestError.invalidParams(void 0, "source request header has no model route");
|
|
45
|
+
selection = {
|
|
46
|
+
provider: config.provider,
|
|
47
|
+
model: config.model,
|
|
48
|
+
...typeof config.reasoningEffort === "string" ? { reasoningEffort: config.reasoningEffort } : {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { sourceId: source.header.id, seed: structuredClone(prefix), agentPreset, selection };
|
|
53
|
+
}
|
|
54
|
+
const blockSchema = object$1({ type: string() }).passthrough();
|
|
55
|
+
const resultSchema = object$1({
|
|
12
56
|
type: literal("tool-result"),
|
|
13
57
|
toolCallId: string().min(1),
|
|
14
58
|
content: array(blockSchema),
|
|
15
59
|
isError: boolean().optional()
|
|
16
60
|
});
|
|
17
|
-
const callSchema = object({
|
|
61
|
+
const callSchema = object$1({
|
|
18
62
|
callId: string().min(1),
|
|
19
63
|
name: string().min(1),
|
|
20
64
|
arguments: string()
|
|
21
65
|
});
|
|
22
|
-
const ptcSchema = object({
|
|
66
|
+
const ptcSchema = object$1({
|
|
23
67
|
subCallId: string().min(1),
|
|
24
68
|
name: string().min(1),
|
|
25
69
|
arguments: unknown(),
|
|
26
70
|
parentCallId: string(),
|
|
27
71
|
rootCallId: string()
|
|
28
72
|
});
|
|
29
|
-
const locationSchema = object({ path: string(), line: number().int().positive().optional() });
|
|
30
|
-
const viewSchema = object({
|
|
73
|
+
const locationSchema = object$1({ path: string(), line: number().int().positive().optional() });
|
|
74
|
+
const viewSchema = object$1({
|
|
31
75
|
card: _enum(["generic", "terminal", "diff", "read", "search", "web"]),
|
|
32
76
|
title: string().optional(),
|
|
33
77
|
kind: _enum(["read", "edit", "delete", "move", "search", "execute", "fetch", "other"]).optional(),
|
|
34
78
|
locations: array(locationSchema).optional(),
|
|
35
|
-
diffs: array(object({ path: string(), oldText: string().nullable(), newText: string() })).optional(),
|
|
79
|
+
diffs: array(object$1({ path: string(), oldText: string().nullable(), newText: string() })).optional(),
|
|
36
80
|
output: string().optional(),
|
|
37
81
|
content: array(blockSchema).optional()
|
|
38
82
|
}).passthrough();
|
|
@@ -163,8 +207,8 @@ class ToolCallBridge {
|
|
|
163
207
|
}
|
|
164
208
|
await this.start(call.callId, call.name, args);
|
|
165
209
|
} else if (type === "tool/result") {
|
|
166
|
-
const event = object({
|
|
167
|
-
message: object({ content: array(resultSchema) }),
|
|
210
|
+
const event = object$1({
|
|
211
|
+
message: object$1({ content: array(resultSchema) }),
|
|
168
212
|
meta: unknown().optional()
|
|
169
213
|
}).passthrough().parse(data);
|
|
170
214
|
for (const block of event.message.content) {
|
|
@@ -174,7 +218,7 @@ class ToolCallBridge {
|
|
|
174
218
|
const call = ptcSchema.parse(data);
|
|
175
219
|
await this.start(call.subCallId, call.name, call.arguments);
|
|
176
220
|
if (type === "tool/ptc-dispatch") {
|
|
177
|
-
const result = object({ content: array(blockSchema), isError: boolean() }).passthrough().parse(data);
|
|
221
|
+
const result = object$1({ content: array(blockSchema), isError: boolean() }).passthrough().parse(data);
|
|
178
222
|
await this.finish(call.subCallId, result, result);
|
|
179
223
|
}
|
|
180
224
|
} else if (type === "turn/end") {
|
|
@@ -234,17 +278,17 @@ class HarnessUsageTracker {
|
|
|
234
278
|
return this.accumulator.update(sessionId, String(seq), { [this.route.model]: usage });
|
|
235
279
|
}
|
|
236
280
|
}
|
|
237
|
-
const questionSchema = object({
|
|
281
|
+
const questionSchema = object$1({
|
|
238
282
|
id: string().refine((value) => value.trim().length > 0),
|
|
239
283
|
question: string().min(1),
|
|
240
284
|
header: string().optional(),
|
|
241
285
|
detail: string().optional(),
|
|
242
|
-
options: array(object({
|
|
286
|
+
options: array(object$1({
|
|
243
287
|
label: string().min(1),
|
|
244
288
|
description: string().optional()
|
|
245
289
|
})).optional(),
|
|
246
290
|
multiSelect: boolean().optional(),
|
|
247
|
-
intent: object({ kind: literal("plan-review"), approve: string() }).optional()
|
|
291
|
+
intent: object$1({ kind: literal("plan-review"), approve: string() }).optional()
|
|
248
292
|
});
|
|
249
293
|
function failure(code, message) {
|
|
250
294
|
return Object.assign(new Error(message), { name: "UserQuestionError", code });
|
|
@@ -364,7 +408,7 @@ class UserQuestionBridge {
|
|
|
364
408
|
throw failure("ASK_ABORTED", "The user cancelled the question");
|
|
365
409
|
if (response.action === "decline")
|
|
366
410
|
throw failure("ASK_DECLINED", "The user declined the question");
|
|
367
|
-
const accepted = object({
|
|
411
|
+
const accepted = object$1({
|
|
368
412
|
action: literal("accept"),
|
|
369
413
|
content: record(string(), unknown())
|
|
370
414
|
}).safeParse(response);
|
|
@@ -409,11 +453,13 @@ const inject = [
|
|
|
409
453
|
"llm",
|
|
410
454
|
"permissionPresets",
|
|
411
455
|
"sessionPersistence",
|
|
456
|
+
"sessions",
|
|
412
457
|
"sessionQuery",
|
|
413
458
|
"sessionTitle",
|
|
414
459
|
"userQuestions"
|
|
415
460
|
];
|
|
416
461
|
const LODY_CAPABILITIES = {
|
|
462
|
+
forkAtTurn: { version: 1 },
|
|
417
463
|
subagentEvents: { version: 1 },
|
|
418
464
|
sessionTitle: { version: 1 },
|
|
419
465
|
compaction: { version: 1 },
|
|
@@ -432,6 +478,7 @@ const MODEL_DISCOVERY_TIMEOUT_MS = 15e3;
|
|
|
432
478
|
const MODEL_DISCOVERY_MAX_BYTES = 1024 * 1024;
|
|
433
479
|
const MODEL_DISCOVERY_MAX_MODELS = 1e3;
|
|
434
480
|
const MODEL_ID_MAX_LENGTH = 512;
|
|
481
|
+
const ACP_MODEL_ID_PREFIX = "dsh-route:";
|
|
435
482
|
const INVALID_MCP_SERVER_NAME_CHARS = /[^A-Za-z0-9_-]/gu;
|
|
436
483
|
const IMAGE_MEDIA_TYPES = [
|
|
437
484
|
"image/png",
|
|
@@ -470,33 +517,60 @@ function resolveAdapterConfig(config) {
|
|
|
470
517
|
...config?.stream ? { stream: config.stream } : {}
|
|
471
518
|
};
|
|
472
519
|
}
|
|
473
|
-
async function loadHarnessModels(llm,
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
520
|
+
async function loadHarnessModels(llm, defaultProvider, configuredModel, endpoint) {
|
|
521
|
+
const providers = llm.listProviders?.() ?? [{ id: defaultProvider, name: defaultProvider }];
|
|
522
|
+
if (!providers.some(({ id }) => id === defaultProvider)) {
|
|
523
|
+
throw new Error(`configured provider ${JSON.stringify(defaultProvider)} is unavailable`);
|
|
524
|
+
}
|
|
525
|
+
const identities = [];
|
|
526
|
+
for (const { id: provider } of providers) {
|
|
527
|
+
const listed = endpoint && provider === defaultProvider ? (await discoverDeepSeekModelIds(endpoint.baseUrl, endpoint.apiKey)).map((id) => ({
|
|
528
|
+
provider,
|
|
529
|
+
id
|
|
530
|
+
})) : await llm.listModels(provider);
|
|
531
|
+
const ids = /* @__PURE__ */ new Set();
|
|
532
|
+
for (const model of listed) {
|
|
533
|
+
if (model.provider !== provider || !model.id.trim())
|
|
534
|
+
continue;
|
|
535
|
+
if (ids.has(model.id)) {
|
|
536
|
+
throw new Error(`acp-extension-dsh: duplicate model ${JSON.stringify(model.id)} for provider ${JSON.stringify(provider)}`);
|
|
537
|
+
}
|
|
538
|
+
ids.add(model.id);
|
|
539
|
+
identities.push({ provider, model: model.id });
|
|
540
|
+
}
|
|
541
|
+
if (endpoint && provider === defaultProvider && ids.size === 0) {
|
|
542
|
+
throw new Error("the endpoint returned no usable models");
|
|
543
|
+
}
|
|
544
|
+
if (provider === defaultProvider && !endpoint && !ids.has(configuredModel)) {
|
|
545
|
+
identities.unshift({ provider, model: configuredModel });
|
|
485
546
|
}
|
|
486
|
-
ids.add(model.id);
|
|
487
|
-
modelIds.push(model.id);
|
|
488
547
|
}
|
|
489
|
-
|
|
548
|
+
const initialSelection = endpoint ? identities.find(({ provider }) => provider === defaultProvider) : { provider: defaultProvider, model: configuredModel };
|
|
549
|
+
if (!initialSelection)
|
|
490
550
|
throw new Error("the endpoint returned no usable models");
|
|
491
|
-
}
|
|
492
|
-
if (!endpoint && !ids.has(configuredModel))
|
|
493
|
-
modelIds.unshift(configuredModel);
|
|
494
|
-
const initialModel = endpoint ? modelIds[0] : configuredModel;
|
|
495
551
|
return {
|
|
496
|
-
models: await Promise.all(
|
|
497
|
-
|
|
552
|
+
models: await Promise.all(identities.map(({ provider, model }) => resolveHarnessModel(llm, provider, model))),
|
|
553
|
+
initialSelection
|
|
498
554
|
};
|
|
499
555
|
}
|
|
556
|
+
function encodeAcpModelId(provider, model) {
|
|
557
|
+
return `${ACP_MODEL_ID_PREFIX}${Buffer.from(JSON.stringify([provider, model])).toString("base64url")}`;
|
|
558
|
+
}
|
|
559
|
+
function decodeAcpModelId(value, legacyProvider) {
|
|
560
|
+
if (!value.startsWith(ACP_MODEL_ID_PREFIX))
|
|
561
|
+
return { provider: legacyProvider, model: value };
|
|
562
|
+
try {
|
|
563
|
+
const decoded = JSON.parse(Buffer.from(value.slice(ACP_MODEL_ID_PREFIX.length), "base64url").toString("utf8"));
|
|
564
|
+
if (Array.isArray(decoded) && decoded.length === 2 && decoded.every((part) => typeof part === "string" && part.trim())) {
|
|
565
|
+
return { provider: decoded[0], model: decoded[1] };
|
|
566
|
+
}
|
|
567
|
+
} catch {
|
|
568
|
+
}
|
|
569
|
+
throw invalidParams("model route id is invalid");
|
|
570
|
+
}
|
|
571
|
+
function selectedModel(record2) {
|
|
572
|
+
return record2.models.find((candidate) => candidate.provider === record2.selection.current.provider && candidate.id === record2.selection.current.model);
|
|
573
|
+
}
|
|
500
574
|
function modelDiscoveryUrl(baseUrl) {
|
|
501
575
|
const url = new URL(baseUrl);
|
|
502
576
|
url.pathname = `${url.pathname.replace(/\/+$/u, "")}/models`;
|
|
@@ -727,13 +801,13 @@ function configOptions(record2) {
|
|
|
727
801
|
{
|
|
728
802
|
id: MODEL_CONFIG_ID,
|
|
729
803
|
name: "Model",
|
|
730
|
-
description: "
|
|
804
|
+
description: "Provider route and model used for the session",
|
|
731
805
|
category: "model",
|
|
732
806
|
type: "select",
|
|
733
|
-
currentValue: record2.selection.current.model,
|
|
807
|
+
currentValue: encodeAcpModelId(record2.selection.current.provider, record2.selection.current.model),
|
|
734
808
|
options: record2.models.map((model2) => ({
|
|
735
|
-
value: model2.id,
|
|
736
|
-
name: model2.name
|
|
809
|
+
value: encodeAcpModelId(model2.provider, model2.id),
|
|
810
|
+
name: `${model2.name} (${model2.provider})`,
|
|
737
811
|
description: model2.description ?? null
|
|
738
812
|
}))
|
|
739
813
|
}
|
|
@@ -743,7 +817,7 @@ function configOptions(record2) {
|
|
|
743
817
|
const state = plan.get(record2.agent);
|
|
744
818
|
options.push(createPlanModeConfigOption(state.pending ?? state.active));
|
|
745
819
|
}
|
|
746
|
-
const model =
|
|
820
|
+
const model = selectedModel(record2);
|
|
747
821
|
if (model?.reasoning && record2.selection.current.reasoningEffort) {
|
|
748
822
|
options.push({
|
|
749
823
|
id: REASONING_EFFORT_CONFIG_ID,
|
|
@@ -763,16 +837,16 @@ function configOptions(record2) {
|
|
|
763
837
|
}
|
|
764
838
|
function legacyModels(record2) {
|
|
765
839
|
return {
|
|
766
|
-
currentModelId: record2.selection.current.model,
|
|
840
|
+
currentModelId: encodeAcpModelId(record2.selection.current.provider, record2.selection.current.model),
|
|
767
841
|
availableModels: record2.models.flatMap((model) => [
|
|
768
842
|
{
|
|
769
|
-
modelId: model.id,
|
|
770
|
-
name: model.name
|
|
843
|
+
modelId: encodeAcpModelId(model.provider, model.id),
|
|
844
|
+
name: `${model.name} (${model.provider})`,
|
|
771
845
|
description: model.description ?? null
|
|
772
846
|
},
|
|
773
847
|
...model.reasoning?.efforts.map((effort) => ({
|
|
774
|
-
modelId: `${model.id}[${effort.id}]`,
|
|
775
|
-
name: `${model.name} (${effort.name})`,
|
|
848
|
+
modelId: `${encodeAcpModelId(model.provider, model.id)}[${effort.id}]`,
|
|
849
|
+
name: `${model.name} (${model.provider}, ${effort.name})`,
|
|
776
850
|
description: model.description ?? null
|
|
777
851
|
})) ?? []
|
|
778
852
|
])
|
|
@@ -1112,6 +1186,11 @@ function apply(ctx, rawConfig) {
|
|
|
1112
1186
|
});
|
|
1113
1187
|
});
|
|
1114
1188
|
});
|
|
1189
|
+
const flushSession = async (session) => {
|
|
1190
|
+
const store = ctx.get("sessions");
|
|
1191
|
+
if (!store || !await store.flush(session))
|
|
1192
|
+
throw new Error("no session durability listener is available");
|
|
1193
|
+
};
|
|
1115
1194
|
const settleAfterQuiescence = (record2, inflight) => {
|
|
1116
1195
|
if (inflight.settlementStarted)
|
|
1117
1196
|
return;
|
|
@@ -1122,6 +1201,7 @@ function apply(ctx, rawConfig) {
|
|
|
1122
1201
|
await record2.agent.whenIdle();
|
|
1123
1202
|
await record2.outputTail;
|
|
1124
1203
|
await record2.tools.interrupt();
|
|
1204
|
+
await flushSession(record2.agent.session);
|
|
1125
1205
|
}
|
|
1126
1206
|
if (record2.inflight !== inflight)
|
|
1127
1207
|
return;
|
|
@@ -1300,7 +1380,11 @@ function apply(ctx, rawConfig) {
|
|
|
1300
1380
|
continue;
|
|
1301
1381
|
await notify({
|
|
1302
1382
|
sessionId: record2.agent.session.id,
|
|
1303
|
-
update: {
|
|
1383
|
+
update: {
|
|
1384
|
+
sessionUpdate: "agent_message_chunk",
|
|
1385
|
+
content,
|
|
1386
|
+
...typeof event.data.turn === "number" ? { _meta: { lody: { turnId: nativeTurnId(event.data.turn) } } } : {}
|
|
1387
|
+
}
|
|
1304
1388
|
});
|
|
1305
1389
|
}
|
|
1306
1390
|
}, inflight);
|
|
@@ -1347,9 +1431,19 @@ function apply(ctx, rawConfig) {
|
|
|
1347
1431
|
}
|
|
1348
1432
|
});
|
|
1349
1433
|
ctx.on("agent/inbox/claimed", ({ agent, message, turn }) => {
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1434
|
+
const record2 = ownedRecord(agent);
|
|
1435
|
+
const inflight = record2?.inflight;
|
|
1436
|
+
if (record2 && inflight && inflight.messageId === message.id) {
|
|
1352
1437
|
inflight.turn = turn;
|
|
1438
|
+
enqueueNotification(record2, {
|
|
1439
|
+
sessionId: agent.session.id,
|
|
1440
|
+
update: {
|
|
1441
|
+
sessionUpdate: "agent_message_chunk",
|
|
1442
|
+
content: { type: "text", text: "" },
|
|
1443
|
+
_meta: { lody: { turnId: nativeTurnId(turn) } }
|
|
1444
|
+
}
|
|
1445
|
+
}, inflight);
|
|
1446
|
+
}
|
|
1353
1447
|
});
|
|
1354
1448
|
ctx.on("agent/error", ({ agent, turn, error }) => {
|
|
1355
1449
|
const record2 = ownedRecord(agent);
|
|
@@ -1440,26 +1534,27 @@ function apply(ctx, rawConfig) {
|
|
|
1440
1534
|
throw invalidParams(`failed to select agent preset ${JSON.stringify(value)}: ${errorChain(error)}`);
|
|
1441
1535
|
});
|
|
1442
1536
|
} else if (params.configId === MODEL_CONFIG_ID) {
|
|
1443
|
-
|
|
1444
|
-
|
|
1537
|
+
const selected = decodeAcpModelId(value, record2.selection.current.provider);
|
|
1538
|
+
return resolveHarnessModel(llm, selected.provider, selected.model).then((model) => {
|
|
1539
|
+
const index = record2.models.findIndex((candidate) => candidate.provider === selected.provider && candidate.id === selected.model);
|
|
1445
1540
|
if (index === -1)
|
|
1446
1541
|
record2.models.push(model);
|
|
1447
1542
|
else
|
|
1448
1543
|
record2.models[index] = model;
|
|
1449
1544
|
const reasoningEffort = resolveReasoningEffort(model, record2.selection.current.reasoningEffort && model.reasoning?.efforts.some((effort) => effort.id === record2.selection.current.reasoningEffort) ? record2.selection.current.reasoningEffort : void 0);
|
|
1450
1545
|
record2.selection.current = {
|
|
1451
|
-
provider:
|
|
1452
|
-
model:
|
|
1546
|
+
provider: selected.provider,
|
|
1547
|
+
model: selected.model,
|
|
1453
1548
|
...reasoningEffort ? { reasoningEffort } : {}
|
|
1454
1549
|
};
|
|
1455
1550
|
return { configOptions: configOptions(record2) };
|
|
1456
1551
|
}).catch((error) => {
|
|
1457
1552
|
if (error instanceof RequestError)
|
|
1458
1553
|
throw error;
|
|
1459
|
-
throw invalidParams(`failed to resolve model ${JSON.stringify(
|
|
1554
|
+
throw invalidParams(`failed to resolve model route ${JSON.stringify(selected.provider)} / ${JSON.stringify(selected.model)}: ${errorChain(error)}`);
|
|
1460
1555
|
});
|
|
1461
1556
|
} else if (params.configId === REASONING_EFFORT_CONFIG_ID) {
|
|
1462
|
-
const model =
|
|
1557
|
+
const model = selectedModel(record2);
|
|
1463
1558
|
if (!model)
|
|
1464
1559
|
throw internalError("selected model metadata is unavailable");
|
|
1465
1560
|
const reasoningEffort = resolveReasoningEffort(model, value);
|
|
@@ -1475,6 +1570,150 @@ function apply(ctx, rawConfig) {
|
|
|
1475
1570
|
}
|
|
1476
1571
|
return { configOptions: configOptions(record2) };
|
|
1477
1572
|
};
|
|
1573
|
+
const createSession = async (params, fork) => {
|
|
1574
|
+
assertOpen();
|
|
1575
|
+
validateSessionParams(params);
|
|
1576
|
+
let catalog;
|
|
1577
|
+
try {
|
|
1578
|
+
catalog = await loadModelCatalog();
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
throw internalError(`failed to discover models: ${errorChain(error)}`);
|
|
1581
|
+
}
|
|
1582
|
+
const models = [...catalog.models];
|
|
1583
|
+
const initialSelection = fork?.selection ?? catalog.initialSelection;
|
|
1584
|
+
if (fork?.selection) {
|
|
1585
|
+
const model = await resolveHarnessModel(llm, initialSelection.provider, initialSelection.model);
|
|
1586
|
+
const index = models.findIndex((candidate) => candidate.provider === model.provider && candidate.id === model.id);
|
|
1587
|
+
if (index < 0)
|
|
1588
|
+
models.push(model);
|
|
1589
|
+
else
|
|
1590
|
+
models[index] = model;
|
|
1591
|
+
}
|
|
1592
|
+
const sessionId = randomUUID();
|
|
1593
|
+
const questions = new UserQuestionBridge(sessionId, (request) => conn.unstable_createElicitation(request), () => questionCapabilities);
|
|
1594
|
+
const initialModel = models.find((model) => model.provider === initialSelection.provider && model.id === initialSelection.model);
|
|
1595
|
+
if (!initialModel)
|
|
1596
|
+
throw internalError("initial model metadata is unavailable");
|
|
1597
|
+
const reasoningEffort = resolveReasoningEffort(initialModel, fork?.selection ? fork.selection.reasoningEffort : config.reasoningEffort);
|
|
1598
|
+
const selection = {
|
|
1599
|
+
current: {
|
|
1600
|
+
provider: initialSelection.provider,
|
|
1601
|
+
model: initialSelection.model,
|
|
1602
|
+
...reasoningEffort ? { reasoningEffort } : {}
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
const agentPresetOptions = (await ctx.agentPresets.list()).filter((preset) => preset.broken === void 0);
|
|
1606
|
+
const requestedPreset = fork?.agentPreset ?? ctx.agentPresets.defaultId;
|
|
1607
|
+
if (!agentPresetOptions.some((preset) => preset.id === requestedPreset)) {
|
|
1608
|
+
throw internalError(`default agent preset ${JSON.stringify(requestedPreset)} is unavailable`);
|
|
1609
|
+
}
|
|
1610
|
+
const mcpServerNames = reserveMcpServerNames(params.mcpServers, sessionId, activeMcpServerNames);
|
|
1611
|
+
let mountedPreset = requestedPreset;
|
|
1612
|
+
let handle;
|
|
1613
|
+
try {
|
|
1614
|
+
handle = await ctx.agents.create({
|
|
1615
|
+
sessionId,
|
|
1616
|
+
meta: {
|
|
1617
|
+
cwd: params.cwd,
|
|
1618
|
+
agentPreset: requestedPreset,
|
|
1619
|
+
...fork ? { parentSession: fork.sourceId, isSeeded: true } : {}
|
|
1620
|
+
},
|
|
1621
|
+
...fork ? { seed: fork.seed, inheritedEventCount: fork.seed.length } : {},
|
|
1622
|
+
agentOptions: {
|
|
1623
|
+
provider: initialSelection.provider,
|
|
1624
|
+
model: initialSelection.model
|
|
1625
|
+
},
|
|
1626
|
+
setup: async (agentContext) => {
|
|
1627
|
+
if (subagentEvents && !carrierKeyOf) {
|
|
1628
|
+
const scope = await agentContext.loader.import("@deepseek-ai/dsh-scope");
|
|
1629
|
+
if (typeof scope.carrierKeyOf !== "function")
|
|
1630
|
+
throw new Error("Harness scope identity API is unavailable");
|
|
1631
|
+
carrierKeyOf = scope.carrierKeyOf;
|
|
1632
|
+
}
|
|
1633
|
+
installModelSelection(agentContext, selection);
|
|
1634
|
+
agentContext.on("user-questions/request", (request, next) => {
|
|
1635
|
+
if (!request.agent || ownedRecord(request.agent)?.questions !== questions)
|
|
1636
|
+
return next();
|
|
1637
|
+
return questions.ask(request);
|
|
1638
|
+
});
|
|
1639
|
+
mountedPreset = (await ctx.agentPresets.mount(agentContext, requestedPreset)).id;
|
|
1640
|
+
await mountMcpServers(agentContext, params.mcpServers, mcpServerNames.names, params.cwd);
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
} catch (error) {
|
|
1644
|
+
mcpServerNames.release();
|
|
1645
|
+
if (error instanceof RequestError)
|
|
1646
|
+
throw error;
|
|
1647
|
+
throw internalError(`failed to create session: ${errorChain(error)}`);
|
|
1648
|
+
}
|
|
1649
|
+
const dispose = async () => {
|
|
1650
|
+
questions.cancel();
|
|
1651
|
+
try {
|
|
1652
|
+
await handle.dispose();
|
|
1653
|
+
} finally {
|
|
1654
|
+
mcpServerNames.release();
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
if (closed) {
|
|
1658
|
+
await dispose();
|
|
1659
|
+
throw internalError("connection closed during session/new");
|
|
1660
|
+
}
|
|
1661
|
+
let permissionMode;
|
|
1662
|
+
let permissionOptions;
|
|
1663
|
+
try {
|
|
1664
|
+
permissionMode = ctx.permissionPresets.current(handle.agent.session);
|
|
1665
|
+
permissionOptions = permissionState(ctx, permissionMode);
|
|
1666
|
+
} catch (error) {
|
|
1667
|
+
await dispose();
|
|
1668
|
+
throw error;
|
|
1669
|
+
}
|
|
1670
|
+
const record2 = {
|
|
1671
|
+
cwd: params.cwd,
|
|
1672
|
+
tools: new ToolCallBridge({
|
|
1673
|
+
cwd: params.cwd,
|
|
1674
|
+
lookup: (name2) => {
|
|
1675
|
+
const tools = handle.agent.ctx.get("tools");
|
|
1676
|
+
return tools?.get(name2, handle.agent);
|
|
1677
|
+
},
|
|
1678
|
+
content: (block) => assistantBlockToAcp(block, attachments),
|
|
1679
|
+
emit: (update) => notify({ sessionId, update }),
|
|
1680
|
+
warn: (message) => ctx.logger.warn(`acp-extension-dsh: ${message}`)
|
|
1681
|
+
}),
|
|
1682
|
+
questions,
|
|
1683
|
+
usage: new HarnessUsageTracker(!baseUrl || /^https:\/\/api\.deepseek\.com(?:\/v1)?\/?$/.test(baseUrl)),
|
|
1684
|
+
agent: handle.agent,
|
|
1685
|
+
dispose,
|
|
1686
|
+
selection,
|
|
1687
|
+
permissionMode,
|
|
1688
|
+
permissionOptions,
|
|
1689
|
+
agentPreset: mountedPreset,
|
|
1690
|
+
agentPresetOptions,
|
|
1691
|
+
models,
|
|
1692
|
+
started: fork?.seed.some((event) => event.type === "turn/start") ?? false,
|
|
1693
|
+
outputTail: Promise.resolve()
|
|
1694
|
+
};
|
|
1695
|
+
if (fork) {
|
|
1696
|
+
try {
|
|
1697
|
+
await flushSession(handle.agent.session);
|
|
1698
|
+
assertOpen();
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
let cleanupError;
|
|
1701
|
+
try {
|
|
1702
|
+
await dispose();
|
|
1703
|
+
} catch (failure2) {
|
|
1704
|
+
cleanupError = failure2;
|
|
1705
|
+
}
|
|
1706
|
+
throw internalError(`fork child ${sessionId} could not be committed: ${errorChain(error)}${cleanupError ? `; cleanup: ${errorChain(cleanupError)}` : ""}`);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
sessions.set(sessionId, record2);
|
|
1710
|
+
return {
|
|
1711
|
+
sessionId,
|
|
1712
|
+
modes: modeState(record2),
|
|
1713
|
+
configOptions: configOptions(record2),
|
|
1714
|
+
models: legacyModels(record2)
|
|
1715
|
+
};
|
|
1716
|
+
};
|
|
1478
1717
|
const makeAgent = (connection) => {
|
|
1479
1718
|
conn = connection;
|
|
1480
1719
|
return {
|
|
@@ -1501,7 +1740,7 @@ function apply(ctx, rawConfig) {
|
|
|
1501
1740
|
embeddedContext: false
|
|
1502
1741
|
},
|
|
1503
1742
|
mcpCapabilities: { http: true },
|
|
1504
|
-
sessionCapabilities: { close: {} },
|
|
1743
|
+
sessionCapabilities: { close: {}, fork: {} },
|
|
1505
1744
|
_meta: { lody: LODY_CAPABILITIES }
|
|
1506
1745
|
},
|
|
1507
1746
|
authMethods: []
|
|
@@ -1510,118 +1749,36 @@ function apply(ctx, rawConfig) {
|
|
|
1510
1749
|
authenticate(_params) {
|
|
1511
1750
|
return Promise.resolve();
|
|
1512
1751
|
},
|
|
1513
|
-
|
|
1752
|
+
newSession(params) {
|
|
1753
|
+
return createSession(params);
|
|
1754
|
+
},
|
|
1755
|
+
async unstable_forkSession(params) {
|
|
1514
1756
|
assertOpen();
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
throw internalError(`failed to discover models: ${errorChain(error)}`);
|
|
1521
|
-
}
|
|
1522
|
-
const { models, initialModel: initialModelId } = catalog;
|
|
1523
|
-
const sessionId = randomUUID();
|
|
1524
|
-
const questions = new UserQuestionBridge(sessionId, (request) => conn.unstable_createElicitation(request), () => questionCapabilities);
|
|
1525
|
-
const initialModel = models.find((model) => model.id === initialModelId);
|
|
1526
|
-
if (!initialModel)
|
|
1527
|
-
throw internalError("initial model metadata is unavailable");
|
|
1528
|
-
const reasoningEffort = resolveReasoningEffort(initialModel, config.reasoningEffort);
|
|
1529
|
-
const selection = {
|
|
1530
|
-
current: {
|
|
1531
|
-
provider: config.provider,
|
|
1532
|
-
model: initialModelId,
|
|
1533
|
-
...reasoningEffort ? { reasoningEffort } : {}
|
|
1534
|
-
}
|
|
1757
|
+
const target = forkTarget(params._meta);
|
|
1758
|
+
const creation = {
|
|
1759
|
+
cwd: params.cwd,
|
|
1760
|
+
mcpServers: params.mcpServers ?? [],
|
|
1761
|
+
additionalDirectories: params.additionalDirectories
|
|
1535
1762
|
};
|
|
1536
|
-
|
|
1537
|
-
const
|
|
1538
|
-
if (!
|
|
1539
|
-
throw internalError(
|
|
1540
|
-
|
|
1541
|
-
const mcpServerNames = reserveMcpServerNames(params.mcpServers, sessionId, activeMcpServerNames);
|
|
1542
|
-
let mountedPreset = requestedPreset;
|
|
1543
|
-
let handle;
|
|
1763
|
+
validateSessionParams(creation);
|
|
1764
|
+
const query = ctx.get("sessionQuery");
|
|
1765
|
+
if (!query)
|
|
1766
|
+
throw internalError("Harness session query is unavailable");
|
|
1767
|
+
let source;
|
|
1544
1768
|
try {
|
|
1545
|
-
|
|
1546
|
-
sessionId,
|
|
1547
|
-
meta: { cwd: params.cwd, agentPreset: requestedPreset },
|
|
1548
|
-
agentOptions: { provider: config.provider, model: initialModelId },
|
|
1549
|
-
setup: async (agentContext) => {
|
|
1550
|
-
if (subagentEvents && !carrierKeyOf) {
|
|
1551
|
-
const scope = await agentContext.loader.import("@deepseek-ai/dsh-scope");
|
|
1552
|
-
if (typeof scope.carrierKeyOf !== "function")
|
|
1553
|
-
throw new Error("Harness scope identity API is unavailable");
|
|
1554
|
-
carrierKeyOf = scope.carrierKeyOf;
|
|
1555
|
-
}
|
|
1556
|
-
installModelSelection(agentContext, selection);
|
|
1557
|
-
agentContext.on("user-questions/request", (request, next) => {
|
|
1558
|
-
if (!request.agent || ownedRecord(request.agent)?.questions !== questions)
|
|
1559
|
-
return next();
|
|
1560
|
-
return questions.ask(request);
|
|
1561
|
-
});
|
|
1562
|
-
mountedPreset = (await ctx.agentPresets.mount(agentContext, requestedPreset)).id;
|
|
1563
|
-
await mountMcpServers(agentContext, params.mcpServers, mcpServerNames.names, params.cwd);
|
|
1564
|
-
}
|
|
1565
|
-
});
|
|
1769
|
+
source = await query.observeSession(params.sessionId);
|
|
1566
1770
|
} catch (error) {
|
|
1567
|
-
|
|
1568
|
-
if (error instanceof RequestError)
|
|
1569
|
-
throw error;
|
|
1570
|
-
throw internalError(`failed to create session: ${errorChain(error)}`);
|
|
1771
|
+
throw invalidParams(`fork source unavailable: ${errorChain(error)}`);
|
|
1571
1772
|
}
|
|
1572
|
-
|
|
1573
|
-
questions.cancel();
|
|
1574
|
-
try {
|
|
1575
|
-
await handle.dispose();
|
|
1576
|
-
} finally {
|
|
1577
|
-
mcpServerNames.release();
|
|
1578
|
-
}
|
|
1579
|
-
};
|
|
1580
|
-
if (closed) {
|
|
1581
|
-
await dispose();
|
|
1582
|
-
throw internalError("connection closed during session/new");
|
|
1583
|
-
}
|
|
1584
|
-
let permissionMode;
|
|
1585
|
-
let permissionOptions;
|
|
1773
|
+
let snapshot;
|
|
1586
1774
|
try {
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1775
|
+
if (source.header.id !== params.sessionId)
|
|
1776
|
+
throw invalidParams("fork source identity mismatch");
|
|
1777
|
+
snapshot = forkSnapshot(source, target);
|
|
1778
|
+
} finally {
|
|
1779
|
+
source[Symbol.dispose]();
|
|
1592
1780
|
}
|
|
1593
|
-
|
|
1594
|
-
cwd: params.cwd,
|
|
1595
|
-
tools: new ToolCallBridge({
|
|
1596
|
-
cwd: params.cwd,
|
|
1597
|
-
lookup: (name2) => {
|
|
1598
|
-
const tools = handle.agent.ctx.get("tools");
|
|
1599
|
-
return tools?.get(name2, handle.agent);
|
|
1600
|
-
},
|
|
1601
|
-
content: (block) => assistantBlockToAcp(block, attachments),
|
|
1602
|
-
emit: (update) => notify({ sessionId, update }),
|
|
1603
|
-
warn: (message) => ctx.logger.warn(`acp-extension-dsh: ${message}`)
|
|
1604
|
-
}),
|
|
1605
|
-
questions,
|
|
1606
|
-
usage: new HarnessUsageTracker(!baseUrl || /^https:\/\/api\.deepseek\.com(?:\/v1)?\/?$/.test(baseUrl)),
|
|
1607
|
-
agent: handle.agent,
|
|
1608
|
-
dispose,
|
|
1609
|
-
selection,
|
|
1610
|
-
permissionMode,
|
|
1611
|
-
permissionOptions,
|
|
1612
|
-
agentPreset: mountedPreset,
|
|
1613
|
-
agentPresetOptions,
|
|
1614
|
-
models,
|
|
1615
|
-
started: false,
|
|
1616
|
-
outputTail: Promise.resolve()
|
|
1617
|
-
};
|
|
1618
|
-
sessions.set(sessionId, record2);
|
|
1619
|
-
return {
|
|
1620
|
-
sessionId,
|
|
1621
|
-
modes: modeState(record2),
|
|
1622
|
-
configOptions: configOptions(record2),
|
|
1623
|
-
models: legacyModels(record2)
|
|
1624
|
-
};
|
|
1781
|
+
return createSession(creation, snapshot);
|
|
1625
1782
|
},
|
|
1626
1783
|
setSessionMode(params) {
|
|
1627
1784
|
const record2 = requireSession(params.sessionId);
|