dsh-packycode-api 1.2.6 → 1.2.8
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/CHANGELOG.md +15 -0
- package/README.en.md +24 -12
- package/README.md +24 -12
- package/lib/client.js +451 -130
- package/lib/index.js +348 -26
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdtempSync, op
|
|
|
8
8
|
import { lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
9
9
|
import { createServer } from "node:http";
|
|
10
10
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
11
|
-
import { credentialKey } from "@deepseek-ai/dsh-credentials";
|
|
12
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { credentialKey } from "@deepseek-ai/dsh-credentials";
|
|
13
13
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
14
14
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
15
15
|
import { lazyStream } from "@earendil-works/pi-ai/api/lazy";
|
|
@@ -1334,7 +1334,9 @@ function pickKeyForModel(modelId, active, usable, covers) {
|
|
|
1334
1334
|
*
|
|
1335
1335
|
* A session-title request is not a delegation: it runs on the small model,
|
|
1336
1336
|
* read-only and outside the workspace, with the title instructions appended,
|
|
1337
|
-
* so naming a session does not cost an Opus agent turn.
|
|
1337
|
+
* so naming a session does not cost an Opus agent turn. Under the CLI's own
|
|
1338
|
+
* system prompt those instructions are easily outweighed, so the prompt ends
|
|
1339
|
+
* by restating them and the answer is unwrapped before DSH reads it.
|
|
1338
1340
|
*
|
|
1339
1341
|
* @module dsh-packycode-api/claude-adapter
|
|
1340
1342
|
*/
|
|
@@ -1796,6 +1798,87 @@ function renderAuxiliaryPrompt(options) {
|
|
|
1796
1798
|
return (Array.isArray(options?.messages) ? options.messages : []).filter((message) => message !== null && typeof message === "object" && message.role !== "system").flatMap((message) => Array.isArray(message.content) ? message.content : []).filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n\n").trim();
|
|
1797
1799
|
}
|
|
1798
1800
|
/**
|
|
1801
|
+
* Languages named by their script. The caller frames a title request in
|
|
1802
|
+
* English, so text in one of these scripts is the user's own.
|
|
1803
|
+
*/
|
|
1804
|
+
const TITLE_SCRIPTS = Object.freeze([
|
|
1805
|
+
["Korean", /\p{Script=Hangul}/u],
|
|
1806
|
+
["Japanese", /[\p{Script=Hiragana}\p{Script=Katakana}]/u],
|
|
1807
|
+
["Chinese", /\p{Script=Han}/u]
|
|
1808
|
+
]);
|
|
1809
|
+
/**
|
|
1810
|
+
* The prompt of a CLI title request: the caller's framed input, verbatim,
|
|
1811
|
+
* then the two rules the small model breaks under the CLI's own system
|
|
1812
|
+
* prompt. There the caller's title instructions are only an appendix and its
|
|
1813
|
+
* input is English framing around a JSON array, so a short message came back
|
|
1814
|
+
* as JSON or in English (`{"title": "Session greeting check"}` for 在吗).
|
|
1815
|
+
* @param options - the assembled title request.
|
|
1816
|
+
* @returns the prompt text.
|
|
1817
|
+
*/
|
|
1818
|
+
function renderTitlePrompt(options) {
|
|
1819
|
+
const framed = renderAuxiliaryPrompt(options);
|
|
1820
|
+
const language = TITLE_SCRIPTS.find(([, script]) => script.test(framed))?.[0];
|
|
1821
|
+
return [
|
|
1822
|
+
framed,
|
|
1823
|
+
"",
|
|
1824
|
+
"Reply with the title alone, as plain text on one line: not JSON, not quoted, with no label such as \"Title:\".",
|
|
1825
|
+
language === void 0 ? "Write it in the language of the messages' text, not in the language of these instructions." : `Write it in ${language}, the language of the messages' text, not in the language of these instructions.`
|
|
1826
|
+
].join("\n");
|
|
1827
|
+
}
|
|
1828
|
+
/** Pairs a reply wraps a whole title in. Book-title marks belong to a title. */
|
|
1829
|
+
const TITLE_WRAPPERS = Object.freeze([
|
|
1830
|
+
["\"", "\""],
|
|
1831
|
+
["'", "'"],
|
|
1832
|
+
["“", "”"],
|
|
1833
|
+
["‘", "’"],
|
|
1834
|
+
["「", "」"],
|
|
1835
|
+
["『", "』"],
|
|
1836
|
+
["```", "```"],
|
|
1837
|
+
["`", "`"],
|
|
1838
|
+
["**", "**"],
|
|
1839
|
+
["«", "»"]
|
|
1840
|
+
]);
|
|
1841
|
+
const TITLE_LABEL = /^[*"'`]*(?:(?:session\s+)?title|(?:会话)?标题)[*"'`]*\s*[::]\**\s*/iu;
|
|
1842
|
+
/** A reply that names the field instead of filling it. */
|
|
1843
|
+
const TITLE_PLACEHOLDER = /^(?:(?:(?:session|conversation|chat)\s+)?title|(?:会话|对话|聊天)?标题)$/iu;
|
|
1844
|
+
/** The title inside a JSON reply: a string, or an object's `title` string. */
|
|
1845
|
+
function jsonTitle(text) {
|
|
1846
|
+
if (!/^[{"]/u.test(text)) return void 0;
|
|
1847
|
+
let value;
|
|
1848
|
+
try {
|
|
1849
|
+
value = JSON.parse(text);
|
|
1850
|
+
} catch {
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
if (typeof value === "string") return value.trim();
|
|
1854
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1855
|
+
const field = Object.keys(value).find((key) => key.toLowerCase() === "title");
|
|
1856
|
+
return field !== void 0 && typeof value[field] === "string" ? value[field].trim() : void 0;
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Recover the bare title from a reply that wrapped it: in a code fence, as a
|
|
1860
|
+
* JSON object's `title` or a JSON string, after a "Title:" label, or in
|
|
1861
|
+
* quotes. Anything else comes back trimmed.
|
|
1862
|
+
*
|
|
1863
|
+
* Some replies hold no usable title: several lines (the model wrote out its
|
|
1864
|
+
* thinking or an explanation, and which line is the title cannot be told), a
|
|
1865
|
+
* placeholder such as "Session title", or an empty wrapper. Those yield no
|
|
1866
|
+
* text, so DSH keeps its own fallback title, the first message's words.
|
|
1867
|
+
* @param text - a title request's whole text answer.
|
|
1868
|
+
* @returns the title text, or '' when the reply holds none.
|
|
1869
|
+
*/
|
|
1870
|
+
function unwrapTitle(text) {
|
|
1871
|
+
const raw = typeof text === "string" ? text.trim() : "";
|
|
1872
|
+
const body = /^```[^\n]*\n([\s\S]*?)\n?```$/u.exec(raw)?.[1].trim() ?? raw;
|
|
1873
|
+
const lines = body.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
1874
|
+
const json = jsonTitle(body) ?? jsonTitle(lines[0] ?? "");
|
|
1875
|
+
if (json === void 0 && lines.length > 1) return "";
|
|
1876
|
+
let title = (json ?? lines[0] ?? "").replace(TITLE_LABEL, "");
|
|
1877
|
+
const wrapper = TITLE_WRAPPERS.find(([open, close]) => title.length > open.length + close.length && title.startsWith(open) && title.endsWith(close));
|
|
1878
|
+
if (wrapper !== void 0) title = title.slice(wrapper[0].length, title.length - wrapper[1].length).trim();
|
|
1879
|
+
return TITLE_PLACEHOLDER.test(title) ? "" : title;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1799
1882
|
* Emit text and reasoning as well-formed harness blocks.
|
|
1800
1883
|
*
|
|
1801
1884
|
* The harness's stream grammar requires every delta to address an open block
|
|
@@ -1948,14 +2031,15 @@ function retryNote(event) {
|
|
|
1948
2031
|
}
|
|
1949
2032
|
/**
|
|
1950
2033
|
* How one request runs on the CLI: a title request on the small model with
|
|
1951
|
-
* the caller's instructions appended, anything else as a
|
|
2034
|
+
* the caller's instructions appended and restated, anything else as a
|
|
2035
|
+
* delegated turn.
|
|
1952
2036
|
*
|
|
1953
2037
|
* The CLI's own system prompt and tool set stay in place even for a title:
|
|
1954
2038
|
* they are part of what the gateway sees from the official client.
|
|
1955
2039
|
*/
|
|
1956
2040
|
async function cliCall(options, { titleModel, cwdOf, modeOf, attachmentsOf, imageLimits }) {
|
|
1957
2041
|
if (options?.purpose === "session-title") return {
|
|
1958
|
-
prompt:
|
|
2042
|
+
prompt: renderTitlePrompt(options),
|
|
1959
2043
|
model: titleModel,
|
|
1960
2044
|
mode: "read-only",
|
|
1961
2045
|
enableSearch: false,
|
|
@@ -2040,6 +2124,7 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
|
|
|
2040
2124
|
}
|
|
2041
2125
|
async function* cliStream(options) {
|
|
2042
2126
|
const quiet = options?.purpose === "session-title";
|
|
2127
|
+
let titleText = "";
|
|
2043
2128
|
const blocks = createBlockWriter();
|
|
2044
2129
|
if (!quiet) yield* blocks.note("▸ 启动 Claude Code CLI…\n");
|
|
2045
2130
|
let call;
|
|
@@ -2112,10 +2197,12 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
|
|
|
2112
2197
|
if (!quiet) yield* blocks.note(` ↻ ${retryNote(event)}\n`);
|
|
2113
2198
|
break;
|
|
2114
2199
|
case "block":
|
|
2200
|
+
if (quiet && event.kind === "text") titleText = "";
|
|
2115
2201
|
blocks.boundary(event.kind);
|
|
2116
2202
|
break;
|
|
2117
2203
|
case "text":
|
|
2118
|
-
|
|
2204
|
+
if (quiet) titleText += event.text;
|
|
2205
|
+
else yield* blocks.write("text", event.text);
|
|
2119
2206
|
break;
|
|
2120
2207
|
case "reasoning":
|
|
2121
2208
|
yield* blocks.write("reasoning", event.text);
|
|
@@ -2150,6 +2237,7 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
|
|
|
2150
2237
|
};
|
|
2151
2238
|
return;
|
|
2152
2239
|
}
|
|
2240
|
+
if (quiet) yield* blocks.write("text", unwrapTitle(titleText));
|
|
2153
2241
|
yield* blocks.close();
|
|
2154
2242
|
yield {
|
|
2155
2243
|
type: "usage",
|
|
@@ -5439,6 +5527,221 @@ function createClaudeCodeTool({ runner, modeCeiling, planActive }) {
|
|
|
5439
5527
|
});
|
|
5440
5528
|
}
|
|
5441
5529
|
//#endregion
|
|
5530
|
+
//#region src/diagnostics.js
|
|
5531
|
+
/**
|
|
5532
|
+
* A support summary of one DSH session, for reports such as "已达到输出 token
|
|
5533
|
+
* 上限" or failing requests: which route, reasoning effort and output ceiling
|
|
5534
|
+
* each request used, how every model step ended and what it consumed, and
|
|
5535
|
+
* which retries and failures happened.
|
|
5536
|
+
*
|
|
5537
|
+
* The summary carries numbers and identifiers only. Message text, reasoning,
|
|
5538
|
+
* tool arguments and results, titles, paths and key material never enter it;
|
|
5539
|
+
* error messages are redacted and shortened. A user can paste it into a bug
|
|
5540
|
+
* report as it is.
|
|
5541
|
+
*
|
|
5542
|
+
* @module dsh-packycode-api/diagnostics
|
|
5543
|
+
*/
|
|
5544
|
+
const DIAGNOSTICS_FORMAT = "packycode-diagnostics/1";
|
|
5545
|
+
const SESSION_LIST_LIMIT = 20;
|
|
5546
|
+
const SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
5547
|
+
const MESSAGE_CHARS = 160;
|
|
5548
|
+
/** How many of each record the summary keeps, newest last. */
|
|
5549
|
+
const DIAGNOSTICS_LIMITS = Object.freeze({
|
|
5550
|
+
requests: 8,
|
|
5551
|
+
truncated: 12,
|
|
5552
|
+
steps: 20,
|
|
5553
|
+
turns: 10,
|
|
5554
|
+
retries: 10,
|
|
5555
|
+
attempts: 10
|
|
5556
|
+
});
|
|
5557
|
+
const finite = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5558
|
+
/** Identifier-like strings only (routes, models, efforts, reason kinds, codes). */
|
|
5559
|
+
const word = (value) => typeof value === "string" && /^[\w.:/@[\]-]{1,80}$/u.test(value) ? value : void 0;
|
|
5560
|
+
const iso = (value) => finite(value) === void 0 ? void 0 : new Date(value).toISOString();
|
|
5561
|
+
const last = (list, count) => list.slice(Math.max(0, list.length - count));
|
|
5562
|
+
const compact = (object) => Object.fromEntries(Object.entries(object).filter(([, value]) => value !== void 0));
|
|
5563
|
+
const chunksOf = (stream) => Array.isArray(stream) ? stream.map((item) => item?.chunk).filter((chunk) => chunk !== null && typeof chunk === "object") : [];
|
|
5564
|
+
/** A provider or harness error message, redacted and shortened. */
|
|
5565
|
+
function safeMessage$1(text, secrets) {
|
|
5566
|
+
if (typeof text !== "string" || text === "") return void 0;
|
|
5567
|
+
const safe = redactSecrets(text, secrets).replace(/sk-[A-Za-z0-9_-]{8,}/gu, "sk-[redacted]").replace(/Bearer\s+[A-Za-z0-9._~+/=-]{8,}/giu, "Bearer [redacted]").replace(/\s+/gu, " ").trim();
|
|
5568
|
+
return safe.length > MESSAGE_CHARS ? `${safe.slice(0, MESSAGE_CHARS)}…` : safe;
|
|
5569
|
+
}
|
|
5570
|
+
function failureOf(reason, secrets) {
|
|
5571
|
+
const failure = reason?.failure ?? reason?.error;
|
|
5572
|
+
return failure === void 0 ? {} : {
|
|
5573
|
+
code: word(failure.code),
|
|
5574
|
+
message: safeMessage$1(failure.message, secrets)
|
|
5575
|
+
};
|
|
5576
|
+
}
|
|
5577
|
+
/**
|
|
5578
|
+
* Summarize one session's events.
|
|
5579
|
+
* @param events - the session's events in log order.
|
|
5580
|
+
* @param options - values to redact from error messages.
|
|
5581
|
+
* @returns the numbers-only summary body.
|
|
5582
|
+
*/
|
|
5583
|
+
function summarizeSessionEvents(events, { secrets = [] } = {}) {
|
|
5584
|
+
const requests = [];
|
|
5585
|
+
const steps = [];
|
|
5586
|
+
const turnEnds = [];
|
|
5587
|
+
const retries = [];
|
|
5588
|
+
const attempts = [];
|
|
5589
|
+
let turns = 0;
|
|
5590
|
+
let compactions = 0;
|
|
5591
|
+
for (const event of events ?? []) {
|
|
5592
|
+
const type = event?.type;
|
|
5593
|
+
const data = event?.data ?? {};
|
|
5594
|
+
if (typeof type !== "string") continue;
|
|
5595
|
+
if (/compact/u.test(type)) compactions += 1;
|
|
5596
|
+
if (type === "turn/start") turns += 1;
|
|
5597
|
+
else if (type === "request/header") {
|
|
5598
|
+
const config = data.header?.config ?? {};
|
|
5599
|
+
requests.push(compact({
|
|
5600
|
+
time: iso(event.time),
|
|
5601
|
+
reason: word(data.reason),
|
|
5602
|
+
provider: word(config.provider),
|
|
5603
|
+
model: word(config.model),
|
|
5604
|
+
reasoningEffort: word(config.reasoningEffort),
|
|
5605
|
+
maxTokens: finite(config.maxTokens),
|
|
5606
|
+
maxTokensFromAdapter: data.header?.adapterDefaults?.maxTokens === true ? true : void 0
|
|
5607
|
+
}));
|
|
5608
|
+
} else if (type === "assistant/message") {
|
|
5609
|
+
const chunks = chunksOf(data.stream);
|
|
5610
|
+
const usage = data.usage ?? chunks.find((chunk) => chunk.type === "usage")?.usage ?? {};
|
|
5611
|
+
const input = finite(usage.inputTokens);
|
|
5612
|
+
const cacheRead = finite(usage.cacheReadTokens);
|
|
5613
|
+
steps.push(compact({
|
|
5614
|
+
turn: finite(data.turn),
|
|
5615
|
+
step: finite(data.step),
|
|
5616
|
+
time: iso(event.time),
|
|
5617
|
+
finish: word(chunks.find((chunk) => chunk.type === "finish")?.reason?.kind) ?? "none",
|
|
5618
|
+
input,
|
|
5619
|
+
cacheRead,
|
|
5620
|
+
cacheWrite: finite(usage.cacheWriteTokens),
|
|
5621
|
+
output: finite(usage.outputTokens),
|
|
5622
|
+
context: input === void 0 && cacheRead === void 0 ? void 0 : (input ?? 0) + (cacheRead ?? 0)
|
|
5623
|
+
}));
|
|
5624
|
+
} else if (type === "assistant/attempt") {
|
|
5625
|
+
const reason = chunksOf(data.stream).find((chunk) => chunk.type === "finish")?.reason;
|
|
5626
|
+
attempts.push(compact({
|
|
5627
|
+
turn: finite(data.turn),
|
|
5628
|
+
step: finite(data.step),
|
|
5629
|
+
time: iso(event.time),
|
|
5630
|
+
finish: word(reason?.kind),
|
|
5631
|
+
...failureOf(reason, secrets)
|
|
5632
|
+
}));
|
|
5633
|
+
} else if (type === "llm/retry") retries.push(compact({
|
|
5634
|
+
turn: finite(data.turn),
|
|
5635
|
+
step: finite(data.step),
|
|
5636
|
+
time: iso(event.time),
|
|
5637
|
+
provider: word(data.provider),
|
|
5638
|
+
retry: finite(data.retry),
|
|
5639
|
+
maxRetries: finite(data.maxRetries),
|
|
5640
|
+
...failureOf({ failure: data.failure }, secrets)
|
|
5641
|
+
}));
|
|
5642
|
+
else if (type === "turn/end") turnEnds.push(compact({
|
|
5643
|
+
turn: finite(data.turn),
|
|
5644
|
+
time: iso(event.time),
|
|
5645
|
+
kind: word(data.reason?.kind),
|
|
5646
|
+
...failureOf(data.reason, secrets)
|
|
5647
|
+
}));
|
|
5648
|
+
}
|
|
5649
|
+
const truncated = steps.filter((step) => step.finish === "max-tokens");
|
|
5650
|
+
return {
|
|
5651
|
+
totals: {
|
|
5652
|
+
turns,
|
|
5653
|
+
steps: steps.length,
|
|
5654
|
+
maxTokenSteps: truncated.length,
|
|
5655
|
+
retries: retries.length,
|
|
5656
|
+
failedAttempts: attempts.length,
|
|
5657
|
+
compactionEvents: compactions
|
|
5658
|
+
},
|
|
5659
|
+
requestSettings: last(requests, DIAGNOSTICS_LIMITS.requests),
|
|
5660
|
+
maxTokenSteps: last(truncated, DIAGNOSTICS_LIMITS.truncated),
|
|
5661
|
+
recentSteps: last(steps, DIAGNOSTICS_LIMITS.steps),
|
|
5662
|
+
turnEnds: last(turnEnds, DIAGNOSTICS_LIMITS.turns),
|
|
5663
|
+
retries: last(retries, DIAGNOSTICS_LIMITS.retries),
|
|
5664
|
+
failedAttempts: last(attempts, DIAGNOSTICS_LIMITS.attempts)
|
|
5665
|
+
};
|
|
5666
|
+
}
|
|
5667
|
+
/**
|
|
5668
|
+
* The diagnostics service behind the settings card.
|
|
5669
|
+
* @param options - DSH's session query service (read lazily), the session
|
|
5670
|
+
* event reader the image originals already use, the key ring and group
|
|
5671
|
+
* reader, the stored secrets to redact, and this package's manifest URL.
|
|
5672
|
+
*/
|
|
5673
|
+
function createDiagnostics({ sessionQuery, readEvents, ring, groups, secrets = async () => [], ownPackageJsonUrl }) {
|
|
5674
|
+
const query = () => {
|
|
5675
|
+
const service = sessionQuery?.();
|
|
5676
|
+
if (typeof service?.listSessions !== "function") throw new Error("PackyCode diagnostics need DSH session history, which this runtime does not provide");
|
|
5677
|
+
return service;
|
|
5678
|
+
};
|
|
5679
|
+
const version = async () => {
|
|
5680
|
+
try {
|
|
5681
|
+
return word(JSON.parse(await readFile(fileURLToPath(ownPackageJsonUrl), "utf8")).version);
|
|
5682
|
+
} catch {
|
|
5683
|
+
return;
|
|
5684
|
+
}
|
|
5685
|
+
};
|
|
5686
|
+
/** Stored conversation keys by position: state and groups, never labels or keys. */
|
|
5687
|
+
const keys = async () => {
|
|
5688
|
+
const listed = (await ring.list()).keys ?? [];
|
|
5689
|
+
let reading;
|
|
5690
|
+
try {
|
|
5691
|
+
reading = await groups?.read({ force: false });
|
|
5692
|
+
} catch {
|
|
5693
|
+
reading = void 0;
|
|
5694
|
+
}
|
|
5695
|
+
return listed.map((entry, index) => {
|
|
5696
|
+
const group = reading?.keys?.find((item) => item.id === entry.id);
|
|
5697
|
+
return compact({
|
|
5698
|
+
key: index + 1,
|
|
5699
|
+
active: entry.active === true,
|
|
5700
|
+
disabled: entry.disabled === true ? true : void 0,
|
|
5701
|
+
groups: Array.isArray(group?.groups) ? group.groups.map(word).filter(Boolean) : void 0,
|
|
5702
|
+
reaches: group?.coverage === void 0 ? void 0 : Object.keys(group.coverage).filter((model) => group.coverage[model] === true)
|
|
5703
|
+
});
|
|
5704
|
+
});
|
|
5705
|
+
};
|
|
5706
|
+
return Object.freeze({
|
|
5707
|
+
/** Recent top-level sessions, newest first, for the picker. */
|
|
5708
|
+
async list(signal) {
|
|
5709
|
+
return { sessions: (await query().listSessions(signal)).filter((record) => typeof record?.header?.id === "string" && !(record.header.delegationDepth > 0)).slice(0, SESSION_LIST_LIMIT).map((record) => compact({
|
|
5710
|
+
id: record.header.id,
|
|
5711
|
+
createdAt: iso(record.header.createdAt),
|
|
5712
|
+
live: record.live === true,
|
|
5713
|
+
folder: typeof record.header.cwd === "string" && record.header.cwd !== "" ? basename(record.header.cwd) : void 0
|
|
5714
|
+
})) };
|
|
5715
|
+
},
|
|
5716
|
+
/** The numbers-only summary of one session. */
|
|
5717
|
+
async read(payload, signal) {
|
|
5718
|
+
const sessionId = payload?.sessionId;
|
|
5719
|
+
if (typeof sessionId !== "string" || !SESSION_ID.test(sessionId)) throw new Error("PackyCode diagnostics need a session to read");
|
|
5720
|
+
query();
|
|
5721
|
+
let events;
|
|
5722
|
+
try {
|
|
5723
|
+
events = await readEvents(sessionId, signal);
|
|
5724
|
+
} catch {
|
|
5725
|
+
throw new Error("PackyCode could not read that session");
|
|
5726
|
+
}
|
|
5727
|
+
const record = (await query().listSessions(signal)).find((item) => item?.header?.id === sessionId);
|
|
5728
|
+
return {
|
|
5729
|
+
format: DIAGNOSTICS_FORMAT,
|
|
5730
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5731
|
+
plugin: compact({ version: await version() }),
|
|
5732
|
+
session: compact({
|
|
5733
|
+
id: sessionId,
|
|
5734
|
+
createdAt: iso(record?.header?.createdAt),
|
|
5735
|
+
live: record === void 0 ? void 0 : record.live === true,
|
|
5736
|
+
delegationDepth: finite(record?.header?.delegationDepth)
|
|
5737
|
+
}),
|
|
5738
|
+
keys: await keys(),
|
|
5739
|
+
...summarizeSessionEvents(events, { secrets: await secrets() })
|
|
5740
|
+
};
|
|
5741
|
+
}
|
|
5742
|
+
});
|
|
5743
|
+
}
|
|
5744
|
+
//#endregion
|
|
5442
5745
|
//#region src/preferences.js
|
|
5443
5746
|
/**
|
|
5444
5747
|
* Where the plugin's preferences are read from and written to, on both
|
|
@@ -5631,6 +5934,7 @@ function createImageController({ ring, balance, readPreference, writePreference,
|
|
|
5631
5934
|
async mutate(operation, payload = {}) {
|
|
5632
5935
|
const operations = {
|
|
5633
5936
|
add: () => ring.add(payload.key, payload.label),
|
|
5937
|
+
rename: () => ring.rename(String(payload.id ?? ""), payload.label),
|
|
5634
5938
|
remove: () => ring.remove(String(payload.id ?? "")),
|
|
5635
5939
|
select: () => ring.select(String(payload.id ?? "")),
|
|
5636
5940
|
enable: () => ring.enable(String(payload.id ?? ""))
|
|
@@ -6961,8 +7265,9 @@ function createRouteAdapter({ ring, keys, fallbackSessionId, resolveAttachments,
|
|
|
6961
7265
|
* Every reply carries only what this plugin owns: key labels and state, the
|
|
6962
7266
|
* balance and group projections the host built, host overrides, whether this
|
|
6963
7267
|
* device has the Claude Code CLI, the plugin version, and the web search
|
|
6964
|
-
* preference and model visibility,
|
|
6965
|
-
* chunks. Key material never crosses this
|
|
7268
|
+
* preference and model visibility, bounded session-authorized original PNG
|
|
7269
|
+
* chunks, or a numbers-only session diagnosis. Key material never crosses this
|
|
7270
|
+
* boundary.
|
|
6966
7271
|
*
|
|
6967
7272
|
* @module dsh-packycode-api/rpc
|
|
6968
7273
|
*/
|
|
@@ -6988,7 +7293,7 @@ const keyId = (payload) => String(payload?.id ?? "");
|
|
|
6988
7293
|
* web search controller (without one, its endpoints refuse).
|
|
6989
7294
|
* @returns a handler for `connection.rpc.handle(CHANNEL, handler)`.
|
|
6990
7295
|
*/
|
|
6991
|
-
function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCli, pluginManager, search, modelSelection, images, imageOriginals }) {
|
|
7296
|
+
function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCli, pluginManager, search, modelSelection, images, imageOriginals, diagnostics }) {
|
|
6992
7297
|
const need = (service, message) => {
|
|
6993
7298
|
if (service === void 0) throw new Error(message);
|
|
6994
7299
|
return service;
|
|
@@ -7000,6 +7305,7 @@ function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCl
|
|
|
7000
7305
|
const models = () => need(modelSelection, "PackyCode model selection is unavailable in this runtime");
|
|
7001
7306
|
const imageSupport = () => need(images, "PackyCode images are unavailable in this runtime");
|
|
7002
7307
|
const originals = () => need(imageOriginals, "PackyCode original images are unavailable in this runtime");
|
|
7308
|
+
const diagnosis = () => need(diagnostics, "PackyCode diagnostics are unavailable in this runtime");
|
|
7003
7309
|
const status = async () => ({
|
|
7004
7310
|
...await ring.list(),
|
|
7005
7311
|
hosts: hosts(),
|
|
@@ -7080,9 +7386,15 @@ function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claudeCl
|
|
|
7080
7386
|
key: payload?.key,
|
|
7081
7387
|
label: payload?.label
|
|
7082
7388
|
}),
|
|
7389
|
+
"images/keys/rename": (payload) => imageSupport().mutate("rename", {
|
|
7390
|
+
id: payload?.id,
|
|
7391
|
+
label: payload?.label
|
|
7392
|
+
}),
|
|
7083
7393
|
"images/keys/remove": (payload) => imageSupport().mutate("remove", { id: payload?.id }),
|
|
7084
7394
|
"images/keys/select": (payload) => imageSupport().mutate("select", { id: payload?.id }),
|
|
7085
|
-
"images/keys/enable": (payload) => imageSupport().mutate("enable", { id: payload?.id })
|
|
7395
|
+
"images/keys/enable": (payload) => imageSupport().mutate("enable", { id: payload?.id }),
|
|
7396
|
+
"diagnostics/sessions": (_payload, signal) => diagnosis().list(signal),
|
|
7397
|
+
"diagnostics/session": (payload, signal) => diagnosis().read(payload, signal)
|
|
7086
7398
|
};
|
|
7087
7399
|
return async (endpoint, payload, signal) => {
|
|
7088
7400
|
try {
|
|
@@ -8278,6 +8590,23 @@ function apply(ctx, config) {
|
|
|
8278
8590
|
search.register(webCtx.web, (callback, label) => scope.effect(callback, label));
|
|
8279
8591
|
});
|
|
8280
8592
|
keys.remember().catch(() => void 0);
|
|
8593
|
+
/** One session's events from DSH's session query service, without projections. */
|
|
8594
|
+
const readSessionEvents = async (sessionId, signal) => {
|
|
8595
|
+
const query = ctx.get?.("sessionQuery");
|
|
8596
|
+
if (typeof query?.observeSession === "function") {
|
|
8597
|
+
const observation = await query.observeSession(sessionId, {
|
|
8598
|
+
signal,
|
|
8599
|
+
projectionMode: "none"
|
|
8600
|
+
});
|
|
8601
|
+
try {
|
|
8602
|
+
return [...observation.events];
|
|
8603
|
+
} finally {
|
|
8604
|
+
observation[Symbol.dispose]();
|
|
8605
|
+
}
|
|
8606
|
+
}
|
|
8607
|
+
if (typeof query?.readSession === "function") return (await query.readSession(sessionId)).events;
|
|
8608
|
+
return [];
|
|
8609
|
+
};
|
|
8281
8610
|
const handler = createPackyCodeRpc({
|
|
8282
8611
|
ring,
|
|
8283
8612
|
balance,
|
|
@@ -8291,22 +8620,15 @@ function apply(ctx, config) {
|
|
|
8291
8620
|
images,
|
|
8292
8621
|
imageOriginals: createImageOriginalReader({
|
|
8293
8622
|
imageDirectory: join(dshHome, "packycode-api", "images"),
|
|
8294
|
-
getSessionEvents:
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
} finally {
|
|
8304
|
-
observation[Symbol.dispose]();
|
|
8305
|
-
}
|
|
8306
|
-
}
|
|
8307
|
-
if (typeof query?.readSession === "function") return (await query.readSession(sessionId)).events;
|
|
8308
|
-
return [];
|
|
8309
|
-
}
|
|
8623
|
+
getSessionEvents: readSessionEvents
|
|
8624
|
+
}),
|
|
8625
|
+
diagnostics: createDiagnostics({
|
|
8626
|
+
sessionQuery: () => ctx.get?.("sessionQuery"),
|
|
8627
|
+
readEvents: readSessionEvents,
|
|
8628
|
+
ring,
|
|
8629
|
+
groups,
|
|
8630
|
+
secrets: async () => (await Promise.all([ring.secrets(), imageRing.secrets()])).flat().map((entry) => entry.key),
|
|
8631
|
+
ownPackageJsonUrl: new URL("../package.json", import.meta.url)
|
|
8310
8632
|
})
|
|
8311
8633
|
});
|
|
8312
8634
|
/**
|
|
@@ -8342,4 +8664,4 @@ function apply(ctx, config) {
|
|
|
8342
8664
|
});
|
|
8343
8665
|
}
|
|
8344
8666
|
//#endregion
|
|
8345
|
-
export { AUTO_CLAUDE_MODE, CHANNEL, CLAUDE_CLI_MODES, CLAUDE_INSTALL_COMMAND, CLAUDE_MISSING_MESSAGE, CLAUDE_NPM_PACKAGE, CLAUDE_TOOL_INSTRUCTIONS, CLI_BACKED_MODEL_IDS, CLI_IMAGE_LIMITS, Config, DEFAULT_API_HOST, DEFAULT_APPEND_SYSTEM_PROMPT, DEFAULT_IMAGE_MODEL, DEFAULT_SEARCH_BACKEND, DEFAULT_STATUS_HOST, DISPLAY_NAME, IMAGE_MODELS, IMAGE_MODEL_IDS, IMAGE_QUALITIES, IMAGE_TIMEOUT_MS, IMAGE_TOOL_NAME, LEGACY_STATUS_HOSTS, MAX_CLI_IMAGE_CALLS, MAX_IMAGE_BYTES, MCP_IMAGE_SCHEMA, PACKYCODE_AUTO_SEARCH_PROVIDER_ID, PACKYCODE_MODELS, PACKYCODE_MODEL_IDS, PACKYCODE_SEARCH_PROVIDER_ID, PLUGIN_ID, PROVIDER, PackyCodeKeyRing, SEARCH_BACKENDS, SEARCH_BACKEND_MODELS, SEARCH_MODES, SETTINGS_NAMESPACE, TITLE_MODEL_ID, TITLE_MODEL_ROUTES, accountHostOrder, apply, assertApiKey, buildClaudeArgs, buildClaudeEnvironment, buildClaudeInput, buildDelegatedContent, buildSearchRequest, capClaudeMode, catalogCoverage, classifyClientPolicy, classifyGatewayFailure, claudeBinaryCandidates, claudeModeFor, claudeModeForSession, cliSearchInstructions, createBalanceReader, createClaudeCliAdapter, createClaudeCliManager, createClaudeCodeTool, createClaudeImageService, createClaudeRunner, createClaudeSearchBridge, createGroupReader, createImageController, createKeyChooser, createModelSelectionController, createPackyCodeAutoSearchProvider, createPackyCodeImageClient, createPackyCodeImageTool, createPackyCodeProfile, createPackyCodeProvider, createPackyCodeRpc, createPackyCodeSearchProvider, createProfileSearchComposition, createRouteAdapter, createSearchComposition, createSearchController, decodeImagePng, delegationInstructions, describeRunKey, explainCliClientPolicyFailure, explainCliGroupFailure, explainClientPolicyFailure, explainNpmFailure, findClaudeBinary, guardKeyRotation, inject, keyNote, locateClaudeBinary, locateNpm, name, normalizeFailureMessage, normalizeHost, npmCandidates, parseBillingSubscription, parseBillingUsage, parseClaudeResult, parseClaudeVersion, parseMessagesSearchResponse, parsePricing, parseResponsesSearchResponse, parseSelectedGroups, parseTokenUsage, pickKeyForModel, planModeForSession, planModeForToolExecution, pngDimensions, readClaudeStreamEvents, readHiddenModels, readImagePreference, readSearchChoice, renderAuxiliaryPrompt, renderDelegatedPrompt, resolveSpawnCommand, statusHostOrder, summarizeToolUse, validateImageOptions };
|
|
8667
|
+
export { AUTO_CLAUDE_MODE, CHANNEL, CLAUDE_CLI_MODES, CLAUDE_INSTALL_COMMAND, CLAUDE_MISSING_MESSAGE, CLAUDE_NPM_PACKAGE, CLAUDE_TOOL_INSTRUCTIONS, CLI_BACKED_MODEL_IDS, CLI_IMAGE_LIMITS, Config, DEFAULT_API_HOST, DEFAULT_APPEND_SYSTEM_PROMPT, DEFAULT_IMAGE_MODEL, DEFAULT_SEARCH_BACKEND, DEFAULT_STATUS_HOST, DIAGNOSTICS_FORMAT, DIAGNOSTICS_LIMITS, DISPLAY_NAME, IMAGE_MODELS, IMAGE_MODEL_IDS, IMAGE_QUALITIES, IMAGE_TIMEOUT_MS, IMAGE_TOOL_NAME, LEGACY_STATUS_HOSTS, MAX_CLI_IMAGE_CALLS, MAX_IMAGE_BYTES, MCP_IMAGE_SCHEMA, PACKYCODE_AUTO_SEARCH_PROVIDER_ID, PACKYCODE_MODELS, PACKYCODE_MODEL_IDS, PACKYCODE_SEARCH_PROVIDER_ID, PLUGIN_ID, PROVIDER, PackyCodeKeyRing, SEARCH_BACKENDS, SEARCH_BACKEND_MODELS, SEARCH_MODES, SETTINGS_NAMESPACE, TITLE_MODEL_ID, TITLE_MODEL_ROUTES, accountHostOrder, apply, assertApiKey, buildClaudeArgs, buildClaudeEnvironment, buildClaudeInput, buildDelegatedContent, buildSearchRequest, capClaudeMode, catalogCoverage, classifyClientPolicy, classifyGatewayFailure, claudeBinaryCandidates, claudeModeFor, claudeModeForSession, cliSearchInstructions, createBalanceReader, createClaudeCliAdapter, createClaudeCliManager, createClaudeCodeTool, createClaudeImageService, createClaudeRunner, createClaudeSearchBridge, createDiagnostics, createGroupReader, createImageController, createKeyChooser, createModelSelectionController, createPackyCodeAutoSearchProvider, createPackyCodeImageClient, createPackyCodeImageTool, createPackyCodeProfile, createPackyCodeProvider, createPackyCodeRpc, createPackyCodeSearchProvider, createProfileSearchComposition, createRouteAdapter, createSearchComposition, createSearchController, decodeImagePng, delegationInstructions, describeRunKey, explainCliClientPolicyFailure, explainCliGroupFailure, explainClientPolicyFailure, explainNpmFailure, findClaudeBinary, guardKeyRotation, inject, keyNote, locateClaudeBinary, locateNpm, name, normalizeFailureMessage, normalizeHost, npmCandidates, parseBillingSubscription, parseBillingUsage, parseClaudeResult, parseClaudeVersion, parseMessagesSearchResponse, parsePricing, parseResponsesSearchResponse, parseSelectedGroups, parseTokenUsage, pickKeyForModel, planModeForSession, planModeForToolExecution, pngDimensions, readClaudeStreamEvents, readHiddenModels, readImagePreference, readSearchChoice, renderAuxiliaryPrompt, renderDelegatedPrompt, renderTitlePrompt, resolveSpawnCommand, statusHostOrder, summarizeSessionEvents, summarizeToolUse, unwrapTitle, validateImageOptions };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-packycode-api",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"description": "Use PackyCode API keys in DeepSeek Harness with multi-protocol models, image generation and editing, separate key rings, and balance display",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|