pi2dsh 0.2.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/LICENSE +21 -0
- package/README.md +122 -0
- package/README.zh.md +122 -0
- package/dist/cli.d.mts +2 -0
- package/dist/cli.mjs +128 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/compat/pi-ai.d.mts +2597 -0
- package/dist/compat/pi-ai.d.mts.map +1 -0
- package/dist/compat/pi-ai.mjs +4669 -0
- package/dist/compat/pi-ai.mjs.map +1 -0
- package/dist/compat/pi-coding-agent.d.mts +745 -0
- package/dist/compat/pi-coding-agent.d.mts.map +1 -0
- package/dist/compat/pi-coding-agent.mjs +4 -0
- package/dist/compat/pi-tui.d.mts +3 -0
- package/dist/compat/pi-tui.mjs +3622 -0
- package/dist/compat/pi-tui.mjs.map +1 -0
- package/dist/host.d.mts +35 -0
- package/dist/host.d.mts.map +1 -0
- package/dist/host.mjs +197 -0
- package/dist/host.mjs.map +1 -0
- package/dist/index.d.mts +69 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +5 -0
- package/dist/mcp-config-jL9w70It.mjs +1535 -0
- package/dist/mcp-config-jL9w70It.mjs.map +1 -0
- package/dist/pi-coding-agent-Dsg6_0ua.mjs +2060 -0
- package/dist/pi-coding-agent-Dsg6_0ua.mjs.map +1 -0
- package/dist/pi-config-shim-CZ1wFzqM.mjs +27 -0
- package/dist/pi-config-shim-CZ1wFzqM.mjs.map +1 -0
- package/dist/pi-tui-iHoF2tFc.d.mts +1043 -0
- package/dist/pi-tui-iHoF2tFc.d.mts.map +1 -0
- package/dist/pi-tui-utils-CcaVtm-3.mjs +895 -0
- package/dist/pi-tui-utils-CcaVtm-3.mjs.map +1 -0
- package/dist/pi-types-KazmR2O5.d.mts +62 -0
- package/dist/pi-types-KazmR2O5.d.mts.map +1 -0
- package/dist/pi-uuid-Db8ShZsK.mjs +47 -0
- package/dist/pi-uuid-Db8ShZsK.mjs.map +1 -0
- package/dist/rolldown-runtime-C2Q2p085.mjs +15 -0
- package/dist/runtime-D84Hv_3m.mjs +1499 -0
- package/dist/runtime-D84Hv_3m.mjs.map +1 -0
- package/dist/runtime.d.mts +31 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +3 -0
- package/dist/source-D7Ir-rPT.mjs +154 -0
- package/dist/source-D7Ir-rPT.mjs.map +1 -0
- package/dist/types-7IWJPPvS.d.mts +59 -0
- package/dist/types-7IWJPPvS.d.mts.map +1 -0
- package/package.json +135 -0
|
@@ -0,0 +1,1499 @@
|
|
|
1
|
+
|
|
2
|
+
import { t as getAgentDir } from "./pi-config-shim-CZ1wFzqM.mjs";
|
|
3
|
+
import { x as Theme } from "./pi-coding-agent-Dsg6_0ua.mjs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { access, readFile } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { EventEmitter } from "node:events";
|
|
9
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
+
import { createJiti } from "jiti";
|
|
11
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
12
|
+
import { renderPrompt } from "@deepseek-ai/dsh-system-prompt";
|
|
13
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
//#region src/session-bridge.ts
|
|
16
|
+
function sidecarDir() {
|
|
17
|
+
return join(getAgentDir(), "session-entries");
|
|
18
|
+
}
|
|
19
|
+
function sessionEvents(session) {
|
|
20
|
+
const events = session.events;
|
|
21
|
+
return typeof events === "function" ? events.call(session) : events ?? [];
|
|
22
|
+
}
|
|
23
|
+
function dshToPiContent$1(content) {
|
|
24
|
+
if (!Array.isArray(content)) return [{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: String(content ?? "")
|
|
27
|
+
}];
|
|
28
|
+
return content.map((block) => {
|
|
29
|
+
if (typeof block !== "object" || block === null) return {
|
|
30
|
+
type: "text",
|
|
31
|
+
text: String(block)
|
|
32
|
+
};
|
|
33
|
+
const record = block;
|
|
34
|
+
if (record.type === "text") return {
|
|
35
|
+
type: "text",
|
|
36
|
+
text: String(record.text ?? "")
|
|
37
|
+
};
|
|
38
|
+
if (record.type === "reasoning") return {
|
|
39
|
+
type: "thinking",
|
|
40
|
+
thinking: String(record.text ?? "")
|
|
41
|
+
};
|
|
42
|
+
if (record.type === "tool-call") return {
|
|
43
|
+
type: "toolCall",
|
|
44
|
+
id: record.id,
|
|
45
|
+
name: record.name,
|
|
46
|
+
arguments: record.arguments
|
|
47
|
+
};
|
|
48
|
+
return { type: record.type };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
var PiSessionBridge = class {
|
|
52
|
+
records = /* @__PURE__ */ new Map();
|
|
53
|
+
loaded = /* @__PURE__ */ new Set();
|
|
54
|
+
sidecarPath(sessionId) {
|
|
55
|
+
const safe = sessionId.replace(/[^a-zA-Z0-9._-]+/gu, "_");
|
|
56
|
+
return join(sidecarDir(), `${safe}.jsonl`);
|
|
57
|
+
}
|
|
58
|
+
load(sessionId) {
|
|
59
|
+
if (this.loaded.has(sessionId)) return;
|
|
60
|
+
this.loaded.add(sessionId);
|
|
61
|
+
const path = this.sidecarPath(sessionId);
|
|
62
|
+
if (!existsSync(path)) {
|
|
63
|
+
this.records.set(sessionId, this.records.get(sessionId) ?? []);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const parsed = [];
|
|
67
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
68
|
+
if (line.trim().length === 0) continue;
|
|
69
|
+
try {
|
|
70
|
+
parsed.push(JSON.parse(line));
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
this.records.set(sessionId, parsed);
|
|
74
|
+
}
|
|
75
|
+
persist(sessionId, record) {
|
|
76
|
+
this.load(sessionId);
|
|
77
|
+
const list = this.records.get(sessionId) ?? [];
|
|
78
|
+
list.push(record);
|
|
79
|
+
this.records.set(sessionId, list);
|
|
80
|
+
mkdirSync(sidecarDir(), { recursive: true });
|
|
81
|
+
appendFileSync(this.sidecarPath(sessionId), `${JSON.stringify(record)}\n`);
|
|
82
|
+
}
|
|
83
|
+
appendCustomEntry(sessionId, customType, data) {
|
|
84
|
+
const id = randomUUID();
|
|
85
|
+
this.persist(sessionId, {
|
|
86
|
+
kind: "custom",
|
|
87
|
+
id,
|
|
88
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
89
|
+
customType,
|
|
90
|
+
...data === void 0 ? {} : { data }
|
|
91
|
+
});
|
|
92
|
+
return id;
|
|
93
|
+
}
|
|
94
|
+
appendLabel(sessionId, targetId, label) {
|
|
95
|
+
this.persist(sessionId, {
|
|
96
|
+
kind: "label",
|
|
97
|
+
id: randomUUID(),
|
|
98
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
99
|
+
targetId,
|
|
100
|
+
...label === void 0 ? {} : { label }
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
setName(sessionId, name) {
|
|
104
|
+
const sanitized = name.replace(/[\r\n]+/gu, " ").trim();
|
|
105
|
+
this.persist(sessionId, {
|
|
106
|
+
kind: "name",
|
|
107
|
+
id: randomUUID(),
|
|
108
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
109
|
+
name: sanitized
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
getName(sessionId) {
|
|
113
|
+
this.load(sessionId);
|
|
114
|
+
const list = this.records.get(sessionId) ?? [];
|
|
115
|
+
for (let i = list.length - 1; i >= 0; i -= 1) if (list[i].kind === "name") return list[i].name;
|
|
116
|
+
}
|
|
117
|
+
labels(sessionId) {
|
|
118
|
+
this.load(sessionId);
|
|
119
|
+
const labels = /* @__PURE__ */ new Map();
|
|
120
|
+
for (const record of this.records.get(sessionId) ?? []) {
|
|
121
|
+
if (record.kind !== "label" || record.targetId === void 0) continue;
|
|
122
|
+
if (record.label === void 0) labels.delete(record.targetId);
|
|
123
|
+
else labels.set(record.targetId, record.label);
|
|
124
|
+
}
|
|
125
|
+
return labels;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Project the DSH durable log plus sidecar records into Pi's entry-chain
|
|
129
|
+
* shape. DSH history is linear, so the projection is a single-branch tree:
|
|
130
|
+
* every entry's parent is its predecessor.
|
|
131
|
+
*/
|
|
132
|
+
projectEntries(session) {
|
|
133
|
+
this.load(session.id);
|
|
134
|
+
const merged = [];
|
|
135
|
+
for (const event of sessionEvents(session)) {
|
|
136
|
+
const seq = Number(event.seq ?? 0);
|
|
137
|
+
const time = Number(event.time ?? 0);
|
|
138
|
+
const data = event.data ?? {};
|
|
139
|
+
const type = event.type;
|
|
140
|
+
if (type === "user/message") merged.push({
|
|
141
|
+
time,
|
|
142
|
+
entry: {
|
|
143
|
+
type: "message",
|
|
144
|
+
id: `dsh-${seq}`,
|
|
145
|
+
timestamp: new Date(time).toISOString(),
|
|
146
|
+
message: {
|
|
147
|
+
role: "user",
|
|
148
|
+
content: dshToPiContent$1(data.content)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
else if (type === "assistant/message") {
|
|
153
|
+
const message = data.message ?? {};
|
|
154
|
+
merged.push({
|
|
155
|
+
time,
|
|
156
|
+
entry: {
|
|
157
|
+
type: "message",
|
|
158
|
+
id: `dsh-${seq}`,
|
|
159
|
+
timestamp: new Date(time).toISOString(),
|
|
160
|
+
message: {
|
|
161
|
+
role: "assistant",
|
|
162
|
+
content: dshToPiContent$1(message.content)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
} else if (type === "tool/result") {
|
|
167
|
+
const message = data.message ?? {};
|
|
168
|
+
const tool = (Array.isArray(message.content) ? message.content : []).find((block) => block.type === "tool-result");
|
|
169
|
+
merged.push({
|
|
170
|
+
time,
|
|
171
|
+
entry: {
|
|
172
|
+
type: "message",
|
|
173
|
+
id: `dsh-${seq}`,
|
|
174
|
+
timestamp: new Date(time).toISOString(),
|
|
175
|
+
message: {
|
|
176
|
+
role: "toolResult",
|
|
177
|
+
toolCallId: tool?.toolCallId,
|
|
178
|
+
content: dshToPiContent$1(tool?.content ?? []),
|
|
179
|
+
isError: tool?.isError === true
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
for (const record of this.records.get(session.id) ?? []) {
|
|
186
|
+
const time = Date.parse(record.timestamp);
|
|
187
|
+
if (record.kind === "custom") merged.push({
|
|
188
|
+
time,
|
|
189
|
+
entry: {
|
|
190
|
+
type: "custom",
|
|
191
|
+
id: record.id,
|
|
192
|
+
timestamp: record.timestamp,
|
|
193
|
+
customType: record.customType,
|
|
194
|
+
...record.data === void 0 ? {} : { data: record.data }
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
else if (record.kind === "label") merged.push({
|
|
198
|
+
time,
|
|
199
|
+
entry: {
|
|
200
|
+
type: "label",
|
|
201
|
+
id: record.id,
|
|
202
|
+
timestamp: record.timestamp,
|
|
203
|
+
targetId: record.targetId,
|
|
204
|
+
label: record.label
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
else merged.push({
|
|
208
|
+
time,
|
|
209
|
+
entry: {
|
|
210
|
+
type: "session_info",
|
|
211
|
+
id: record.id,
|
|
212
|
+
timestamp: record.timestamp,
|
|
213
|
+
name: record.name
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
merged.sort((left, right) => left.time - right.time);
|
|
218
|
+
const entries = [];
|
|
219
|
+
let parentId = null;
|
|
220
|
+
for (const item of merged) {
|
|
221
|
+
const entry = {
|
|
222
|
+
...item.entry,
|
|
223
|
+
parentId
|
|
224
|
+
};
|
|
225
|
+
entries.push(entry);
|
|
226
|
+
parentId = entry.id;
|
|
227
|
+
}
|
|
228
|
+
return entries;
|
|
229
|
+
}
|
|
230
|
+
/** The exact 14-method surface Pi exposes as ctx.sessionManager. */
|
|
231
|
+
readonlySessionManager(session, cwd) {
|
|
232
|
+
const entriesOf = () => this.projectEntries(session);
|
|
233
|
+
const leafOf = () => entriesOf().at(-1);
|
|
234
|
+
return {
|
|
235
|
+
getCwd: () => cwd,
|
|
236
|
+
getSessionDir: () => sidecarDir(),
|
|
237
|
+
getSessionId: () => session.id,
|
|
238
|
+
getSessionFile: () => this.sidecarPath(session.id),
|
|
239
|
+
getLeafId: () => leafOf()?.id ?? null,
|
|
240
|
+
getLeafEntry: () => leafOf(),
|
|
241
|
+
getEntry: (id) => entriesOf().find((entry) => entry.id === id),
|
|
242
|
+
getLabel: (id) => this.labels(session.id).get(id),
|
|
243
|
+
getBranch: (fromId) => {
|
|
244
|
+
const entries = entriesOf();
|
|
245
|
+
if (fromId === void 0) return entries;
|
|
246
|
+
const index = entries.findIndex((entry) => entry.id === fromId);
|
|
247
|
+
return index === -1 ? [] : entries.slice(0, index + 1);
|
|
248
|
+
},
|
|
249
|
+
buildContextEntries: () => entriesOf().filter((entry) => entry.type === "message" || entry.type === "compaction" || entry.type === "branch_summary" || entry.type === "custom_message"),
|
|
250
|
+
getHeader: () => ({
|
|
251
|
+
type: "session",
|
|
252
|
+
version: 3,
|
|
253
|
+
id: session.id,
|
|
254
|
+
timestamp: new Date(Number(sessionEvents(session)[0]?.time ?? Date.now())).toISOString(),
|
|
255
|
+
cwd
|
|
256
|
+
}),
|
|
257
|
+
getEntries: () => entriesOf(),
|
|
258
|
+
getTree: () => {
|
|
259
|
+
const entries = entriesOf();
|
|
260
|
+
const labels = this.labels(session.id);
|
|
261
|
+
let root;
|
|
262
|
+
let cursor;
|
|
263
|
+
for (const entry of entries) {
|
|
264
|
+
const node = {
|
|
265
|
+
entry,
|
|
266
|
+
children: [],
|
|
267
|
+
...labels.has(entry.id) ? { label: labels.get(entry.id) } : {}
|
|
268
|
+
};
|
|
269
|
+
if (cursor === void 0) root = node;
|
|
270
|
+
else cursor.children.push(node);
|
|
271
|
+
cursor = node;
|
|
272
|
+
}
|
|
273
|
+
return root === void 0 ? [] : [root];
|
|
274
|
+
},
|
|
275
|
+
getSessionName: () => this.getName(session.id)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/runtime.ts
|
|
281
|
+
function logger(ctx) {
|
|
282
|
+
const candidate = ctx.logger;
|
|
283
|
+
return {
|
|
284
|
+
warn: (message) => candidate?.warn?.(message) ?? console.warn(message),
|
|
285
|
+
info: (message) => candidate?.info?.(message) ?? console.info(message),
|
|
286
|
+
debug: (message) => candidate?.debug?.(message) ?? void 0
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function cloneJson(value) {
|
|
290
|
+
return structuredClone(value);
|
|
291
|
+
}
|
|
292
|
+
function jsonEqual(left, right) {
|
|
293
|
+
try {
|
|
294
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
295
|
+
} catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function jsonValue(value) {
|
|
300
|
+
if (value === void 0) return null;
|
|
301
|
+
try {
|
|
302
|
+
return JSON.parse(JSON.stringify(value));
|
|
303
|
+
} catch {
|
|
304
|
+
return String(value);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function textBlocks(content) {
|
|
308
|
+
if (!Array.isArray(content)) return [{
|
|
309
|
+
type: "text",
|
|
310
|
+
text: String(content ?? "")
|
|
311
|
+
}];
|
|
312
|
+
return content.map((block) => {
|
|
313
|
+
if (typeof block === "object" && block !== null && block.type === "text") return {
|
|
314
|
+
type: "text",
|
|
315
|
+
text: String(block.text ?? "")
|
|
316
|
+
};
|
|
317
|
+
if (typeof block === "object" && block !== null && block.type === "image") return {
|
|
318
|
+
type: "text",
|
|
319
|
+
text: `[Pi tool returned ${String(block.mimeType ?? "image")}; binary image output requires a native DSH attachment adapter]`
|
|
320
|
+
};
|
|
321
|
+
return {
|
|
322
|
+
type: "text",
|
|
323
|
+
text: String(block)
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
function normalizeToolResult(result) {
|
|
328
|
+
if (typeof result !== "object" || result === null || Array.isArray(result)) return {
|
|
329
|
+
content: [{
|
|
330
|
+
type: "text",
|
|
331
|
+
text: String(result ?? "")
|
|
332
|
+
}],
|
|
333
|
+
details: null
|
|
334
|
+
};
|
|
335
|
+
const record = result;
|
|
336
|
+
return {
|
|
337
|
+
content: textBlocks(record.content),
|
|
338
|
+
details: jsonValue(record.details),
|
|
339
|
+
...record.isError === true ? { isError: true } : {},
|
|
340
|
+
...record.usage !== void 0 ? { usage: jsonValue(record.usage) } : {},
|
|
341
|
+
...record.terminate === true ? { terminate: true } : {}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
async function piToDshContent(ctx, content) {
|
|
345
|
+
const values = Array.isArray(content) ? content : [{
|
|
346
|
+
type: "text",
|
|
347
|
+
text: String(content ?? "")
|
|
348
|
+
}];
|
|
349
|
+
const blocks = [];
|
|
350
|
+
for (const value of values) {
|
|
351
|
+
if (typeof value !== "object" || value === null) {
|
|
352
|
+
blocks.push({
|
|
353
|
+
type: "text",
|
|
354
|
+
text: String(value)
|
|
355
|
+
});
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const block = value;
|
|
359
|
+
if (block.type === "text") {
|
|
360
|
+
blocks.push({
|
|
361
|
+
type: "text",
|
|
362
|
+
text: String(block.text ?? "")
|
|
363
|
+
});
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (block.type !== "image") {
|
|
367
|
+
blocks.push({
|
|
368
|
+
type: "text",
|
|
369
|
+
text: String(value)
|
|
370
|
+
});
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const attachments = optionalService(ctx, "attachments");
|
|
374
|
+
if (attachments === void 0) throw new Error("pi2dsh: Pi image content requires the DSH attachments service");
|
|
375
|
+
if (typeof block.data !== "string" || typeof block.mimeType !== "string") throw new TypeError("pi2dsh: Pi image content requires base64 data and mimeType");
|
|
376
|
+
const attachment = await attachments.saveImage({
|
|
377
|
+
data: Buffer.from(block.data, "base64"),
|
|
378
|
+
mediaType: block.mimeType,
|
|
379
|
+
...typeof block.name === "string" ? { name: block.name } : {}
|
|
380
|
+
});
|
|
381
|
+
blocks.push({
|
|
382
|
+
type: "image",
|
|
383
|
+
attachment
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return blocks;
|
|
387
|
+
}
|
|
388
|
+
async function normalizeToolResultForDsh(ctx, result) {
|
|
389
|
+
if (typeof result !== "object" || result === null || Array.isArray(result)) return {
|
|
390
|
+
content: [{
|
|
391
|
+
type: "text",
|
|
392
|
+
text: String(result ?? "")
|
|
393
|
+
}],
|
|
394
|
+
details: null
|
|
395
|
+
};
|
|
396
|
+
const record = result;
|
|
397
|
+
return {
|
|
398
|
+
content: await piToDshContent(ctx, record.content),
|
|
399
|
+
details: jsonValue(record.details),
|
|
400
|
+
...record.isError === true ? { isError: true } : {},
|
|
401
|
+
...record.usage !== void 0 ? { usage: jsonValue(record.usage) } : {},
|
|
402
|
+
...record.terminate === true ? { terminate: true } : {}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
const SUPPORTED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
|
|
406
|
+
"type",
|
|
407
|
+
"oneOf",
|
|
408
|
+
"anyOf",
|
|
409
|
+
"properties",
|
|
410
|
+
"required",
|
|
411
|
+
"additionalProperties",
|
|
412
|
+
"items",
|
|
413
|
+
"enum",
|
|
414
|
+
"const",
|
|
415
|
+
"description",
|
|
416
|
+
"title",
|
|
417
|
+
"default",
|
|
418
|
+
"examples"
|
|
419
|
+
]);
|
|
420
|
+
function normalizeSchemaNode(value, path, warnings) {
|
|
421
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
422
|
+
warnings.push(`${path}: non-object schema replaced with unconstrained JSON`);
|
|
423
|
+
return {};
|
|
424
|
+
}
|
|
425
|
+
const source = value;
|
|
426
|
+
const output = {};
|
|
427
|
+
for (const key of Object.keys(source)) if (!SUPPORTED_SCHEMA_KEYS.has(key)) warnings.push(`${path}.${key}: constraint is not enforced by DSH and was dropped`);
|
|
428
|
+
const type = source.type;
|
|
429
|
+
if (typeof type === "string" && [
|
|
430
|
+
"object",
|
|
431
|
+
"array",
|
|
432
|
+
"string",
|
|
433
|
+
"number",
|
|
434
|
+
"integer",
|
|
435
|
+
"boolean",
|
|
436
|
+
"null"
|
|
437
|
+
].includes(type)) output.type = type;
|
|
438
|
+
else if (type !== void 0) warnings.push(`${path}.type: unsupported type was dropped`);
|
|
439
|
+
const union = Array.isArray(source.oneOf) ? source.oneOf : Array.isArray(source.anyOf) ? source.anyOf : void 0;
|
|
440
|
+
if (union !== void 0) {
|
|
441
|
+
if (source.anyOf !== void 0) warnings.push(`${path}.anyOf: converted to DSH exact-one oneOf semantics`);
|
|
442
|
+
output.oneOf = union.map((entry, index) => normalizeSchemaNode(entry, `${path}.oneOf[${index}]`, warnings));
|
|
443
|
+
delete output.type;
|
|
444
|
+
}
|
|
445
|
+
if (output.type === "object") {
|
|
446
|
+
const properties = typeof source.properties === "object" && source.properties !== null && !Array.isArray(source.properties) ? source.properties : {};
|
|
447
|
+
output.properties = Object.fromEntries(Object.entries(properties).map(([key, entry]) => [key, normalizeSchemaNode(entry, `${path}.properties.${key}`, warnings)]));
|
|
448
|
+
if (Array.isArray(source.required)) output.required = source.required.filter((item) => typeof item === "string");
|
|
449
|
+
if (typeof source.additionalProperties === "boolean") output.additionalProperties = source.additionalProperties;
|
|
450
|
+
else if (source.additionalProperties !== void 0) {
|
|
451
|
+
output.additionalProperties = true;
|
|
452
|
+
warnings.push(`${path}.additionalProperties: schema-valued form widened to true`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (output.type === "array" && source.items !== void 0) output.items = normalizeSchemaNode(source.items, `${path}.items`, warnings);
|
|
456
|
+
if (Array.isArray(source.enum)) output.enum = source.enum.filter((item) => item === null || [
|
|
457
|
+
"string",
|
|
458
|
+
"number",
|
|
459
|
+
"boolean"
|
|
460
|
+
].includes(typeof item));
|
|
461
|
+
if (source.const === null || [
|
|
462
|
+
"string",
|
|
463
|
+
"number",
|
|
464
|
+
"boolean"
|
|
465
|
+
].includes(typeof source.const)) output.const = source.const;
|
|
466
|
+
for (const annotation of ["description", "title"]) if (typeof source[annotation] === "string") output[annotation] = source[annotation];
|
|
467
|
+
for (const annotation of ["default", "examples"]) if (source[annotation] !== void 0) output[annotation] = jsonValue(source[annotation]);
|
|
468
|
+
return output;
|
|
469
|
+
}
|
|
470
|
+
function normalizeToolSchema(schema) {
|
|
471
|
+
const warnings = [];
|
|
472
|
+
const normalized = normalizeSchemaNode(schema, "$", warnings);
|
|
473
|
+
if (normalized.type !== "object") throw new TypeError("Pi tool parameters must use an object-root TypeBox schema");
|
|
474
|
+
return {
|
|
475
|
+
schema: normalized,
|
|
476
|
+
warnings
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
function cwdOf(agent) {
|
|
480
|
+
const session = agent?.session;
|
|
481
|
+
if (typeof session === "object" && session !== null) {
|
|
482
|
+
const header = session.header;
|
|
483
|
+
if (typeof header === "object" && header !== null && typeof header.cwd === "string") return header.cwd;
|
|
484
|
+
}
|
|
485
|
+
return process.cwd();
|
|
486
|
+
}
|
|
487
|
+
function unsupported(name) {
|
|
488
|
+
throw new Error(`pi2dsh: Pi API ${name} requires a native DSH port; inspect the compatibility report`);
|
|
489
|
+
}
|
|
490
|
+
function optionalService(ctx, name) {
|
|
491
|
+
return ctx.get(name);
|
|
492
|
+
}
|
|
493
|
+
function requireAgent(state, operation) {
|
|
494
|
+
const agent = currentAgent(state);
|
|
495
|
+
if (agent === void 0) throw new Error(`pi2dsh: ${operation} requires one active DSH agent context`);
|
|
496
|
+
return agent;
|
|
497
|
+
}
|
|
498
|
+
function answerText(answer) {
|
|
499
|
+
if (answer === void 0) return void 0;
|
|
500
|
+
if (typeof answer.custom === "string" && answer.custom.length > 0) return answer.custom;
|
|
501
|
+
const selected = answer.selected;
|
|
502
|
+
return Array.isArray(selected) && typeof selected[0] === "string" ? selected[0] : void 0;
|
|
503
|
+
}
|
|
504
|
+
async function askOne(ctx, agent, signal, question) {
|
|
505
|
+
const service = optionalService(ctx, "userQuestions");
|
|
506
|
+
if (service === void 0) unsupported("ctx.ui AskUser");
|
|
507
|
+
return answerText((await service.ask({
|
|
508
|
+
questions: [question],
|
|
509
|
+
...agent !== void 0 ? { agent } : {},
|
|
510
|
+
signal
|
|
511
|
+
})).answers.find((answer) => answer.id === question.id));
|
|
512
|
+
}
|
|
513
|
+
function agentSession(agent) {
|
|
514
|
+
const session = agent?.session;
|
|
515
|
+
if (typeof session !== "object" || session === null) return void 0;
|
|
516
|
+
const record = session;
|
|
517
|
+
if (typeof record.id !== "string") return void 0;
|
|
518
|
+
return record;
|
|
519
|
+
}
|
|
520
|
+
function thinkingLevelOf(state, agent) {
|
|
521
|
+
if (agent !== void 0) {
|
|
522
|
+
const scoped = state.thinkingLevels.get(agent);
|
|
523
|
+
if (scoped !== void 0) return scoped;
|
|
524
|
+
}
|
|
525
|
+
return state.globalThinkingLevel;
|
|
526
|
+
}
|
|
527
|
+
function contextFor(ctx, state, agent, signal, command = false) {
|
|
528
|
+
const notices = [];
|
|
529
|
+
const userQuestions = optionalService(ctx, "userQuestions");
|
|
530
|
+
const ui = {
|
|
531
|
+
notify(message) {
|
|
532
|
+
const text = String(message);
|
|
533
|
+
notices.push(text);
|
|
534
|
+
state.notifications.push(text);
|
|
535
|
+
logger(ctx).info(`[pi2dsh] ${text}`);
|
|
536
|
+
},
|
|
537
|
+
select: (title, options) => askOne(ctx, agent, signal, {
|
|
538
|
+
id: "pi2dsh-select",
|
|
539
|
+
question: String(title),
|
|
540
|
+
options: options.map((option) => ({ label: String(option) }))
|
|
541
|
+
}),
|
|
542
|
+
async confirm(title, message) {
|
|
543
|
+
return await askOne(ctx, agent, signal, {
|
|
544
|
+
id: "pi2dsh-confirm",
|
|
545
|
+
question: String(title),
|
|
546
|
+
detail: String(message),
|
|
547
|
+
options: [{ label: "Yes" }, { label: "No" }]
|
|
548
|
+
}) === "Yes";
|
|
549
|
+
},
|
|
550
|
+
input: (title, placeholder) => askOne(ctx, agent, signal, {
|
|
551
|
+
id: "pi2dsh-input",
|
|
552
|
+
question: String(title),
|
|
553
|
+
...placeholder === void 0 ? {} : { detail: String(placeholder) }
|
|
554
|
+
}),
|
|
555
|
+
editor: (title, prefill) => askOne(ctx, agent, signal, {
|
|
556
|
+
id: "pi2dsh-editor",
|
|
557
|
+
question: String(title),
|
|
558
|
+
...prefill === void 0 ? {} : { detail: `Current text:\n${String(prefill)}` }
|
|
559
|
+
}),
|
|
560
|
+
setStatus: () => void 0,
|
|
561
|
+
setWidget: () => void 0,
|
|
562
|
+
onTerminalInput: () => () => void 0,
|
|
563
|
+
setWorkingMessage: () => void 0,
|
|
564
|
+
setWorkingVisible: () => void 0,
|
|
565
|
+
setWorkingIndicator: () => void 0,
|
|
566
|
+
setHiddenThinkingLabel: () => void 0,
|
|
567
|
+
setFooter: () => void 0,
|
|
568
|
+
setHeader: () => void 0,
|
|
569
|
+
setTitle: () => void 0,
|
|
570
|
+
custom: async () => void 0,
|
|
571
|
+
pasteToEditor(text) {
|
|
572
|
+
if (agent !== void 0) state.editorBuffers.set(agent, (state.editorBuffers.get(agent) ?? "") + String(text));
|
|
573
|
+
},
|
|
574
|
+
setEditorText(text) {
|
|
575
|
+
if (agent !== void 0) state.editorBuffers.set(agent, String(text));
|
|
576
|
+
},
|
|
577
|
+
getEditorText: () => agent === void 0 ? "" : state.editorBuffers.get(agent) ?? "",
|
|
578
|
+
addAutocompleteProvider(factory) {
|
|
579
|
+
state.autocompleteProviders.push(factory);
|
|
580
|
+
},
|
|
581
|
+
setEditorComponent(factory) {
|
|
582
|
+
state.editorComponentFactory = factory;
|
|
583
|
+
},
|
|
584
|
+
getEditorComponent: () => state.editorComponentFactory,
|
|
585
|
+
get theme() {
|
|
586
|
+
return state.theme;
|
|
587
|
+
},
|
|
588
|
+
getAllThemes: () => [{
|
|
589
|
+
name: state.theme.name,
|
|
590
|
+
path: void 0
|
|
591
|
+
}],
|
|
592
|
+
getTheme: (name) => name === state.theme.name ? state.theme : void 0,
|
|
593
|
+
setTheme: (target) => typeof target === "object" || target === state.theme.name ? { success: true } : {
|
|
594
|
+
success: false,
|
|
595
|
+
error: `pi2dsh headless mode ships a single theme (${state.theme.name})`
|
|
596
|
+
},
|
|
597
|
+
getToolsExpanded: () => state.toolsExpanded,
|
|
598
|
+
setToolsExpanded(expanded) {
|
|
599
|
+
state.toolsExpanded = expanded === true;
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
const session = agentSession(agent);
|
|
603
|
+
const base = {
|
|
604
|
+
ui,
|
|
605
|
+
mode: "rpc",
|
|
606
|
+
hasUI: userQuestions !== void 0,
|
|
607
|
+
cwd: cwdOf(agent),
|
|
608
|
+
sessionManager: session === void 0 ? state.bridge.readonlySessionManager({
|
|
609
|
+
id: "pi2dsh-detached",
|
|
610
|
+
events: []
|
|
611
|
+
}, cwdOf(agent)) : state.bridge.readonlySessionManager(session, cwdOf(agent)),
|
|
612
|
+
modelRegistry: {
|
|
613
|
+
getAll: () => [],
|
|
614
|
+
getAvailable: () => [],
|
|
615
|
+
find: (_provider, _modelId) => void 0,
|
|
616
|
+
getError: () => void 0,
|
|
617
|
+
hasConfiguredAuth: (_model) => false,
|
|
618
|
+
getProviderAuthStatus: (_provider) => "none",
|
|
619
|
+
getProvider: (_provider) => void 0,
|
|
620
|
+
getProviderDisplayName: (provider) => provider,
|
|
621
|
+
getProviderAuth: async (_provider) => void 0,
|
|
622
|
+
getApiKeyForProvider: async (_provider) => void 0,
|
|
623
|
+
isUsingOAuth: (_model) => false,
|
|
624
|
+
refresh: async () => ({
|
|
625
|
+
models: [],
|
|
626
|
+
errors: []
|
|
627
|
+
})
|
|
628
|
+
},
|
|
629
|
+
model: agent === void 0 ? void 0 : state.modelOverrides.get(agent),
|
|
630
|
+
scopedModels: [],
|
|
631
|
+
thinkingLevel: thinkingLevelOf(state, agent),
|
|
632
|
+
isIdle: () => command,
|
|
633
|
+
isProjectTrusted: () => false,
|
|
634
|
+
signal,
|
|
635
|
+
abort: () => {
|
|
636
|
+
const target = agent;
|
|
637
|
+
if (typeof target?.cancel !== "function") unsupported("ctx.abort without a live DSH agent");
|
|
638
|
+
target.cancel({
|
|
639
|
+
kind: "hook",
|
|
640
|
+
reason: "pi2dsh: aborted by migrated Pi extension"
|
|
641
|
+
});
|
|
642
|
+
},
|
|
643
|
+
hasPendingMessages: () => false,
|
|
644
|
+
shutdown: () => unsupported("ctx.shutdown"),
|
|
645
|
+
getContextUsage: () => void 0,
|
|
646
|
+
compact: () => unsupported("ctx.compact"),
|
|
647
|
+
getSystemPrompt: () => state.currentSystemPrompt,
|
|
648
|
+
__agent: agent,
|
|
649
|
+
__notices: notices
|
|
650
|
+
};
|
|
651
|
+
if (command) Object.assign(base, {
|
|
652
|
+
getSystemPromptOptions: () => ({}),
|
|
653
|
+
waitForIdle: async () => {
|
|
654
|
+
const wait = agent?.whenIdle;
|
|
655
|
+
if (typeof wait === "function") await wait.call(agent);
|
|
656
|
+
},
|
|
657
|
+
newSession: () => unsupported("ctx.newSession"),
|
|
658
|
+
fork: () => unsupported("ctx.fork"),
|
|
659
|
+
navigateTree: () => unsupported("ctx.navigateTree"),
|
|
660
|
+
switchSession: () => unsupported("ctx.switchSession"),
|
|
661
|
+
reload: () => unsupported("ctx.reload")
|
|
662
|
+
});
|
|
663
|
+
return base;
|
|
664
|
+
}
|
|
665
|
+
async function dispatch(state, eventName, event, eventContext) {
|
|
666
|
+
const results = [];
|
|
667
|
+
const agent = eventContext.__agent;
|
|
668
|
+
for (const handler of state.handlers.get(eventName) ?? []) results.push(await state.agentScope.run(agent, () => handler(event, eventContext)));
|
|
669
|
+
return results;
|
|
670
|
+
}
|
|
671
|
+
function dshToPiContent(content) {
|
|
672
|
+
return content.map((block) => {
|
|
673
|
+
if (block.type === "text") return {
|
|
674
|
+
type: "text",
|
|
675
|
+
text: block.text
|
|
676
|
+
};
|
|
677
|
+
if (block.type === "reasoning") return {
|
|
678
|
+
type: "thinking",
|
|
679
|
+
thinking: block.text
|
|
680
|
+
};
|
|
681
|
+
if (block.type === "tool-call") return {
|
|
682
|
+
type: "toolCall",
|
|
683
|
+
id: block.id,
|
|
684
|
+
name: block.name,
|
|
685
|
+
arguments: block.arguments
|
|
686
|
+
};
|
|
687
|
+
return { type: block.type };
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
function messageFromSessionEvent(event) {
|
|
691
|
+
const type = event.type;
|
|
692
|
+
const data = event.data;
|
|
693
|
+
if (typeof data !== "object" || data === null) return void 0;
|
|
694
|
+
const record = data;
|
|
695
|
+
if (type === "user/message") return {
|
|
696
|
+
role: "user",
|
|
697
|
+
content: dshToPiContent(record.content ?? [])
|
|
698
|
+
};
|
|
699
|
+
if (type === "assistant/message") {
|
|
700
|
+
const message = record.message;
|
|
701
|
+
return {
|
|
702
|
+
role: "assistant",
|
|
703
|
+
content: dshToPiContent(message?.content ?? [])
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
if (type === "tool/result") {
|
|
707
|
+
const tool = (record.message?.content ?? []).find((block) => block.type === "tool-result");
|
|
708
|
+
return {
|
|
709
|
+
role: "toolResult",
|
|
710
|
+
toolCallId: tool?.toolCallId,
|
|
711
|
+
content: dshToPiContent(tool?.content ?? []),
|
|
712
|
+
isError: tool?.isError === true
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function sourceReason(value) {
|
|
717
|
+
return value === "resume" ? "resume" : value === "fork" ? "fork" : "startup";
|
|
718
|
+
}
|
|
719
|
+
function subscribeLifecycle(ctx, state) {
|
|
720
|
+
const cordis = ctx;
|
|
721
|
+
const warn = (event, error) => logger(ctx).warn(`[pi2dsh] ${event} handler failed: ${String(error)}`);
|
|
722
|
+
cordis.on("agent/session-start", (payload) => {
|
|
723
|
+
const agent = payload.agent;
|
|
724
|
+
state.activeAgents.add(agent);
|
|
725
|
+
const session = agentSession(agent);
|
|
726
|
+
if (session !== void 0) state.bridge.load(session.id);
|
|
727
|
+
if (state.pendingActiveTools !== void 0) state.agentScope.run(agent, () => setActiveTools(ctx, state, state.pendingActiveTools));
|
|
728
|
+
dispatch(state, "session_start", {
|
|
729
|
+
type: "session_start",
|
|
730
|
+
reason: sourceReason(payload.source)
|
|
731
|
+
}, contextFor(ctx, state, agent, void 0)).catch((error) => warn("session_start", error));
|
|
732
|
+
});
|
|
733
|
+
cordis.on("agent/disposed", (payload) => {
|
|
734
|
+
const agent = payload.agent;
|
|
735
|
+
state.activeAgents.delete(agent);
|
|
736
|
+
if (typeof agent === "object" && agent !== null) {
|
|
737
|
+
state.toolRestrictions.get(agent)?.();
|
|
738
|
+
state.toolRestrictions.delete(agent);
|
|
739
|
+
}
|
|
740
|
+
if (typeof agent === "object" && agent !== null && !state.disposedAgents.has(agent)) {
|
|
741
|
+
state.disposedAgents.add(agent);
|
|
742
|
+
dispatch(state, "session_shutdown", {
|
|
743
|
+
type: "session_shutdown",
|
|
744
|
+
reason: "quit"
|
|
745
|
+
}, contextFor(ctx, state, agent, void 0)).catch((error) => warn("session_shutdown", error));
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
cordis.on("session/event", (session, event) => {
|
|
749
|
+
const agent = [...state.activeAgents].find((candidate) => candidate.session === session);
|
|
750
|
+
const eventContext = contextFor(ctx, state, agent, void 0);
|
|
751
|
+
const type = event.type;
|
|
752
|
+
if (type === "turn/start") {
|
|
753
|
+
const turn = Number(event.data.turn ?? 1);
|
|
754
|
+
dispatch(state, "agent_start", { type: "agent_start" }, eventContext).catch((error) => warn("agent_start", error));
|
|
755
|
+
dispatch(state, "turn_start", {
|
|
756
|
+
type: "turn_start",
|
|
757
|
+
turnIndex: turn - 1,
|
|
758
|
+
timestamp: event.time ?? Date.now()
|
|
759
|
+
}, eventContext).catch((error) => warn("turn_start", error));
|
|
760
|
+
}
|
|
761
|
+
if (type === "tool/call") {
|
|
762
|
+
const data = event.data;
|
|
763
|
+
let args = {};
|
|
764
|
+
try {
|
|
765
|
+
args = JSON.parse(String(data.arguments ?? "{}"));
|
|
766
|
+
} catch {
|
|
767
|
+
args = {};
|
|
768
|
+
}
|
|
769
|
+
dispatch(state, "tool_execution_start", {
|
|
770
|
+
type: "tool_execution_start",
|
|
771
|
+
toolCallId: data.callId,
|
|
772
|
+
toolName: data.name,
|
|
773
|
+
args
|
|
774
|
+
}, eventContext).catch((error) => warn("tool_execution_start", error));
|
|
775
|
+
}
|
|
776
|
+
if (type === "assistant/chunk" && (state.handlers.get("message_update")?.length ?? 0) > 0) {
|
|
777
|
+
const data = event.data;
|
|
778
|
+
const chunk = data.chunk ?? {};
|
|
779
|
+
const key = `${String(session.id ?? "")}:${String(data.turn ?? 0)}:${String(data.step ?? 0)}`;
|
|
780
|
+
const delta = typeof chunk.text === "string" ? chunk.text : typeof chunk.delta === "string" ? chunk.delta : "";
|
|
781
|
+
const accumulated = (state.streamingTexts.get(key) ?? "") + delta;
|
|
782
|
+
state.streamingTexts.set(key, accumulated);
|
|
783
|
+
dispatch(state, "message_update", {
|
|
784
|
+
type: "message_update",
|
|
785
|
+
message: {
|
|
786
|
+
role: "assistant",
|
|
787
|
+
content: [{
|
|
788
|
+
type: "text",
|
|
789
|
+
text: accumulated
|
|
790
|
+
}]
|
|
791
|
+
},
|
|
792
|
+
assistantMessageEvent: chunk
|
|
793
|
+
}, eventContext).catch((error) => warn("message_update", error));
|
|
794
|
+
}
|
|
795
|
+
if (type === "assistant/message") {
|
|
796
|
+
const data = event.data;
|
|
797
|
+
state.streamingTexts.delete(`${String(session.id ?? "")}:${String(data.turn ?? 0)}:${String(data.step ?? 0)}`);
|
|
798
|
+
}
|
|
799
|
+
if (type === "session/title") {
|
|
800
|
+
const data = event.data;
|
|
801
|
+
dispatch(state, "session_info_changed", {
|
|
802
|
+
type: "session_info_changed",
|
|
803
|
+
name: typeof data.title === "string" ? data.title : void 0
|
|
804
|
+
}, eventContext).catch((error) => warn("session_info_changed", error));
|
|
805
|
+
}
|
|
806
|
+
if (type === "compaction/start") dispatch(state, "session_before_compact", {
|
|
807
|
+
type: "session_before_compact",
|
|
808
|
+
preparation: { ...event.data },
|
|
809
|
+
branchEntries: [],
|
|
810
|
+
reason: "threshold",
|
|
811
|
+
willRetry: false
|
|
812
|
+
}, eventContext).catch((error) => warn("session_before_compact", error));
|
|
813
|
+
if (type === "compaction/end" || type === "compaction/summary") {
|
|
814
|
+
const data = event.data;
|
|
815
|
+
dispatch(state, "session_compact", {
|
|
816
|
+
type: "session_compact",
|
|
817
|
+
compactionEntry: {
|
|
818
|
+
type: "compaction",
|
|
819
|
+
id: `dsh-${String(event.seq ?? "")}`,
|
|
820
|
+
summary: typeof data.summary === "string" ? data.summary : "",
|
|
821
|
+
...data
|
|
822
|
+
},
|
|
823
|
+
fromExtension: false,
|
|
824
|
+
reason: "threshold",
|
|
825
|
+
willRetry: false
|
|
826
|
+
}, eventContext).catch((error) => warn("session_compact", error));
|
|
827
|
+
}
|
|
828
|
+
if (type === "request/header") {
|
|
829
|
+
const header = event.data.header ?? {};
|
|
830
|
+
const model = header.model ?? header.config?.model;
|
|
831
|
+
if (model !== void 0 && agent !== void 0) {
|
|
832
|
+
const previous = state.lastLoggedModels.get(agent);
|
|
833
|
+
if (previous !== void 0 && previous !== String(model)) dispatch(state, "model_select", {
|
|
834
|
+
type: "model_select",
|
|
835
|
+
model: { id: String(model) },
|
|
836
|
+
previousModel: { id: previous },
|
|
837
|
+
source: "set"
|
|
838
|
+
}, eventContext).catch((error) => warn("model_select", error));
|
|
839
|
+
state.lastLoggedModels.set(agent, String(model));
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const message = messageFromSessionEvent(event);
|
|
843
|
+
if (message !== void 0) dispatch(state, "message_start", {
|
|
844
|
+
type: "message_start",
|
|
845
|
+
message
|
|
846
|
+
}, eventContext).then(() => dispatch(state, "message_end", {
|
|
847
|
+
type: "message_end",
|
|
848
|
+
message
|
|
849
|
+
}, eventContext)).catch((error) => warn("message lifecycle", error));
|
|
850
|
+
if (type === "turn/end") {
|
|
851
|
+
const data = event.data;
|
|
852
|
+
dispatch(state, "turn_end", {
|
|
853
|
+
type: "turn_end",
|
|
854
|
+
turnIndex: Number(data.turn ?? 1) - 1,
|
|
855
|
+
message: {
|
|
856
|
+
role: "assistant",
|
|
857
|
+
content: []
|
|
858
|
+
},
|
|
859
|
+
toolResults: []
|
|
860
|
+
}, eventContext).then(() => dispatch(state, "agent_end", {
|
|
861
|
+
type: "agent_end",
|
|
862
|
+
messages: []
|
|
863
|
+
}, eventContext)).then(() => dispatch(state, "agent_settled", { type: "agent_settled" }, eventContext)).catch((error) => warn("turn end lifecycle", error));
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
cordis.on("agent/request", async (payload, next) => {
|
|
867
|
+
const config = await next();
|
|
868
|
+
const agent = payload.agent;
|
|
869
|
+
if (agent === void 0) return config;
|
|
870
|
+
const override = state.modelOverrides.get(agent);
|
|
871
|
+
const thinking = state.thinkingLevels.get(agent);
|
|
872
|
+
if (override === void 0 && thinking === void 0) return config;
|
|
873
|
+
return {
|
|
874
|
+
...config,
|
|
875
|
+
...override?.provider === void 0 ? {} : { provider: override.provider },
|
|
876
|
+
...override?.model === void 0 ? {} : { model: override.model },
|
|
877
|
+
...thinking === void 0 || thinking === "off" ? {} : { reasoningEffort: thinking }
|
|
878
|
+
};
|
|
879
|
+
});
|
|
880
|
+
cordis.on("tools/result", (exec, result) => {
|
|
881
|
+
const agent = exec.agent;
|
|
882
|
+
dispatch(state, "tool_execution_end", {
|
|
883
|
+
type: "tool_execution_end",
|
|
884
|
+
toolCallId: exec.callId,
|
|
885
|
+
toolName: exec.name,
|
|
886
|
+
result: {
|
|
887
|
+
content: dshToPiContent(result.content),
|
|
888
|
+
details: result.meta ?? null
|
|
889
|
+
},
|
|
890
|
+
isError: result.isError
|
|
891
|
+
}, contextFor(ctx, state, agent, exec.signal)).catch((error) => warn("tool_execution_end", error));
|
|
892
|
+
});
|
|
893
|
+
cordis.effect(() => async () => {
|
|
894
|
+
for (const agent of state.activeAgents) if (typeof agent === "object" && agent !== null && !state.disposedAgents.has(agent)) {
|
|
895
|
+
state.disposedAgents.add(agent);
|
|
896
|
+
await dispatch(state, "session_shutdown", {
|
|
897
|
+
type: "session_shutdown",
|
|
898
|
+
reason: "quit"
|
|
899
|
+
}, contextFor(ctx, state, agent, void 0));
|
|
900
|
+
}
|
|
901
|
+
state.activeAgents.clear();
|
|
902
|
+
for (const dispose of state.toolDisposers.values()) dispose();
|
|
903
|
+
state.toolDisposers.clear();
|
|
904
|
+
state.eventBus.removeAllListeners();
|
|
905
|
+
}, "pi2dsh session shutdown");
|
|
906
|
+
}
|
|
907
|
+
function subscribeInterceptors(ctx, state) {
|
|
908
|
+
const cordis = ctx;
|
|
909
|
+
cordis.on("tools/pre-execute", async (exec, next) => {
|
|
910
|
+
const input = cloneJson(exec.arguments);
|
|
911
|
+
const event = {
|
|
912
|
+
type: "tool_call",
|
|
913
|
+
toolName: exec.name,
|
|
914
|
+
toolCallId: exec.callId,
|
|
915
|
+
input
|
|
916
|
+
};
|
|
917
|
+
const results = await dispatch(state, "tool_call", event, contextFor(ctx, state, exec.agent, exec.signal));
|
|
918
|
+
if (!jsonEqual(event.input, exec.arguments)) {
|
|
919
|
+
if (state.tools.has(exec.name)) state.argMutations.set(exec, cloneJson(event.input));
|
|
920
|
+
else return {
|
|
921
|
+
kind: "deny",
|
|
922
|
+
reason: `pi2dsh: a Pi tool_call hook mutated arguments of native DSH tool ${JSON.stringify(exec.name)}; DSH logs arguments before policy, so this mutation cannot be honored`
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
for (const result of results) if (typeof result === "object" && result !== null && result.block === true) return {
|
|
926
|
+
kind: "deny",
|
|
927
|
+
reason: String(result.reason ?? "blocked by migrated Pi tool_call hook")
|
|
928
|
+
};
|
|
929
|
+
return next();
|
|
930
|
+
});
|
|
931
|
+
cordis.on("tools/post-execute", async (exec, result, next) => {
|
|
932
|
+
const downstream = await next();
|
|
933
|
+
if (downstream.kind === "block") return downstream;
|
|
934
|
+
const event = {
|
|
935
|
+
type: "tool_result",
|
|
936
|
+
toolName: exec.name,
|
|
937
|
+
toolCallId: exec.callId,
|
|
938
|
+
input: cloneJson(exec.arguments),
|
|
939
|
+
content: dshToPiContent(result.content),
|
|
940
|
+
details: result.meta ?? null,
|
|
941
|
+
isError: result.isError,
|
|
942
|
+
usage: void 0
|
|
943
|
+
};
|
|
944
|
+
const results = await dispatch(state, "tool_result", event, contextFor(ctx, state, exec.agent, exec.signal));
|
|
945
|
+
for (const patch of results) {
|
|
946
|
+
if (typeof patch !== "object" || patch === null) continue;
|
|
947
|
+
Object.assign(event, patch);
|
|
948
|
+
}
|
|
949
|
+
const content = textBlocks(event.content);
|
|
950
|
+
if (event.isError === true && !result.isError) return {
|
|
951
|
+
kind: "block",
|
|
952
|
+
feedback: content
|
|
953
|
+
};
|
|
954
|
+
if (event.isError === false && result.isError) logger(ctx).warn("[pi2dsh] a Pi tool_result hook attempted to recover a DSH error; error recovery was ignored");
|
|
955
|
+
if (!jsonEqual(event.content, dshToPiContent(result.content))) return {
|
|
956
|
+
kind: "accept",
|
|
957
|
+
content
|
|
958
|
+
};
|
|
959
|
+
return downstream;
|
|
960
|
+
});
|
|
961
|
+
cordis.on("system-prompt/assemble", async (assembly, assembleContext, next) => {
|
|
962
|
+
const downstream = await next();
|
|
963
|
+
if ((state.handlers.get("before_agent_start")?.length ?? 0) === 0) return downstream;
|
|
964
|
+
const original = renderPrompt(downstream);
|
|
965
|
+
state.currentSystemPrompt = original;
|
|
966
|
+
const event = {
|
|
967
|
+
type: "before_agent_start",
|
|
968
|
+
prompt: "",
|
|
969
|
+
systemPrompt: original,
|
|
970
|
+
systemPromptOptions: {}
|
|
971
|
+
};
|
|
972
|
+
const results = await dispatch(state, "before_agent_start", event, contextFor(ctx, state, assembleContext.scope, assembleContext.signal));
|
|
973
|
+
let replacement = original;
|
|
974
|
+
for (const result of results) {
|
|
975
|
+
if (typeof result === "object" && result !== null && typeof result.systemPrompt === "string") {
|
|
976
|
+
replacement = result.systemPrompt;
|
|
977
|
+
event.systemPrompt = replacement;
|
|
978
|
+
}
|
|
979
|
+
if (typeof result === "object" && result !== null && result.message !== void 0) logger(ctx).warn("[pi2dsh] before_agent_start custom-message injection is unsupported and was ignored");
|
|
980
|
+
}
|
|
981
|
+
state.currentSystemPrompt = replacement;
|
|
982
|
+
return {
|
|
983
|
+
...downstream,
|
|
984
|
+
sections: [{
|
|
985
|
+
name: "pi2dsh:system-prompt",
|
|
986
|
+
text: replacement
|
|
987
|
+
}]
|
|
988
|
+
};
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
function registerTool(ctx, state, tool) {
|
|
992
|
+
if (state.tools.has(tool.name)) throw new Error(`Pi tool ${JSON.stringify(tool.name)} is already registered`);
|
|
993
|
+
const normalized = normalizeToolSchema(tool.parameters);
|
|
994
|
+
for (const warning of normalized.warnings) logger(ctx).warn(`[pi2dsh] tool ${tool.name}: ${warning}`);
|
|
995
|
+
state.tools.set(tool.name, tool);
|
|
996
|
+
const definition = {
|
|
997
|
+
name: tool.name,
|
|
998
|
+
description: tool.description,
|
|
999
|
+
parameters: normalized.schema,
|
|
1000
|
+
output: {
|
|
1001
|
+
schema: {},
|
|
1002
|
+
render: (_args, value) => value.content,
|
|
1003
|
+
presentationMeta: (_args, value) => jsonValue(value.details)
|
|
1004
|
+
},
|
|
1005
|
+
isConcurrencySafe: () => tool.executionMode === "parallel",
|
|
1006
|
+
async execute(args, exec) {
|
|
1007
|
+
const mutated = state.argMutations.get(exec);
|
|
1008
|
+
if (mutated !== void 0) state.argMutations.delete(exec);
|
|
1009
|
+
const effective = mutated ?? args;
|
|
1010
|
+
const prepared = tool.prepareArguments?.(cloneJson(effective)) ?? effective;
|
|
1011
|
+
const agent = exec.agent;
|
|
1012
|
+
const result = await normalizeToolResultForDsh(ctx, await state.agentScope.run(agent, () => tool.execute(String(exec.callId), prepared, exec.signal, (update) => {
|
|
1013
|
+
dispatch(state, "tool_execution_update", {
|
|
1014
|
+
type: "tool_execution_update",
|
|
1015
|
+
toolCallId: String(exec.callId),
|
|
1016
|
+
toolName: tool.name,
|
|
1017
|
+
args: prepared,
|
|
1018
|
+
partialResult: jsonValue(update)
|
|
1019
|
+
}, contextFor(ctx, state, agent, exec.signal)).catch((error) => logger(ctx).warn(`[pi2dsh] tool_execution_update handler failed: ${String(error)}`));
|
|
1020
|
+
}, contextFor(ctx, state, agent, exec.signal))));
|
|
1021
|
+
if (result.terminate === true) exec.concludeTurn();
|
|
1022
|
+
if (result.isError === true) {
|
|
1023
|
+
const message = textBlocks(result.content).map((block) => block.text).filter(Boolean).join("\n");
|
|
1024
|
+
throw new Error(message || `Pi tool ${tool.name} failed`);
|
|
1025
|
+
}
|
|
1026
|
+
return result;
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
const dispose = ctx.tools.register(definition);
|
|
1030
|
+
state.toolDisposers.set(tool.name, dispose);
|
|
1031
|
+
}
|
|
1032
|
+
function unregisterTool(state, name) {
|
|
1033
|
+
const dispose = state.toolDisposers.get(name);
|
|
1034
|
+
if (dispose === void 0) return false;
|
|
1035
|
+
dispose();
|
|
1036
|
+
state.toolDisposers.delete(name);
|
|
1037
|
+
state.tools.delete(name);
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
1040
|
+
function currentAgent(state) {
|
|
1041
|
+
const scoped = state.agentScope.getStore();
|
|
1042
|
+
if (scoped !== void 0) return scoped;
|
|
1043
|
+
if (state.activeAgents.size === 1) return state.activeAgents.values().next().value;
|
|
1044
|
+
}
|
|
1045
|
+
function toolRuntime(ctx, agent) {
|
|
1046
|
+
return (agent?.ctx)?.tools ?? ctx.tools;
|
|
1047
|
+
}
|
|
1048
|
+
function getActiveTools(ctx, state) {
|
|
1049
|
+
const agent = currentAgent(state);
|
|
1050
|
+
return toolRuntime(ctx, agent).schemas(agent).map((tool) => tool.name);
|
|
1051
|
+
}
|
|
1052
|
+
function setActiveTools(ctx, state, names) {
|
|
1053
|
+
const unique = [...new Set(names)];
|
|
1054
|
+
const agent = currentAgent(state);
|
|
1055
|
+
state.pendingActiveTools = unique;
|
|
1056
|
+
if (agent === void 0 || typeof agent !== "object" || agent === null) return;
|
|
1057
|
+
const scopedTools = agent.ctx === void 0 ? void 0 : toolRuntime(ctx, agent);
|
|
1058
|
+
if (scopedTools === void 0 || typeof scopedTools.restrict !== "function") {
|
|
1059
|
+
logger(ctx).warn("[pi2dsh] setActiveTools deferred: the current agent exposes no scoped tools.restrict()");
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
state.toolRestrictions.get(agent)?.();
|
|
1063
|
+
state.toolRestrictions.set(agent, scopedTools.restrict({ allow: unique }));
|
|
1064
|
+
}
|
|
1065
|
+
function deliverAgentMessage(agent, message, mode) {
|
|
1066
|
+
const deliver = agent[mode];
|
|
1067
|
+
if (typeof deliver !== "function") throw new Error(`pi2dsh: active DSH agent has no ${mode}() delivery method`);
|
|
1068
|
+
deliver.call(agent, message);
|
|
1069
|
+
}
|
|
1070
|
+
async function sendPiMessage(ctx, state, content, mode) {
|
|
1071
|
+
const agent = requireAgent(state, mode === "inject" ? "sendMessage" : "sendUserMessage");
|
|
1072
|
+
const blocks = await piToDshContent(ctx, typeof content === "string" ? [{
|
|
1073
|
+
type: "text",
|
|
1074
|
+
text: content
|
|
1075
|
+
}] : content);
|
|
1076
|
+
deliverAgentMessage(agent, createUserMessage({
|
|
1077
|
+
content: blocks,
|
|
1078
|
+
source: {
|
|
1079
|
+
kind: "plugin",
|
|
1080
|
+
plugin: state.messageSource
|
|
1081
|
+
}
|
|
1082
|
+
}), mode);
|
|
1083
|
+
}
|
|
1084
|
+
function combineExecSignal(options) {
|
|
1085
|
+
const controller = new AbortController();
|
|
1086
|
+
let killed = false;
|
|
1087
|
+
let timer;
|
|
1088
|
+
const abort = (reason) => {
|
|
1089
|
+
if (controller.signal.aborted) return;
|
|
1090
|
+
killed = true;
|
|
1091
|
+
controller.abort(reason);
|
|
1092
|
+
};
|
|
1093
|
+
const onAbort = () => abort(options.signal?.reason ?? /* @__PURE__ */ new Error("Pi exec aborted"));
|
|
1094
|
+
if (options.signal?.aborted) onAbort();
|
|
1095
|
+
else options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1096
|
+
if (typeof options.timeout === "number" && Number.isFinite(options.timeout) && options.timeout > 0) timer = setTimeout(() => abort(/* @__PURE__ */ new Error(`Pi exec timed out after ${options.timeout}ms`)), options.timeout);
|
|
1097
|
+
return {
|
|
1098
|
+
signal: controller.signal,
|
|
1099
|
+
killed: () => killed,
|
|
1100
|
+
cleanup() {
|
|
1101
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1102
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
async function executePiCommand(service, cwd, command, args, options) {
|
|
1107
|
+
const operation = combineExecSignal(options);
|
|
1108
|
+
try {
|
|
1109
|
+
if (typeof command !== "string" || command.length === 0) throw new TypeError("Pi exec command must be a non-empty string");
|
|
1110
|
+
if (!Array.isArray(args) || args.some((value) => typeof value !== "string")) throw new TypeError("Pi exec args must be strings");
|
|
1111
|
+
const executable = await service.resolveExecutable(command, void 0, operation.signal);
|
|
1112
|
+
const collect = { maxBytes: 67108864 };
|
|
1113
|
+
const handle = service.spawn({
|
|
1114
|
+
argv: [executable, ...args],
|
|
1115
|
+
cwd: options.cwd ?? cwd,
|
|
1116
|
+
stdio: {
|
|
1117
|
+
stdin: "ignore",
|
|
1118
|
+
stdout: collect,
|
|
1119
|
+
stderr: collect
|
|
1120
|
+
},
|
|
1121
|
+
graceMs: 5e3,
|
|
1122
|
+
signal: operation.signal
|
|
1123
|
+
});
|
|
1124
|
+
const outcome = await handle.done;
|
|
1125
|
+
const stdout = handle.collected.stdout?.readFrom(0);
|
|
1126
|
+
const stderr = handle.collected.stderr?.readFrom(0);
|
|
1127
|
+
const truncation = [stdout?.lossy ? "stdout" : "", stderr?.lossy ? "stderr" : ""].filter(Boolean);
|
|
1128
|
+
return {
|
|
1129
|
+
stdout: stdout?.text ?? "",
|
|
1130
|
+
stderr: `${stderr?.text ?? ""}${truncation.length === 0 ? "" : `\n[pi2dsh: ${truncation.join(" and ")} exceeded the 64 MiB compatibility limit]`}`,
|
|
1131
|
+
code: outcome.exitCode ?? 0,
|
|
1132
|
+
killed: operation.killed()
|
|
1133
|
+
};
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
return {
|
|
1136
|
+
stdout: "",
|
|
1137
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
1138
|
+
code: operation.signal.aborted ? 0 : 1,
|
|
1139
|
+
killed: operation.killed()
|
|
1140
|
+
};
|
|
1141
|
+
} finally {
|
|
1142
|
+
operation.cleanup();
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
function dshCommandName(ctx, piName) {
|
|
1146
|
+
const normalized = piName.toLowerCase().replace(/[^a-z0-9_-]+/gu, "-").replace(/^[^a-z]+/u, "");
|
|
1147
|
+
const name = normalized.length > 0 ? normalized : "pi-command";
|
|
1148
|
+
if (name !== piName) logger(ctx).warn(`[pi2dsh] Pi command /${piName} registered as /${name} to satisfy DSH command naming`);
|
|
1149
|
+
return name;
|
|
1150
|
+
}
|
|
1151
|
+
function registerCommand(ctx, state, command) {
|
|
1152
|
+
if (state.commands.has(command.name)) throw new Error(`Pi command ${JSON.stringify(command.name)} is already registered`);
|
|
1153
|
+
state.commands.set(command.name, command);
|
|
1154
|
+
const commands = ctx.get("commands");
|
|
1155
|
+
if (commands === void 0) {
|
|
1156
|
+
logger(ctx).warn(`[pi2dsh] command /${command.name} was not registered because this DSH composition has no ctx.commands`);
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
commands.register({
|
|
1160
|
+
name: dshCommandName(ctx, command.name),
|
|
1161
|
+
description: command.description || `Migrated Pi command /${command.name}`,
|
|
1162
|
+
...command.argumentHint !== void 0 ? { input: { hint: command.argumentHint } } : {},
|
|
1163
|
+
async handler(invocation) {
|
|
1164
|
+
const agent = invocation.agent;
|
|
1165
|
+
const commandContext = contextFor(ctx, state, agent, invocation.signal, true);
|
|
1166
|
+
await state.agentScope.run(agent, () => command.handler(String(invocation.rawInput ?? "").trimStart(), commandContext));
|
|
1167
|
+
const notices = commandContext.__notices;
|
|
1168
|
+
return {
|
|
1169
|
+
kind: "success",
|
|
1170
|
+
...notices.length > 0 ? { text: notices.join("\n") } : {}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
function requireSession(state, operation) {
|
|
1176
|
+
const session = agentSession(currentAgent(state));
|
|
1177
|
+
if (session === void 0) throw new Error(`pi2dsh: ${operation} requires one active DSH agent with a durable session`);
|
|
1178
|
+
return session;
|
|
1179
|
+
}
|
|
1180
|
+
function createPiApi(ctx, state) {
|
|
1181
|
+
return {
|
|
1182
|
+
on(event, handler) {
|
|
1183
|
+
const list = state.handlers.get(event) ?? [];
|
|
1184
|
+
list.push(handler);
|
|
1185
|
+
state.handlers.set(event, list);
|
|
1186
|
+
},
|
|
1187
|
+
registerTool: (tool) => registerTool(ctx, state, tool),
|
|
1188
|
+
unregisterTool: (name) => unregisterTool(state, name),
|
|
1189
|
+
registerCommand(name, options) {
|
|
1190
|
+
registerCommand(ctx, state, {
|
|
1191
|
+
name,
|
|
1192
|
+
description: typeof options.description === "string" ? options.description : `Migrated Pi command /${name}`,
|
|
1193
|
+
...typeof options.argumentHint === "string" ? { argumentHint: options.argumentHint } : {},
|
|
1194
|
+
handler: options.handler
|
|
1195
|
+
});
|
|
1196
|
+
},
|
|
1197
|
+
registerShortcut(shortcut, options) {
|
|
1198
|
+
state.shortcuts.set(shortcut, options);
|
|
1199
|
+
},
|
|
1200
|
+
registerFlag(name, options) {
|
|
1201
|
+
state.flags.set(name, options.default);
|
|
1202
|
+
logger(ctx).warn(`[pi2dsh] Pi flag --${name} uses its default only; DSH CLI registration is unsupported`);
|
|
1203
|
+
},
|
|
1204
|
+
getFlag: (name) => state.flags.get(name),
|
|
1205
|
+
registerProvider(providerOrName, config) {
|
|
1206
|
+
const name = typeof providerOrName === "string" ? providerOrName : String(providerOrName?.name ?? "unnamed");
|
|
1207
|
+
const value = typeof providerOrName === "string" ? config ?? {} : providerOrName;
|
|
1208
|
+
state.providers.set(name, value);
|
|
1209
|
+
logger(ctx).info(`[pi2dsh] recorded Pi provider ${JSON.stringify(name)}; model calls stay on DSH llm adapters`);
|
|
1210
|
+
},
|
|
1211
|
+
unregisterProvider(name) {
|
|
1212
|
+
state.providers.delete(name);
|
|
1213
|
+
},
|
|
1214
|
+
registerMessageRenderer(customType, renderer) {
|
|
1215
|
+
state.messageRenderers.set(customType, renderer);
|
|
1216
|
+
},
|
|
1217
|
+
registerEntryRenderer(customType, renderer) {
|
|
1218
|
+
state.entryRenderers.set(customType, renderer);
|
|
1219
|
+
},
|
|
1220
|
+
registerMarkdownTransformer(transformer) {
|
|
1221
|
+
state.markdownTransformer = transformer;
|
|
1222
|
+
},
|
|
1223
|
+
sendMessage(message, options = {}) {
|
|
1224
|
+
requireAgent(state, "sendMessage");
|
|
1225
|
+
const mode = options.deliverAs === "steer" ? "steer" : options.deliverAs === "followUp" || options.deliverAs === "nextTurn" || options.triggerTurn === true ? "followup" : "inject";
|
|
1226
|
+
return sendPiMessage(ctx, state, message.content, mode);
|
|
1227
|
+
},
|
|
1228
|
+
sendUserMessage(content, options = {}) {
|
|
1229
|
+
requireAgent(state, "sendUserMessage");
|
|
1230
|
+
return sendPiMessage(ctx, state, content, options.deliverAs === "steer" ? "steer" : "followup");
|
|
1231
|
+
},
|
|
1232
|
+
appendEntry(customType, data) {
|
|
1233
|
+
const session = requireSession(state, "appendEntry");
|
|
1234
|
+
state.bridge.appendCustomEntry(session.id, customType, data);
|
|
1235
|
+
},
|
|
1236
|
+
setSessionName(name) {
|
|
1237
|
+
const session = requireSession(state, "setSessionName");
|
|
1238
|
+
state.bridge.setName(session.id, String(name));
|
|
1239
|
+
dispatch(state, "session_info_changed", {
|
|
1240
|
+
type: "session_info_changed",
|
|
1241
|
+
name: state.bridge.getName(session.id)
|
|
1242
|
+
}, contextFor(ctx, state, currentAgent(state), void 0)).catch((error) => logger(ctx).warn(`[pi2dsh] session_info_changed handler failed: ${String(error)}`));
|
|
1243
|
+
},
|
|
1244
|
+
getSessionName() {
|
|
1245
|
+
const session = agentSession(currentAgent(state));
|
|
1246
|
+
return session === void 0 ? void 0 : state.bridge.getName(session.id);
|
|
1247
|
+
},
|
|
1248
|
+
setLabel(entryId, label) {
|
|
1249
|
+
const session = requireSession(state, "setLabel");
|
|
1250
|
+
state.bridge.appendLabel(session.id, String(entryId), label);
|
|
1251
|
+
},
|
|
1252
|
+
exec(command, args = [], options = {}) {
|
|
1253
|
+
const service = optionalService(ctx, "subprocess");
|
|
1254
|
+
if (service === void 0) unsupported("exec");
|
|
1255
|
+
return executePiCommand(service, cwdOf(currentAgent(state)), command, args, options);
|
|
1256
|
+
},
|
|
1257
|
+
getActiveTools: () => getActiveTools(ctx, state),
|
|
1258
|
+
getAllTools: () => toolRuntime(ctx, currentAgent(state)).schemas(currentAgent(state)).map((tool) => ({
|
|
1259
|
+
name: tool.name,
|
|
1260
|
+
description: tool.description ?? "",
|
|
1261
|
+
parameters: tool.parameters ?? {},
|
|
1262
|
+
source: state.tools.has(tool.name) ? "extension" : "builtin",
|
|
1263
|
+
sourceInfo: {
|
|
1264
|
+
path: "",
|
|
1265
|
+
source: state.tools.has(tool.name) ? "pi2dsh" : "dsh",
|
|
1266
|
+
scope: "session",
|
|
1267
|
+
origin: "runtime"
|
|
1268
|
+
}
|
|
1269
|
+
})),
|
|
1270
|
+
setActiveTools: (names) => setActiveTools(ctx, state, names),
|
|
1271
|
+
getCommands: () => [...state.commands.values()].map((command) => ({
|
|
1272
|
+
name: command.name,
|
|
1273
|
+
description: command.description,
|
|
1274
|
+
source: "extension",
|
|
1275
|
+
sourceInfo: {
|
|
1276
|
+
path: "",
|
|
1277
|
+
source: "pi2dsh",
|
|
1278
|
+
scope: "user",
|
|
1279
|
+
origin: "package"
|
|
1280
|
+
}
|
|
1281
|
+
})),
|
|
1282
|
+
async setModel(model) {
|
|
1283
|
+
const agent = currentAgent(state);
|
|
1284
|
+
if (agent === void 0) return false;
|
|
1285
|
+
const override = {
|
|
1286
|
+
...typeof model?.provider === "string" ? { provider: model.provider } : {},
|
|
1287
|
+
...typeof model?.id === "string" ? { model: model.id } : {}
|
|
1288
|
+
};
|
|
1289
|
+
if (override.model === void 0) return false;
|
|
1290
|
+
state.modelOverrides.set(agent, override);
|
|
1291
|
+
dispatch(state, "model_select", {
|
|
1292
|
+
type: "model_select",
|
|
1293
|
+
model,
|
|
1294
|
+
previousModel: state.modelOverrides.get(agent),
|
|
1295
|
+
source: "set"
|
|
1296
|
+
}, contextFor(ctx, state, agent, void 0)).catch((error) => logger(ctx).warn(`[pi2dsh] model_select handler failed: ${String(error)}`));
|
|
1297
|
+
return true;
|
|
1298
|
+
},
|
|
1299
|
+
getThinkingLevel: () => thinkingLevelOf(state, currentAgent(state)),
|
|
1300
|
+
setThinkingLevel(level) {
|
|
1301
|
+
const agent = currentAgent(state);
|
|
1302
|
+
const previousLevel = thinkingLevelOf(state, agent);
|
|
1303
|
+
if (agent === void 0) state.globalThinkingLevel = String(level);
|
|
1304
|
+
else state.thinkingLevels.set(agent, String(level));
|
|
1305
|
+
dispatch(state, "thinking_level_select", {
|
|
1306
|
+
type: "thinking_level_select",
|
|
1307
|
+
level: String(level),
|
|
1308
|
+
previousLevel
|
|
1309
|
+
}, contextFor(ctx, state, agent, void 0)).catch((error) => logger(ctx).warn(`[pi2dsh] thinking_level_select handler failed: ${String(error)}`));
|
|
1310
|
+
},
|
|
1311
|
+
events: {
|
|
1312
|
+
emit(channel, data) {
|
|
1313
|
+
state.eventBus.emit(channel, data);
|
|
1314
|
+
},
|
|
1315
|
+
on(channel, handler) {
|
|
1316
|
+
const safeHandler = (data) => {
|
|
1317
|
+
Promise.resolve(handler(data)).catch((error) => logger(ctx).warn(`[pi2dsh] package event ${channel} handler failed: ${String(error)}`));
|
|
1318
|
+
};
|
|
1319
|
+
state.eventBus.on(channel, safeHandler);
|
|
1320
|
+
return () => state.eventBus.off(channel, safeHandler);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
function splitArguments(input) {
|
|
1326
|
+
const values = [];
|
|
1327
|
+
let current = "";
|
|
1328
|
+
let quote;
|
|
1329
|
+
for (const character of input) if (quote !== void 0) {
|
|
1330
|
+
if (character === quote) quote = void 0;
|
|
1331
|
+
else current += character;
|
|
1332
|
+
} else if (character === "\"" || character === "'") quote = character;
|
|
1333
|
+
else if (/\s/u.test(character)) {
|
|
1334
|
+
if (current.length > 0) {
|
|
1335
|
+
values.push(current);
|
|
1336
|
+
current = "";
|
|
1337
|
+
}
|
|
1338
|
+
} else current += character;
|
|
1339
|
+
if (current.length > 0) values.push(current);
|
|
1340
|
+
return values;
|
|
1341
|
+
}
|
|
1342
|
+
function promptBody(text) {
|
|
1343
|
+
const normalized = text.replace(/\r\n?/gu, "\n");
|
|
1344
|
+
if (!normalized.startsWith("---")) return normalized;
|
|
1345
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
1346
|
+
if (endIndex === -1) return normalized;
|
|
1347
|
+
return normalized.slice(endIndex + 4).trim();
|
|
1348
|
+
}
|
|
1349
|
+
function expandPrompt(text, rawInput) {
|
|
1350
|
+
const args = splitArguments(rawInput);
|
|
1351
|
+
const all = args.join(" ");
|
|
1352
|
+
return promptBody(text).replace(/\$\{(\d+|ARGUMENTS|@):-([^}]*)\}|\$\{@:(\d+)(?::(\d+))?\}|\$(ARGUMENTS|@|\d+)/gu, (_match, defaultTarget, fallback, sliceStart, sliceLength, simple) => {
|
|
1353
|
+
if (defaultTarget !== void 0) return (defaultTarget === "@" || defaultTarget === "ARGUMENTS" ? all : args[Number(defaultTarget) - 1]) || fallback || "";
|
|
1354
|
+
if (sliceStart !== void 0) {
|
|
1355
|
+
const offset = Math.max(0, Number(sliceStart) - 1);
|
|
1356
|
+
return args.slice(offset, sliceLength === void 0 ? void 0 : offset + Number(sliceLength)).join(" ");
|
|
1357
|
+
}
|
|
1358
|
+
if (simple === "@" || simple === "ARGUMENTS") return all;
|
|
1359
|
+
return args[Number(simple) - 1] ?? "";
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
async function registerPromptCommands(ctx, state, rootDir, manifest) {
|
|
1363
|
+
for (const prompt of manifest.prompts) {
|
|
1364
|
+
const text = await readFile(join(rootDir, prompt.path), "utf8");
|
|
1365
|
+
registerCommand(ctx, state, {
|
|
1366
|
+
name: prompt.name,
|
|
1367
|
+
description: prompt.description,
|
|
1368
|
+
...prompt.argumentHint !== void 0 ? { argumentHint: prompt.argumentHint } : {},
|
|
1369
|
+
handler(rawInput, commandContext) {
|
|
1370
|
+
const invocationAgent = commandContext.__agent ?? [...state.activeAgents][0];
|
|
1371
|
+
if (invocationAgent === void 0 || typeof invocationAgent.steer !== "function") throw new Error(`pi2dsh: /${prompt.name} requires a live DSH agent`);
|
|
1372
|
+
invocationAgent.steer(createUserMessage({
|
|
1373
|
+
content: [{
|
|
1374
|
+
type: "text",
|
|
1375
|
+
text: expandPrompt(text, rawInput)
|
|
1376
|
+
}],
|
|
1377
|
+
source: {
|
|
1378
|
+
kind: "plugin",
|
|
1379
|
+
plugin: `pi2dsh:${manifest.package.name}`,
|
|
1380
|
+
form: "relay"
|
|
1381
|
+
}
|
|
1382
|
+
}));
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
async function loadExtensions(rootDir, manifest, api, onExtensionError) {
|
|
1388
|
+
const resolveShim = async (name) => {
|
|
1389
|
+
const compiled = fileURLToPath(new URL(`./compat/${name}.mjs`, import.meta.url));
|
|
1390
|
+
try {
|
|
1391
|
+
await access(compiled);
|
|
1392
|
+
return compiled;
|
|
1393
|
+
} catch {
|
|
1394
|
+
return fileURLToPath(new URL(`./compat/${name}.ts`, import.meta.url));
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
const [codingAgentShim, tuiShim, aiShim] = await Promise.all([
|
|
1398
|
+
resolveShim("pi-coding-agent"),
|
|
1399
|
+
resolveShim("pi-tui"),
|
|
1400
|
+
resolveShim("pi-ai")
|
|
1401
|
+
]);
|
|
1402
|
+
const aliases = {};
|
|
1403
|
+
for (const family of ["@earendil-works", "@mariozechner"]) {
|
|
1404
|
+
aliases[`${family}/pi-coding-agent`] = codingAgentShim;
|
|
1405
|
+
aliases[`${family}/pi-tui`] = tuiShim;
|
|
1406
|
+
aliases[`${family}/pi-ai`] = aiShim;
|
|
1407
|
+
aliases[`${family}/pi-ai/compat`] = aiShim;
|
|
1408
|
+
aliases[`${family}/pi-ai/oauth`] = aiShim;
|
|
1409
|
+
aliases[`${family}/pi-ai/providers/all`] = aiShim;
|
|
1410
|
+
}
|
|
1411
|
+
const require = createRequire(import.meta.url);
|
|
1412
|
+
for (const entry of [
|
|
1413
|
+
"typebox",
|
|
1414
|
+
"typebox/value",
|
|
1415
|
+
"typebox/compile"
|
|
1416
|
+
]) try {
|
|
1417
|
+
const resolved = require.resolve(entry);
|
|
1418
|
+
aliases[entry] = resolved;
|
|
1419
|
+
aliases[entry.replace("typebox", "@sinclair/typebox")] = resolved;
|
|
1420
|
+
} catch {}
|
|
1421
|
+
const jiti = createJiti(import.meta.url, {
|
|
1422
|
+
interopDefault: true,
|
|
1423
|
+
alias: aliases
|
|
1424
|
+
});
|
|
1425
|
+
const failures = [];
|
|
1426
|
+
let mounted = 0;
|
|
1427
|
+
for (const extension of manifest.extensions) try {
|
|
1428
|
+
const loaded = await jiti.import(join(rootDir, extension));
|
|
1429
|
+
const candidate = typeof loaded === "object" && loaded !== null && "default" in loaded ? loaded.default : loaded;
|
|
1430
|
+
if (typeof candidate !== "function") throw new TypeError(`Pi extension ${extension} has no default factory function`);
|
|
1431
|
+
await candidate(api);
|
|
1432
|
+
mounted += 1;
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
failures.push(`${extension}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1435
|
+
}
|
|
1436
|
+
if (failures.length > 0 && mounted === 0 && manifest.extensions.length > 0) throw new Error(`every Pi extension entry failed to load:\n${failures.map((item) => `- ${item}`).join("\n")}`);
|
|
1437
|
+
for (const failure of failures) onExtensionError?.(failure);
|
|
1438
|
+
}
|
|
1439
|
+
async function applyPiPackage(ctx, options) {
|
|
1440
|
+
if (options.manifest.schemaVersion !== 1) throw new Error(`unsupported pi2dsh manifest version ${String(options.manifest.schemaVersion)}`);
|
|
1441
|
+
const rootDir = fileURLToPath(options.rootUrl);
|
|
1442
|
+
const state = {
|
|
1443
|
+
handlers: /* @__PURE__ */ new Map(),
|
|
1444
|
+
tools: /* @__PURE__ */ new Map(),
|
|
1445
|
+
toolDisposers: /* @__PURE__ */ new Map(),
|
|
1446
|
+
toolRestrictions: /* @__PURE__ */ new WeakMap(),
|
|
1447
|
+
commands: /* @__PURE__ */ new Map(),
|
|
1448
|
+
flags: /* @__PURE__ */ new Map(),
|
|
1449
|
+
notifications: [],
|
|
1450
|
+
activeAgents: /* @__PURE__ */ new Set(),
|
|
1451
|
+
disposedAgents: /* @__PURE__ */ new WeakSet(),
|
|
1452
|
+
currentSystemPrompt: "",
|
|
1453
|
+
messageSource: `pi2dsh:${options.manifest.package.name}`,
|
|
1454
|
+
eventBus: new EventEmitter(),
|
|
1455
|
+
agentScope: new AsyncLocalStorage(),
|
|
1456
|
+
bridge: new PiSessionBridge(),
|
|
1457
|
+
theme: new Theme(),
|
|
1458
|
+
shortcuts: /* @__PURE__ */ new Map(),
|
|
1459
|
+
messageRenderers: /* @__PURE__ */ new Map(),
|
|
1460
|
+
entryRenderers: /* @__PURE__ */ new Map(),
|
|
1461
|
+
providers: /* @__PURE__ */ new Map(),
|
|
1462
|
+
autocompleteProviders: [],
|
|
1463
|
+
editorBuffers: /* @__PURE__ */ new WeakMap(),
|
|
1464
|
+
toolsExpanded: false,
|
|
1465
|
+
modelOverrides: /* @__PURE__ */ new WeakMap(),
|
|
1466
|
+
thinkingLevels: /* @__PURE__ */ new WeakMap(),
|
|
1467
|
+
globalThinkingLevel: "off",
|
|
1468
|
+
argMutations: /* @__PURE__ */ new WeakMap(),
|
|
1469
|
+
streamingTexts: /* @__PURE__ */ new Map(),
|
|
1470
|
+
lastLoggedModels: /* @__PURE__ */ new WeakMap()
|
|
1471
|
+
};
|
|
1472
|
+
subscribeLifecycle(ctx, state);
|
|
1473
|
+
subscribeInterceptors(ctx, state);
|
|
1474
|
+
if (options.manifest.skillDirs.length > 0) {
|
|
1475
|
+
if (ctx.get("skills") === void 0) logger(ctx).warn("[pi2dsh] migrated skills were not mounted because this DSH composition has no ctx.skills");
|
|
1476
|
+
else {
|
|
1477
|
+
const { apply: applyFilesystemSkills } = await import("@deepseek-ai/dsh-skill-filesystem");
|
|
1478
|
+
applyFilesystemSkills(ctx, {
|
|
1479
|
+
providerName: `pi2dsh-${options.manifest.package.name.replace(/[^a-zA-Z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "")}`,
|
|
1480
|
+
includeDefaultRoots: false,
|
|
1481
|
+
customSkillDirs: options.manifest.skillDirs.map((path) => join(rootDir, path)),
|
|
1482
|
+
watch: false
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
await registerPromptCommands(ctx, state, rootDir, options.manifest);
|
|
1487
|
+
await loadExtensions(rootDir, options.manifest, createPiApi(ctx, state), (failure) => logger(ctx).warn(`[pi2dsh] extension entry failed and was skipped (matching Pi's per-extension error isolation): ${failure}`));
|
|
1488
|
+
logger(ctx).info(`[pi2dsh] loaded ${options.manifest.package.name}: ${state.tools.size} tools, ${state.commands.size} commands, ${options.manifest.skillDirs.length} skill roots`);
|
|
1489
|
+
}
|
|
1490
|
+
const runtimeInternals = {
|
|
1491
|
+
expandPrompt,
|
|
1492
|
+
normalizeToolResult,
|
|
1493
|
+
splitArguments,
|
|
1494
|
+
textBlocks
|
|
1495
|
+
};
|
|
1496
|
+
//#endregion
|
|
1497
|
+
export { normalizeToolSchema as n, runtimeInternals as r, applyPiPackage as t };
|
|
1498
|
+
|
|
1499
|
+
//# sourceMappingURL=runtime-D84Hv_3m.mjs.map
|