lody 0.103.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-BzvviK-N.js → acp-C0LiROfB.js} +3 -3
- package/dist/chunks/{cloudflared-native-DbUToPHH.js → cloudflared-native-BWtpTE_5.js} +3 -3
- package/dist/chunks/{daemon-session-tools-DESvdAeY.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-DEdPzgH7.js → index-Sv4ANSUY.js} +8893 -2560
- package/dist/chunks/ios-simulator-hTbW-tat.js +161 -0
- package/dist/chunks/{review-viewer-BZiXmQnJ.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 +253 -128
- package/dist/devin-acp.js +1152 -0
- package/dist/file-index-scan-worker.js +2 -2
- package/dist/index.js +107512 -37004
- 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-BzvviK-N.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 },
|
|
@@ -1140,6 +1186,11 @@ function apply(ctx, rawConfig) {
|
|
|
1140
1186
|
});
|
|
1141
1187
|
});
|
|
1142
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
|
+
};
|
|
1143
1194
|
const settleAfterQuiescence = (record2, inflight) => {
|
|
1144
1195
|
if (inflight.settlementStarted)
|
|
1145
1196
|
return;
|
|
@@ -1150,6 +1201,7 @@ function apply(ctx, rawConfig) {
|
|
|
1150
1201
|
await record2.agent.whenIdle();
|
|
1151
1202
|
await record2.outputTail;
|
|
1152
1203
|
await record2.tools.interrupt();
|
|
1204
|
+
await flushSession(record2.agent.session);
|
|
1153
1205
|
}
|
|
1154
1206
|
if (record2.inflight !== inflight)
|
|
1155
1207
|
return;
|
|
@@ -1328,7 +1380,11 @@ function apply(ctx, rawConfig) {
|
|
|
1328
1380
|
continue;
|
|
1329
1381
|
await notify({
|
|
1330
1382
|
sessionId: record2.agent.session.id,
|
|
1331
|
-
update: {
|
|
1383
|
+
update: {
|
|
1384
|
+
sessionUpdate: "agent_message_chunk",
|
|
1385
|
+
content,
|
|
1386
|
+
...typeof event.data.turn === "number" ? { _meta: { lody: { turnId: nativeTurnId(event.data.turn) } } } : {}
|
|
1387
|
+
}
|
|
1332
1388
|
});
|
|
1333
1389
|
}
|
|
1334
1390
|
}, inflight);
|
|
@@ -1375,9 +1431,19 @@ function apply(ctx, rawConfig) {
|
|
|
1375
1431
|
}
|
|
1376
1432
|
});
|
|
1377
1433
|
ctx.on("agent/inbox/claimed", ({ agent, message, turn }) => {
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1434
|
+
const record2 = ownedRecord(agent);
|
|
1435
|
+
const inflight = record2?.inflight;
|
|
1436
|
+
if (record2 && inflight && inflight.messageId === message.id) {
|
|
1380
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
|
+
}
|
|
1381
1447
|
});
|
|
1382
1448
|
ctx.on("agent/error", ({ agent, turn, error }) => {
|
|
1383
1449
|
const record2 = ownedRecord(agent);
|
|
@@ -1504,6 +1570,150 @@ function apply(ctx, rawConfig) {
|
|
|
1504
1570
|
}
|
|
1505
1571
|
return { configOptions: configOptions(record2) };
|
|
1506
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
|
+
};
|
|
1507
1717
|
const makeAgent = (connection) => {
|
|
1508
1718
|
conn = connection;
|
|
1509
1719
|
return {
|
|
@@ -1530,7 +1740,7 @@ function apply(ctx, rawConfig) {
|
|
|
1530
1740
|
embeddedContext: false
|
|
1531
1741
|
},
|
|
1532
1742
|
mcpCapabilities: { http: true },
|
|
1533
|
-
sessionCapabilities: { close: {} },
|
|
1743
|
+
sessionCapabilities: { close: {}, fork: {} },
|
|
1534
1744
|
_meta: { lody: LODY_CAPABILITIES }
|
|
1535
1745
|
},
|
|
1536
1746
|
authMethods: []
|
|
@@ -1539,121 +1749,36 @@ function apply(ctx, rawConfig) {
|
|
|
1539
1749
|
authenticate(_params) {
|
|
1540
1750
|
return Promise.resolve();
|
|
1541
1751
|
},
|
|
1542
|
-
|
|
1752
|
+
newSession(params) {
|
|
1753
|
+
return createSession(params);
|
|
1754
|
+
},
|
|
1755
|
+
async unstable_forkSession(params) {
|
|
1543
1756
|
assertOpen();
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
throw internalError(`failed to discover models: ${errorChain(error)}`);
|
|
1550
|
-
}
|
|
1551
|
-
const { models, initialSelection } = catalog;
|
|
1552
|
-
const sessionId = randomUUID();
|
|
1553
|
-
const questions = new UserQuestionBridge(sessionId, (request) => conn.unstable_createElicitation(request), () => questionCapabilities);
|
|
1554
|
-
const initialModel = models.find((model) => model.provider === initialSelection.provider && model.id === initialSelection.model);
|
|
1555
|
-
if (!initialModel)
|
|
1556
|
-
throw internalError("initial model metadata is unavailable");
|
|
1557
|
-
const reasoningEffort = resolveReasoningEffort(initialModel, config.reasoningEffort);
|
|
1558
|
-
const selection = {
|
|
1559
|
-
current: {
|
|
1560
|
-
provider: initialSelection.provider,
|
|
1561
|
-
model: initialSelection.model,
|
|
1562
|
-
...reasoningEffort ? { reasoningEffort } : {}
|
|
1563
|
-
}
|
|
1757
|
+
const target = forkTarget(params._meta);
|
|
1758
|
+
const creation = {
|
|
1759
|
+
cwd: params.cwd,
|
|
1760
|
+
mcpServers: params.mcpServers ?? [],
|
|
1761
|
+
additionalDirectories: params.additionalDirectories
|
|
1564
1762
|
};
|
|
1565
|
-
|
|
1566
|
-
const
|
|
1567
|
-
if (!
|
|
1568
|
-
throw internalError(
|
|
1569
|
-
|
|
1570
|
-
const mcpServerNames = reserveMcpServerNames(params.mcpServers, sessionId, activeMcpServerNames);
|
|
1571
|
-
let mountedPreset = requestedPreset;
|
|
1572
|
-
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;
|
|
1573
1768
|
try {
|
|
1574
|
-
|
|
1575
|
-
sessionId,
|
|
1576
|
-
meta: { cwd: params.cwd, agentPreset: requestedPreset },
|
|
1577
|
-
agentOptions: {
|
|
1578
|
-
provider: initialSelection.provider,
|
|
1579
|
-
model: initialSelection.model
|
|
1580
|
-
},
|
|
1581
|
-
setup: async (agentContext) => {
|
|
1582
|
-
if (subagentEvents && !carrierKeyOf) {
|
|
1583
|
-
const scope = await agentContext.loader.import("@deepseek-ai/dsh-scope");
|
|
1584
|
-
if (typeof scope.carrierKeyOf !== "function")
|
|
1585
|
-
throw new Error("Harness scope identity API is unavailable");
|
|
1586
|
-
carrierKeyOf = scope.carrierKeyOf;
|
|
1587
|
-
}
|
|
1588
|
-
installModelSelection(agentContext, selection);
|
|
1589
|
-
agentContext.on("user-questions/request", (request, next) => {
|
|
1590
|
-
if (!request.agent || ownedRecord(request.agent)?.questions !== questions)
|
|
1591
|
-
return next();
|
|
1592
|
-
return questions.ask(request);
|
|
1593
|
-
});
|
|
1594
|
-
mountedPreset = (await ctx.agentPresets.mount(agentContext, requestedPreset)).id;
|
|
1595
|
-
await mountMcpServers(agentContext, params.mcpServers, mcpServerNames.names, params.cwd);
|
|
1596
|
-
}
|
|
1597
|
-
});
|
|
1769
|
+
source = await query.observeSession(params.sessionId);
|
|
1598
1770
|
} catch (error) {
|
|
1599
|
-
|
|
1600
|
-
if (error instanceof RequestError)
|
|
1601
|
-
throw error;
|
|
1602
|
-
throw internalError(`failed to create session: ${errorChain(error)}`);
|
|
1603
|
-
}
|
|
1604
|
-
const dispose = async () => {
|
|
1605
|
-
questions.cancel();
|
|
1606
|
-
try {
|
|
1607
|
-
await handle.dispose();
|
|
1608
|
-
} finally {
|
|
1609
|
-
mcpServerNames.release();
|
|
1610
|
-
}
|
|
1611
|
-
};
|
|
1612
|
-
if (closed) {
|
|
1613
|
-
await dispose();
|
|
1614
|
-
throw internalError("connection closed during session/new");
|
|
1771
|
+
throw invalidParams(`fork source unavailable: ${errorChain(error)}`);
|
|
1615
1772
|
}
|
|
1616
|
-
let
|
|
1617
|
-
let permissionOptions;
|
|
1773
|
+
let snapshot;
|
|
1618
1774
|
try {
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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]();
|
|
1624
1780
|
}
|
|
1625
|
-
|
|
1626
|
-
cwd: params.cwd,
|
|
1627
|
-
tools: new ToolCallBridge({
|
|
1628
|
-
cwd: params.cwd,
|
|
1629
|
-
lookup: (name2) => {
|
|
1630
|
-
const tools = handle.agent.ctx.get("tools");
|
|
1631
|
-
return tools?.get(name2, handle.agent);
|
|
1632
|
-
},
|
|
1633
|
-
content: (block) => assistantBlockToAcp(block, attachments),
|
|
1634
|
-
emit: (update) => notify({ sessionId, update }),
|
|
1635
|
-
warn: (message) => ctx.logger.warn(`acp-extension-dsh: ${message}`)
|
|
1636
|
-
}),
|
|
1637
|
-
questions,
|
|
1638
|
-
usage: new HarnessUsageTracker(!baseUrl || /^https:\/\/api\.deepseek\.com(?:\/v1)?\/?$/.test(baseUrl)),
|
|
1639
|
-
agent: handle.agent,
|
|
1640
|
-
dispose,
|
|
1641
|
-
selection,
|
|
1642
|
-
permissionMode,
|
|
1643
|
-
permissionOptions,
|
|
1644
|
-
agentPreset: mountedPreset,
|
|
1645
|
-
agentPresetOptions,
|
|
1646
|
-
models,
|
|
1647
|
-
started: false,
|
|
1648
|
-
outputTail: Promise.resolve()
|
|
1649
|
-
};
|
|
1650
|
-
sessions.set(sessionId, record2);
|
|
1651
|
-
return {
|
|
1652
|
-
sessionId,
|
|
1653
|
-
modes: modeState(record2),
|
|
1654
|
-
configOptions: configOptions(record2),
|
|
1655
|
-
models: legacyModels(record2)
|
|
1656
|
-
};
|
|
1781
|
+
return createSession(creation, snapshot);
|
|
1657
1782
|
},
|
|
1658
1783
|
setSessionMode(params) {
|
|
1659
1784
|
const record2 = requireSession(params.sessionId);
|