@harmonyos-arkts/opencode-acp 0.0.1 → 0.0.3
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 +91 -0
- package/README.md +100 -38
- package/README.zh.md +41 -39
- package/dist/index.cjs +1420 -215
- package/dist/index.cjs.map +4 -4
- package/docs/codebase-overview.md +191 -0
- package/docs/mcp-extmethod-design.md +366 -0
- package/docs/provider-config-flow.md +312 -0
- package/docs/question-asked.md +35 -18
- package/docs/session-stats-to-vscode-design.md +217 -0
- package/docs/subagent-visibility.md +26 -25
- package/docs/tui-vs-acp-analysis.md +36 -36
- package/package.json +10 -4
package/dist/index.cjs
CHANGED
|
@@ -396,7 +396,7 @@ var require_parse = __commonJS({
|
|
|
396
396
|
var resolveCommand = require_resolveCommand();
|
|
397
397
|
var escape = require_escape();
|
|
398
398
|
var readShebang = require_readShebang();
|
|
399
|
-
var
|
|
399
|
+
var isWin2 = process.platform === "win32";
|
|
400
400
|
var isExecutableRegExp = /\.(?:com|exe)$/i;
|
|
401
401
|
var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
|
|
402
402
|
function detectShebang(parsed) {
|
|
@@ -410,7 +410,7 @@ var require_parse = __commonJS({
|
|
|
410
410
|
return parsed.file;
|
|
411
411
|
}
|
|
412
412
|
function parseNonShell(parsed) {
|
|
413
|
-
if (!
|
|
413
|
+
if (!isWin2) {
|
|
414
414
|
return parsed;
|
|
415
415
|
}
|
|
416
416
|
const commandFile = detectShebang(parsed);
|
|
@@ -454,7 +454,7 @@ var require_parse = __commonJS({
|
|
|
454
454
|
var require_enoent = __commonJS({
|
|
455
455
|
"node_modules/cross-spawn/lib/enoent.js"(exports2, module2) {
|
|
456
456
|
"use strict";
|
|
457
|
-
var
|
|
457
|
+
var isWin2 = process.platform === "win32";
|
|
458
458
|
function notFoundError(original, syscall) {
|
|
459
459
|
return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
|
|
460
460
|
code: "ENOENT",
|
|
@@ -465,7 +465,7 @@ var require_enoent = __commonJS({
|
|
|
465
465
|
});
|
|
466
466
|
}
|
|
467
467
|
function hookChildProcess(cp, parsed) {
|
|
468
|
-
if (!
|
|
468
|
+
if (!isWin2) {
|
|
469
469
|
return;
|
|
470
470
|
}
|
|
471
471
|
const originalEmit = cp.emit;
|
|
@@ -480,13 +480,13 @@ var require_enoent = __commonJS({
|
|
|
480
480
|
};
|
|
481
481
|
}
|
|
482
482
|
function verifyENOENT(status, parsed) {
|
|
483
|
-
if (
|
|
483
|
+
if (isWin2 && status === 1 && !parsed.file) {
|
|
484
484
|
return notFoundError(parsed.original, "spawn");
|
|
485
485
|
}
|
|
486
486
|
return null;
|
|
487
487
|
}
|
|
488
488
|
function verifyENOENTSync(status, parsed) {
|
|
489
|
-
if (
|
|
489
|
+
if (isWin2 && status === 1 && !parsed.file) {
|
|
490
490
|
return notFoundError(parsed.original, "spawnSync");
|
|
491
491
|
}
|
|
492
492
|
return null;
|
|
@@ -19586,6 +19586,9 @@ function createOpencodeClient(config2) {
|
|
|
19586
19586
|
// node_modules/@opencode-ai/sdk/dist/v2/server.js
|
|
19587
19587
|
var import_cross_spawn = __toESM(require_cross_spawn(), 1);
|
|
19588
19588
|
|
|
19589
|
+
// src/agent.ts
|
|
19590
|
+
var import_url2 = require("url");
|
|
19591
|
+
|
|
19589
19592
|
// src/session-manager.ts
|
|
19590
19593
|
var SessionManager = class {
|
|
19591
19594
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -19613,10 +19616,7 @@ var SessionManager = class {
|
|
|
19613
19616
|
* Called by agent.newSession() / agent.loadSession().
|
|
19614
19617
|
*/
|
|
19615
19618
|
async create(cwd, mcpServers, model) {
|
|
19616
|
-
const response = await this.sdk.session.create(
|
|
19617
|
-
{ directory: cwd },
|
|
19618
|
-
{ throwOnError: true }
|
|
19619
|
-
);
|
|
19619
|
+
const response = await this.sdk.session.create({ directory: cwd }, { throwOnError: true });
|
|
19620
19620
|
const session = response.data;
|
|
19621
19621
|
const state = {
|
|
19622
19622
|
id: session.id,
|
|
@@ -19633,10 +19633,7 @@ var SessionManager = class {
|
|
|
19633
19633
|
* Called by agent.loadSession() / agent.unstable_resumeSession().
|
|
19634
19634
|
*/
|
|
19635
19635
|
async load(sessionId, cwd, mcpServers, model) {
|
|
19636
|
-
await this.sdk.session.get(
|
|
19637
|
-
{ sessionID: sessionId, directory: cwd },
|
|
19638
|
-
{ throwOnError: true }
|
|
19639
|
-
);
|
|
19636
|
+
await this.sdk.session.get({ sessionID: sessionId, directory: cwd }, { throwOnError: true });
|
|
19640
19637
|
const state = {
|
|
19641
19638
|
id: sessionId,
|
|
19642
19639
|
cwd,
|
|
@@ -19724,7 +19721,562 @@ var SessionManager = class {
|
|
|
19724
19721
|
}
|
|
19725
19722
|
};
|
|
19726
19723
|
|
|
19724
|
+
// node_modules/diff/libesm/util/string.js
|
|
19725
|
+
function hasOnlyWinLineEndings(string4) {
|
|
19726
|
+
return string4.includes("\r\n") && !string4.startsWith("\n") && !string4.match(/[^\r]\n/);
|
|
19727
|
+
}
|
|
19728
|
+
function hasOnlyUnixLineEndings(string4) {
|
|
19729
|
+
return !string4.includes("\r\n") && string4.includes("\n");
|
|
19730
|
+
}
|
|
19731
|
+
|
|
19732
|
+
// node_modules/diff/libesm/patch/line-endings.js
|
|
19733
|
+
function unixToWin(patch) {
|
|
19734
|
+
if (Array.isArray(patch)) {
|
|
19735
|
+
return patch.map((p) => unixToWin(p));
|
|
19736
|
+
}
|
|
19737
|
+
return Object.assign(Object.assign({}, patch), { hunks: patch.hunks.map((hunk) => Object.assign(Object.assign({}, hunk), { lines: hunk.lines.map((line, i) => {
|
|
19738
|
+
var _a2;
|
|
19739
|
+
return line.startsWith("\\") || line.endsWith("\r") || ((_a2 = hunk.lines[i + 1]) === null || _a2 === void 0 ? void 0 : _a2.startsWith("\\")) ? line : line + "\r";
|
|
19740
|
+
}) })) });
|
|
19741
|
+
}
|
|
19742
|
+
function winToUnix(patch) {
|
|
19743
|
+
if (Array.isArray(patch)) {
|
|
19744
|
+
return patch.map((p) => winToUnix(p));
|
|
19745
|
+
}
|
|
19746
|
+
return Object.assign(Object.assign({}, patch), { hunks: patch.hunks.map((hunk) => Object.assign(Object.assign({}, hunk), { lines: hunk.lines.map((line) => line.endsWith("\r") ? line.substring(0, line.length - 1) : line) })) });
|
|
19747
|
+
}
|
|
19748
|
+
function isUnix(patch) {
|
|
19749
|
+
if (!Array.isArray(patch)) {
|
|
19750
|
+
patch = [patch];
|
|
19751
|
+
}
|
|
19752
|
+
return !patch.some((index) => index.hunks.some((hunk) => hunk.lines.some((line) => !line.startsWith("\\") && line.endsWith("\r"))));
|
|
19753
|
+
}
|
|
19754
|
+
function isWin(patch) {
|
|
19755
|
+
if (!Array.isArray(patch)) {
|
|
19756
|
+
patch = [patch];
|
|
19757
|
+
}
|
|
19758
|
+
return patch.some((index) => index.hunks.some((hunk) => hunk.lines.some((line) => line.endsWith("\r")))) && patch.every((index) => index.hunks.every((hunk) => hunk.lines.every((line, i) => {
|
|
19759
|
+
var _a2;
|
|
19760
|
+
return line.startsWith("\\") || line.endsWith("\r") || ((_a2 = hunk.lines[i + 1]) === null || _a2 === void 0 ? void 0 : _a2.startsWith("\\"));
|
|
19761
|
+
})));
|
|
19762
|
+
}
|
|
19763
|
+
|
|
19764
|
+
// node_modules/diff/libesm/patch/parse.js
|
|
19765
|
+
function parsePatch(uniDiff) {
|
|
19766
|
+
const diffstr = uniDiff.split(/\n/), list = [];
|
|
19767
|
+
let i = 0;
|
|
19768
|
+
function isGitDiffHeader(line) {
|
|
19769
|
+
return /^diff --git /.test(line);
|
|
19770
|
+
}
|
|
19771
|
+
function isDiffHeader(line) {
|
|
19772
|
+
return isGitDiffHeader(line) || /^Index:\s/.test(line) || /^diff(?: -r \w+)+\s/.test(line);
|
|
19773
|
+
}
|
|
19774
|
+
function isFileHeader(line) {
|
|
19775
|
+
return /^(---|\+\+\+)\s/.test(line);
|
|
19776
|
+
}
|
|
19777
|
+
function isHunkHeader(line) {
|
|
19778
|
+
return /^@@\s/.test(line);
|
|
19779
|
+
}
|
|
19780
|
+
function parseIndex() {
|
|
19781
|
+
var _a2;
|
|
19782
|
+
const index = {};
|
|
19783
|
+
index.hunks = [];
|
|
19784
|
+
list.push(index);
|
|
19785
|
+
let seenDiffHeader = false;
|
|
19786
|
+
while (i < diffstr.length) {
|
|
19787
|
+
const line = diffstr[i];
|
|
19788
|
+
if (isFileHeader(line) || isHunkHeader(line)) {
|
|
19789
|
+
break;
|
|
19790
|
+
}
|
|
19791
|
+
if (isGitDiffHeader(line)) {
|
|
19792
|
+
if (seenDiffHeader) {
|
|
19793
|
+
return;
|
|
19794
|
+
}
|
|
19795
|
+
seenDiffHeader = true;
|
|
19796
|
+
index.isGit = true;
|
|
19797
|
+
const paths = parseGitDiffHeader(line);
|
|
19798
|
+
if (paths) {
|
|
19799
|
+
index.oldFileName = paths.oldFileName;
|
|
19800
|
+
index.newFileName = paths.newFileName;
|
|
19801
|
+
}
|
|
19802
|
+
i++;
|
|
19803
|
+
while (i < diffstr.length) {
|
|
19804
|
+
const extLine = diffstr[i];
|
|
19805
|
+
if (isFileHeader(extLine) || isHunkHeader(extLine) || isDiffHeader(extLine)) {
|
|
19806
|
+
break;
|
|
19807
|
+
}
|
|
19808
|
+
const renameFromMatch = /^rename from (.*)/.exec(extLine);
|
|
19809
|
+
if (renameFromMatch) {
|
|
19810
|
+
index.oldFileName = "a/" + unquoteIfQuoted(renameFromMatch[1]);
|
|
19811
|
+
index.isRename = true;
|
|
19812
|
+
}
|
|
19813
|
+
const renameToMatch = /^rename to (.*)/.exec(extLine);
|
|
19814
|
+
if (renameToMatch) {
|
|
19815
|
+
index.newFileName = "b/" + unquoteIfQuoted(renameToMatch[1]);
|
|
19816
|
+
index.isRename = true;
|
|
19817
|
+
}
|
|
19818
|
+
const copyFromMatch = /^copy from (.*)/.exec(extLine);
|
|
19819
|
+
if (copyFromMatch) {
|
|
19820
|
+
index.oldFileName = "a/" + unquoteIfQuoted(copyFromMatch[1]);
|
|
19821
|
+
index.isCopy = true;
|
|
19822
|
+
}
|
|
19823
|
+
const copyToMatch = /^copy to (.*)/.exec(extLine);
|
|
19824
|
+
if (copyToMatch) {
|
|
19825
|
+
index.newFileName = "b/" + unquoteIfQuoted(copyToMatch[1]);
|
|
19826
|
+
index.isCopy = true;
|
|
19827
|
+
}
|
|
19828
|
+
const newFileModeMatch = /^new file mode (\d+)/.exec(extLine);
|
|
19829
|
+
if (newFileModeMatch) {
|
|
19830
|
+
index.isCreate = true;
|
|
19831
|
+
index.newMode = newFileModeMatch[1];
|
|
19832
|
+
}
|
|
19833
|
+
const deletedFileModeMatch = /^deleted file mode (\d+)/.exec(extLine);
|
|
19834
|
+
if (deletedFileModeMatch) {
|
|
19835
|
+
index.isDelete = true;
|
|
19836
|
+
index.oldMode = deletedFileModeMatch[1];
|
|
19837
|
+
}
|
|
19838
|
+
const oldModeMatch = /^old mode (\d+)/.exec(extLine);
|
|
19839
|
+
if (oldModeMatch) {
|
|
19840
|
+
index.oldMode = oldModeMatch[1];
|
|
19841
|
+
}
|
|
19842
|
+
const newModeMatch = /^new mode (\d+)/.exec(extLine);
|
|
19843
|
+
if (newModeMatch) {
|
|
19844
|
+
index.newMode = newModeMatch[1];
|
|
19845
|
+
}
|
|
19846
|
+
if (/^Binary files /.test(extLine)) {
|
|
19847
|
+
index.isBinary = true;
|
|
19848
|
+
}
|
|
19849
|
+
i++;
|
|
19850
|
+
}
|
|
19851
|
+
continue;
|
|
19852
|
+
} else if (isDiffHeader(line)) {
|
|
19853
|
+
if (seenDiffHeader) {
|
|
19854
|
+
return;
|
|
19855
|
+
}
|
|
19856
|
+
seenDiffHeader = true;
|
|
19857
|
+
const headerMatch = /^(?:Index:|diff(?: -r \w+)+)\s+/.exec(line);
|
|
19858
|
+
if (headerMatch) {
|
|
19859
|
+
index.index = line.substring(headerMatch[0].length).trim();
|
|
19860
|
+
}
|
|
19861
|
+
}
|
|
19862
|
+
i++;
|
|
19863
|
+
}
|
|
19864
|
+
parseFileHeader(index);
|
|
19865
|
+
parseFileHeader(index);
|
|
19866
|
+
if (index.oldFileName === void 0 !== (index.newFileName === void 0)) {
|
|
19867
|
+
throw new Error("Missing " + (index.oldFileName !== void 0 ? '"+++ ..."' : '"--- ..."') + " file header for " + ((_a2 = index.oldFileName) !== null && _a2 !== void 0 ? _a2 : index.newFileName));
|
|
19868
|
+
}
|
|
19869
|
+
while (i < diffstr.length) {
|
|
19870
|
+
const line = diffstr[i];
|
|
19871
|
+
if (isDiffHeader(line) || isFileHeader(line) || /^===================================================================/.test(line)) {
|
|
19872
|
+
break;
|
|
19873
|
+
} else if (isHunkHeader(line)) {
|
|
19874
|
+
index.hunks.push(parseHunk());
|
|
19875
|
+
} else {
|
|
19876
|
+
i++;
|
|
19877
|
+
}
|
|
19878
|
+
}
|
|
19879
|
+
}
|
|
19880
|
+
function parseGitDiffHeader(line) {
|
|
19881
|
+
const rest = line.substring("diff --git ".length);
|
|
19882
|
+
if (rest.startsWith('"')) {
|
|
19883
|
+
const oldPath = parseQuotedFileName(rest);
|
|
19884
|
+
if (oldPath === null) {
|
|
19885
|
+
return null;
|
|
19886
|
+
}
|
|
19887
|
+
const afterOld = rest.substring(oldPath.rawLength + 1);
|
|
19888
|
+
let newFileName;
|
|
19889
|
+
if (afterOld.startsWith('"')) {
|
|
19890
|
+
const newPath = parseQuotedFileName(afterOld);
|
|
19891
|
+
if (newPath === null) {
|
|
19892
|
+
return null;
|
|
19893
|
+
}
|
|
19894
|
+
newFileName = newPath.fileName;
|
|
19895
|
+
} else {
|
|
19896
|
+
newFileName = afterOld;
|
|
19897
|
+
}
|
|
19898
|
+
return {
|
|
19899
|
+
oldFileName: oldPath.fileName,
|
|
19900
|
+
newFileName
|
|
19901
|
+
};
|
|
19902
|
+
}
|
|
19903
|
+
const quoteIdx = rest.indexOf('"');
|
|
19904
|
+
if (quoteIdx > 0) {
|
|
19905
|
+
const oldFileName = rest.substring(0, quoteIdx - 1);
|
|
19906
|
+
const newPath = parseQuotedFileName(rest.substring(quoteIdx));
|
|
19907
|
+
if (newPath === null) {
|
|
19908
|
+
return null;
|
|
19909
|
+
}
|
|
19910
|
+
return {
|
|
19911
|
+
oldFileName,
|
|
19912
|
+
newFileName: newPath.fileName
|
|
19913
|
+
};
|
|
19914
|
+
}
|
|
19915
|
+
if (rest.startsWith("a/")) {
|
|
19916
|
+
const splits = [];
|
|
19917
|
+
let idx = 0;
|
|
19918
|
+
while (true) {
|
|
19919
|
+
idx = rest.indexOf(" b/", idx + 1);
|
|
19920
|
+
if (idx === -1) {
|
|
19921
|
+
break;
|
|
19922
|
+
}
|
|
19923
|
+
splits.push(idx);
|
|
19924
|
+
}
|
|
19925
|
+
if (splits.length > 0) {
|
|
19926
|
+
const mid = splits[Math.floor(splits.length / 2)];
|
|
19927
|
+
return {
|
|
19928
|
+
oldFileName: rest.substring(0, mid),
|
|
19929
|
+
newFileName: rest.substring(mid + 1)
|
|
19930
|
+
};
|
|
19931
|
+
}
|
|
19932
|
+
}
|
|
19933
|
+
return null;
|
|
19934
|
+
}
|
|
19935
|
+
function unquoteIfQuoted(s) {
|
|
19936
|
+
if (s.startsWith('"')) {
|
|
19937
|
+
const parsed = parseQuotedFileName(s);
|
|
19938
|
+
if (parsed) {
|
|
19939
|
+
return parsed.fileName;
|
|
19940
|
+
}
|
|
19941
|
+
}
|
|
19942
|
+
return s;
|
|
19943
|
+
}
|
|
19944
|
+
function parseQuotedFileName(s) {
|
|
19945
|
+
if (!s.startsWith('"')) {
|
|
19946
|
+
return null;
|
|
19947
|
+
}
|
|
19948
|
+
let result = "";
|
|
19949
|
+
let j = 1;
|
|
19950
|
+
while (j < s.length) {
|
|
19951
|
+
if (s[j] === '"') {
|
|
19952
|
+
return { fileName: result, rawLength: j + 1 };
|
|
19953
|
+
}
|
|
19954
|
+
if (s[j] === "\\" && j + 1 < s.length) {
|
|
19955
|
+
j++;
|
|
19956
|
+
switch (s[j]) {
|
|
19957
|
+
case "a":
|
|
19958
|
+
result += "\x07";
|
|
19959
|
+
break;
|
|
19960
|
+
case "b":
|
|
19961
|
+
result += "\b";
|
|
19962
|
+
break;
|
|
19963
|
+
case "f":
|
|
19964
|
+
result += "\f";
|
|
19965
|
+
break;
|
|
19966
|
+
case "n":
|
|
19967
|
+
result += "\n";
|
|
19968
|
+
break;
|
|
19969
|
+
case "r":
|
|
19970
|
+
result += "\r";
|
|
19971
|
+
break;
|
|
19972
|
+
case "t":
|
|
19973
|
+
result += " ";
|
|
19974
|
+
break;
|
|
19975
|
+
case "v":
|
|
19976
|
+
result += "\v";
|
|
19977
|
+
break;
|
|
19978
|
+
case "\\":
|
|
19979
|
+
result += "\\";
|
|
19980
|
+
break;
|
|
19981
|
+
case '"':
|
|
19982
|
+
result += '"';
|
|
19983
|
+
break;
|
|
19984
|
+
case "0":
|
|
19985
|
+
case "1":
|
|
19986
|
+
case "2":
|
|
19987
|
+
case "3":
|
|
19988
|
+
case "4":
|
|
19989
|
+
case "5":
|
|
19990
|
+
case "6":
|
|
19991
|
+
case "7": {
|
|
19992
|
+
if (j + 2 >= s.length || s[j + 1] < "0" || s[j + 1] > "7" || s[j + 2] < "0" || s[j + 2] > "7") {
|
|
19993
|
+
return null;
|
|
19994
|
+
}
|
|
19995
|
+
const bytes = [parseInt(s.substring(j, j + 3), 8)];
|
|
19996
|
+
j += 3;
|
|
19997
|
+
while (s[j] === "\\" && s[j + 1] >= "0" && s[j + 1] <= "7") {
|
|
19998
|
+
if (j + 3 >= s.length || s[j + 2] < "0" || s[j + 2] > "7" || s[j + 3] < "0" || s[j + 3] > "7") {
|
|
19999
|
+
return null;
|
|
20000
|
+
}
|
|
20001
|
+
bytes.push(parseInt(s.substring(j + 1, j + 4), 8));
|
|
20002
|
+
j += 4;
|
|
20003
|
+
}
|
|
20004
|
+
result += new TextDecoder("utf-8").decode(new Uint8Array(bytes));
|
|
20005
|
+
continue;
|
|
20006
|
+
}
|
|
20007
|
+
// Note that in C, there are also three kinds of hex escape sequences:
|
|
20008
|
+
// - \xhh
|
|
20009
|
+
// - \uhhhh
|
|
20010
|
+
// - \Uhhhhhhhh
|
|
20011
|
+
// We do not bother to parse them here because, so far as we know,
|
|
20012
|
+
// they are never emitted by any tools that generate unified diff
|
|
20013
|
+
// format diffs, and so for now jsdiff does not consider them legal.
|
|
20014
|
+
default:
|
|
20015
|
+
return null;
|
|
20016
|
+
}
|
|
20017
|
+
} else {
|
|
20018
|
+
result += s[j];
|
|
20019
|
+
}
|
|
20020
|
+
j++;
|
|
20021
|
+
}
|
|
20022
|
+
return null;
|
|
20023
|
+
}
|
|
20024
|
+
function parseFileHeader(index) {
|
|
20025
|
+
const fileHeaderMatch = /^(---|\+\+\+)\s+/.exec(diffstr[i]);
|
|
20026
|
+
if (fileHeaderMatch) {
|
|
20027
|
+
const prefix = fileHeaderMatch[1], data = diffstr[i].substring(3).trim().split(" ", 2), header = (data[1] || "").trim();
|
|
20028
|
+
let fileName = data[0];
|
|
20029
|
+
if (fileName.startsWith('"')) {
|
|
20030
|
+
fileName = unquoteIfQuoted(fileName);
|
|
20031
|
+
} else {
|
|
20032
|
+
fileName = fileName.replace(/\\\\/g, "\\");
|
|
20033
|
+
}
|
|
20034
|
+
if (prefix === "---") {
|
|
20035
|
+
index.oldFileName = fileName;
|
|
20036
|
+
index.oldHeader = header;
|
|
20037
|
+
} else {
|
|
20038
|
+
index.newFileName = fileName;
|
|
20039
|
+
index.newHeader = header;
|
|
20040
|
+
}
|
|
20041
|
+
i++;
|
|
20042
|
+
}
|
|
20043
|
+
}
|
|
20044
|
+
function parseHunk() {
|
|
20045
|
+
var _a2;
|
|
20046
|
+
const chunkHeaderIndex = i, chunkHeaderLine = diffstr[i++], chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
|
|
20047
|
+
const hunk = {
|
|
20048
|
+
oldStart: +chunkHeader[1],
|
|
20049
|
+
oldLines: typeof chunkHeader[2] === "undefined" ? 1 : +chunkHeader[2],
|
|
20050
|
+
newStart: +chunkHeader[3],
|
|
20051
|
+
newLines: typeof chunkHeader[4] === "undefined" ? 1 : +chunkHeader[4],
|
|
20052
|
+
lines: []
|
|
20053
|
+
};
|
|
20054
|
+
if (hunk.oldLines === 0) {
|
|
20055
|
+
hunk.oldStart += 1;
|
|
20056
|
+
}
|
|
20057
|
+
if (hunk.newLines === 0) {
|
|
20058
|
+
hunk.newStart += 1;
|
|
20059
|
+
}
|
|
20060
|
+
let addCount = 0, removeCount = 0;
|
|
20061
|
+
for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || ((_a2 = diffstr[i]) === null || _a2 === void 0 ? void 0 : _a2.startsWith("\\"))); i++) {
|
|
20062
|
+
const operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? " " : diffstr[i][0];
|
|
20063
|
+
if (operation === "+" || operation === "-" || operation === " " || operation === "\\") {
|
|
20064
|
+
hunk.lines.push(diffstr[i]);
|
|
20065
|
+
if (operation === "+") {
|
|
20066
|
+
addCount++;
|
|
20067
|
+
} else if (operation === "-") {
|
|
20068
|
+
removeCount++;
|
|
20069
|
+
} else if (operation === " ") {
|
|
20070
|
+
addCount++;
|
|
20071
|
+
removeCount++;
|
|
20072
|
+
}
|
|
20073
|
+
} else {
|
|
20074
|
+
throw new Error(`Hunk at line ${chunkHeaderIndex + 1} contained invalid line ${diffstr[i]}`);
|
|
20075
|
+
}
|
|
20076
|
+
}
|
|
20077
|
+
if (!addCount && hunk.newLines === 1) {
|
|
20078
|
+
hunk.newLines = 0;
|
|
20079
|
+
}
|
|
20080
|
+
if (!removeCount && hunk.oldLines === 1) {
|
|
20081
|
+
hunk.oldLines = 0;
|
|
20082
|
+
}
|
|
20083
|
+
if (addCount !== hunk.newLines) {
|
|
20084
|
+
throw new Error("Added line count did not match for hunk at line " + (chunkHeaderIndex + 1));
|
|
20085
|
+
}
|
|
20086
|
+
if (removeCount !== hunk.oldLines) {
|
|
20087
|
+
throw new Error("Removed line count did not match for hunk at line " + (chunkHeaderIndex + 1));
|
|
20088
|
+
}
|
|
20089
|
+
if (i < diffstr.length && diffstr[i] && /^[+ -]/.test(diffstr[i]) && !isFileHeader(diffstr[i])) {
|
|
20090
|
+
throw new Error("Hunk at line " + (chunkHeaderIndex + 1) + " has more lines than expected (expected " + hunk.oldLines + " old lines and " + hunk.newLines + " new lines)");
|
|
20091
|
+
}
|
|
20092
|
+
return hunk;
|
|
20093
|
+
}
|
|
20094
|
+
while (i < diffstr.length) {
|
|
20095
|
+
parseIndex();
|
|
20096
|
+
}
|
|
20097
|
+
return list;
|
|
20098
|
+
}
|
|
20099
|
+
|
|
20100
|
+
// node_modules/diff/libesm/util/distance-iterator.js
|
|
20101
|
+
function distance_iterator_default(start, minLine, maxLine) {
|
|
20102
|
+
let wantForward = true, backwardExhausted = false, forwardExhausted = false, localOffset = 1;
|
|
20103
|
+
return function iterator() {
|
|
20104
|
+
if (wantForward && !forwardExhausted) {
|
|
20105
|
+
if (backwardExhausted) {
|
|
20106
|
+
localOffset++;
|
|
20107
|
+
} else {
|
|
20108
|
+
wantForward = false;
|
|
20109
|
+
}
|
|
20110
|
+
if (start + localOffset <= maxLine) {
|
|
20111
|
+
return start + localOffset;
|
|
20112
|
+
}
|
|
20113
|
+
forwardExhausted = true;
|
|
20114
|
+
}
|
|
20115
|
+
if (!backwardExhausted) {
|
|
20116
|
+
if (!forwardExhausted) {
|
|
20117
|
+
wantForward = true;
|
|
20118
|
+
}
|
|
20119
|
+
if (minLine <= start - localOffset) {
|
|
20120
|
+
return start - localOffset++;
|
|
20121
|
+
}
|
|
20122
|
+
backwardExhausted = true;
|
|
20123
|
+
return iterator();
|
|
20124
|
+
}
|
|
20125
|
+
return void 0;
|
|
20126
|
+
};
|
|
20127
|
+
}
|
|
20128
|
+
|
|
20129
|
+
// node_modules/diff/libesm/patch/apply.js
|
|
20130
|
+
function applyPatch(source, patch, options = {}) {
|
|
20131
|
+
let patches;
|
|
20132
|
+
if (typeof patch === "string") {
|
|
20133
|
+
patches = parsePatch(patch);
|
|
20134
|
+
} else if (Array.isArray(patch)) {
|
|
20135
|
+
patches = patch;
|
|
20136
|
+
} else {
|
|
20137
|
+
patches = [patch];
|
|
20138
|
+
}
|
|
20139
|
+
if (patches.length > 1) {
|
|
20140
|
+
throw new Error("applyPatch only works with a single input.");
|
|
20141
|
+
}
|
|
20142
|
+
return applyStructuredPatch(source, patches[0], options);
|
|
20143
|
+
}
|
|
20144
|
+
function applyStructuredPatch(source, patch, options = {}) {
|
|
20145
|
+
if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) {
|
|
20146
|
+
if (hasOnlyWinLineEndings(source) && isUnix(patch)) {
|
|
20147
|
+
patch = unixToWin(patch);
|
|
20148
|
+
} else if (hasOnlyUnixLineEndings(source) && isWin(patch)) {
|
|
20149
|
+
patch = winToUnix(patch);
|
|
20150
|
+
}
|
|
20151
|
+
}
|
|
20152
|
+
const lines = source.split("\n"), hunks = patch.hunks, compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), fuzzFactor = options.fuzzFactor || 0;
|
|
20153
|
+
let minLine = 0;
|
|
20154
|
+
if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) {
|
|
20155
|
+
throw new Error("fuzzFactor must be a non-negative integer");
|
|
20156
|
+
}
|
|
20157
|
+
if (!hunks.length) {
|
|
20158
|
+
return source;
|
|
20159
|
+
}
|
|
20160
|
+
let prevLine = "", removeEOFNL = false, addEOFNL = false;
|
|
20161
|
+
for (let i = 0; i < hunks[hunks.length - 1].lines.length; i++) {
|
|
20162
|
+
const line = hunks[hunks.length - 1].lines[i];
|
|
20163
|
+
if (line[0] == "\\") {
|
|
20164
|
+
if (prevLine[0] == "+") {
|
|
20165
|
+
removeEOFNL = true;
|
|
20166
|
+
} else if (prevLine[0] == "-") {
|
|
20167
|
+
addEOFNL = true;
|
|
20168
|
+
}
|
|
20169
|
+
}
|
|
20170
|
+
prevLine = line;
|
|
20171
|
+
}
|
|
20172
|
+
if (removeEOFNL) {
|
|
20173
|
+
if (addEOFNL) {
|
|
20174
|
+
if (!fuzzFactor && lines[lines.length - 1] == "") {
|
|
20175
|
+
return false;
|
|
20176
|
+
}
|
|
20177
|
+
} else if (lines[lines.length - 1] == "") {
|
|
20178
|
+
lines.pop();
|
|
20179
|
+
} else if (!fuzzFactor) {
|
|
20180
|
+
return false;
|
|
20181
|
+
}
|
|
20182
|
+
} else if (addEOFNL) {
|
|
20183
|
+
if (lines[lines.length - 1] != "") {
|
|
20184
|
+
lines.push("");
|
|
20185
|
+
} else if (!fuzzFactor) {
|
|
20186
|
+
return false;
|
|
20187
|
+
}
|
|
20188
|
+
}
|
|
20189
|
+
function applyHunk(hunkLines, toPos, maxErrors, hunkLinesI = 0, lastContextLineMatched = true, patchedLines = [], patchedLinesLength = 0) {
|
|
20190
|
+
let nConsecutiveOldContextLines = 0;
|
|
20191
|
+
let nextContextLineMustMatch = false;
|
|
20192
|
+
for (; hunkLinesI < hunkLines.length; hunkLinesI++) {
|
|
20193
|
+
const hunkLine = hunkLines[hunkLinesI], operation = hunkLine.length > 0 ? hunkLine[0] : " ", content = hunkLine.length > 0 ? hunkLine.substr(1) : hunkLine;
|
|
20194
|
+
if (operation === "-") {
|
|
20195
|
+
if (compareLine(toPos + 1, lines[toPos], operation, content)) {
|
|
20196
|
+
toPos++;
|
|
20197
|
+
nConsecutiveOldContextLines = 0;
|
|
20198
|
+
} else {
|
|
20199
|
+
if (!maxErrors || lines[toPos] == null) {
|
|
20200
|
+
return null;
|
|
20201
|
+
}
|
|
20202
|
+
patchedLines[patchedLinesLength] = lines[toPos];
|
|
20203
|
+
return applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1);
|
|
20204
|
+
}
|
|
20205
|
+
}
|
|
20206
|
+
if (operation === "+") {
|
|
20207
|
+
if (!lastContextLineMatched) {
|
|
20208
|
+
return null;
|
|
20209
|
+
}
|
|
20210
|
+
patchedLines[patchedLinesLength] = content;
|
|
20211
|
+
patchedLinesLength++;
|
|
20212
|
+
nConsecutiveOldContextLines = 0;
|
|
20213
|
+
nextContextLineMustMatch = true;
|
|
20214
|
+
}
|
|
20215
|
+
if (operation === " ") {
|
|
20216
|
+
nConsecutiveOldContextLines++;
|
|
20217
|
+
patchedLines[patchedLinesLength] = lines[toPos];
|
|
20218
|
+
if (compareLine(toPos + 1, lines[toPos], operation, content)) {
|
|
20219
|
+
patchedLinesLength++;
|
|
20220
|
+
lastContextLineMatched = true;
|
|
20221
|
+
nextContextLineMustMatch = false;
|
|
20222
|
+
toPos++;
|
|
20223
|
+
} else {
|
|
20224
|
+
if (nextContextLineMustMatch || !maxErrors) {
|
|
20225
|
+
return null;
|
|
20226
|
+
}
|
|
20227
|
+
return lines[toPos] && (applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength + 1) || applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1)) || applyHunk(hunkLines, toPos, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength);
|
|
20228
|
+
}
|
|
20229
|
+
}
|
|
20230
|
+
}
|
|
20231
|
+
patchedLinesLength -= nConsecutiveOldContextLines;
|
|
20232
|
+
toPos -= nConsecutiveOldContextLines;
|
|
20233
|
+
patchedLines.length = patchedLinesLength;
|
|
20234
|
+
return {
|
|
20235
|
+
patchedLines,
|
|
20236
|
+
oldLineLastI: toPos - 1
|
|
20237
|
+
};
|
|
20238
|
+
}
|
|
20239
|
+
const resultLines = [];
|
|
20240
|
+
let prevHunkOffset = 0;
|
|
20241
|
+
for (let i = 0; i < hunks.length; i++) {
|
|
20242
|
+
const hunk = hunks[i];
|
|
20243
|
+
let hunkResult;
|
|
20244
|
+
const maxLine = lines.length - hunk.oldLines + fuzzFactor;
|
|
20245
|
+
let toPos;
|
|
20246
|
+
for (let maxErrors = 0; maxErrors <= fuzzFactor; maxErrors++) {
|
|
20247
|
+
toPos = hunk.oldStart + prevHunkOffset - 1;
|
|
20248
|
+
const iterator = distance_iterator_default(toPos, minLine, maxLine);
|
|
20249
|
+
for (; toPos !== void 0; toPos = iterator()) {
|
|
20250
|
+
hunkResult = applyHunk(hunk.lines, toPos, maxErrors);
|
|
20251
|
+
if (hunkResult) {
|
|
20252
|
+
break;
|
|
20253
|
+
}
|
|
20254
|
+
}
|
|
20255
|
+
if (hunkResult) {
|
|
20256
|
+
break;
|
|
20257
|
+
}
|
|
20258
|
+
}
|
|
20259
|
+
if (!hunkResult) {
|
|
20260
|
+
return false;
|
|
20261
|
+
}
|
|
20262
|
+
for (let i2 = minLine; i2 < toPos; i2++) {
|
|
20263
|
+
resultLines.push(lines[i2]);
|
|
20264
|
+
}
|
|
20265
|
+
for (let i2 = 0; i2 < hunkResult.patchedLines.length; i2++) {
|
|
20266
|
+
const line = hunkResult.patchedLines[i2];
|
|
20267
|
+
resultLines.push(line);
|
|
20268
|
+
}
|
|
20269
|
+
minLine = hunkResult.oldLineLastI + 1;
|
|
20270
|
+
prevHunkOffset = toPos + 1 - hunk.oldStart;
|
|
20271
|
+
}
|
|
20272
|
+
for (let i = minLine; i < lines.length; i++) {
|
|
20273
|
+
resultLines.push(lines[i]);
|
|
20274
|
+
}
|
|
20275
|
+
return resultLines.join("\n");
|
|
20276
|
+
}
|
|
20277
|
+
|
|
19727
20278
|
// src/utils.ts
|
|
20279
|
+
var import_url = require("url");
|
|
19728
20280
|
function toToolKind(toolName) {
|
|
19729
20281
|
const tool = toolName.toLowerCase();
|
|
19730
20282
|
switch (tool) {
|
|
@@ -19764,6 +20316,26 @@ function toLocations(toolName, input) {
|
|
|
19764
20316
|
return [];
|
|
19765
20317
|
}
|
|
19766
20318
|
}
|
|
20319
|
+
function parseUri(uri) {
|
|
20320
|
+
try {
|
|
20321
|
+
if (uri.startsWith("file://")) {
|
|
20322
|
+
const path = uri.slice(7);
|
|
20323
|
+
const name = path.split("/").pop() || path;
|
|
20324
|
+
return { type: "file", url: uri, filename: name, mime: "text/plain" };
|
|
20325
|
+
}
|
|
20326
|
+
if (uri.startsWith("zed://")) {
|
|
20327
|
+
const url2 = new URL(uri);
|
|
20328
|
+
const path = url2.searchParams.get("path");
|
|
20329
|
+
if (path) {
|
|
20330
|
+
const name = path.split("/").pop() || path;
|
|
20331
|
+
return { type: "file", url: (0, import_url.pathToFileURL)(path).href, filename: name, mime: "text/plain" };
|
|
20332
|
+
}
|
|
20333
|
+
}
|
|
20334
|
+
return { type: "text", text: uri };
|
|
20335
|
+
} catch {
|
|
20336
|
+
return { type: "text", text: uri };
|
|
20337
|
+
}
|
|
20338
|
+
}
|
|
19767
20339
|
|
|
19768
20340
|
// src/logger.ts
|
|
19769
20341
|
var import_fs = require("fs");
|
|
@@ -19785,7 +20357,11 @@ function setLogEnabled(v) {
|
|
|
19785
20357
|
}
|
|
19786
20358
|
function cleanOldLogs() {
|
|
19787
20359
|
try {
|
|
19788
|
-
const files = (0, import_fs.readdirSync)(LOG_DIR).filter((f) => f.endsWith(".log")).map((f) => ({
|
|
20360
|
+
const files = (0, import_fs.readdirSync)(LOG_DIR).filter((f) => f.endsWith(".log")).map((f) => ({
|
|
20361
|
+
name: f,
|
|
20362
|
+
path: (0, import_path.join)(LOG_DIR, f),
|
|
20363
|
+
mtime: (0, import_fs.statSync)((0, import_path.join)(LOG_DIR, f)).mtime.getTime()
|
|
20364
|
+
})).sort((a, b) => b.mtime - a.mtime);
|
|
19789
20365
|
for (let i = MAX_LOG_FILES; i < files.length; i++) {
|
|
19790
20366
|
try {
|
|
19791
20367
|
const { unlinkSync } = require("fs");
|
|
@@ -19867,6 +20443,7 @@ function sanitizeAssembled(obj, depth = 0) {
|
|
|
19867
20443
|
}
|
|
19868
20444
|
|
|
19869
20445
|
// src/event-handler.ts
|
|
20446
|
+
var QUESTION_TIMEOUT_MS = 6e4;
|
|
19870
20447
|
var permissionOptions = [
|
|
19871
20448
|
{ optionId: "once", kind: "allow_once", name: "Allow once" },
|
|
19872
20449
|
{ optionId: "always", kind: "allow_always", name: "Always allow" },
|
|
@@ -19883,6 +20460,11 @@ var EventHandler = class {
|
|
|
19883
20460
|
questionQueues = /* @__PURE__ */ new Map();
|
|
19884
20461
|
started = false;
|
|
19885
20462
|
messageBuffers = /* @__PURE__ */ new Map();
|
|
20463
|
+
/** Index: sessionId → Set<messageId> for O(1) flush lookup. */
|
|
20464
|
+
sessionBufferIndex = /* @__PURE__ */ new Map();
|
|
20465
|
+
messageMetaCache = /* @__PURE__ */ new Map();
|
|
20466
|
+
/** Track tool call counts per child session to detect completion. */
|
|
20467
|
+
childToolCounts = /* @__PURE__ */ new Map();
|
|
19886
20468
|
/**
|
|
19887
20469
|
* Wrapper around connection.sessionUpdate that logs what's sent to the ACP client.
|
|
19888
20470
|
* This is the single point where we record all outgoing traffic.
|
|
@@ -19893,7 +20475,15 @@ var EventHandler = class {
|
|
|
19893
20475
|
this.flushMessageBuffer(params.sessionId, `boundary:${updateType}`);
|
|
19894
20476
|
acpOut(`sessionUpdate.${updateType}`, {
|
|
19895
20477
|
sessionId: params.sessionId.slice(0, 12),
|
|
19896
|
-
...updateType === "tool_call" || updateType === "tool_call_update" ? {
|
|
20478
|
+
...updateType === "tool_call" || updateType === "tool_call_update" ? {
|
|
20479
|
+
toolCallId: params.update.toolCallId,
|
|
20480
|
+
tool: params.update.title,
|
|
20481
|
+
kind: params.update.kind,
|
|
20482
|
+
status: params.update.status
|
|
20483
|
+
} : updateType === "session_info_update" ? {
|
|
20484
|
+
title: params.update.title,
|
|
20485
|
+
_meta: params.update._meta
|
|
20486
|
+
} : {}
|
|
19897
20487
|
});
|
|
19898
20488
|
}
|
|
19899
20489
|
return this.connection.sessionUpdate(params);
|
|
@@ -19908,6 +20498,12 @@ var EventHandler = class {
|
|
|
19908
20498
|
if (!buf) {
|
|
19909
20499
|
buf = { sessionId, text: "", thought: "" };
|
|
19910
20500
|
this.messageBuffers.set(messageId, buf);
|
|
20501
|
+
const msgSet = this.sessionBufferIndex.get(sessionId);
|
|
20502
|
+
if (msgSet) {
|
|
20503
|
+
msgSet.add(messageId);
|
|
20504
|
+
} else {
|
|
20505
|
+
this.sessionBufferIndex.set(sessionId, /* @__PURE__ */ new Set([messageId]));
|
|
20506
|
+
}
|
|
19911
20507
|
}
|
|
19912
20508
|
buf[kind] += delta;
|
|
19913
20509
|
}
|
|
@@ -19917,8 +20513,11 @@ var EventHandler = class {
|
|
|
19917
20513
|
* text/thought phase is complete.
|
|
19918
20514
|
*/
|
|
19919
20515
|
flushMessageBuffer(sessionId, reason) {
|
|
19920
|
-
|
|
19921
|
-
|
|
20516
|
+
const msgIds = this.sessionBufferIndex.get(sessionId);
|
|
20517
|
+
if (!msgIds) return;
|
|
20518
|
+
for (const messageId of msgIds) {
|
|
20519
|
+
const buf = this.messageBuffers.get(messageId);
|
|
20520
|
+
if (!buf) continue;
|
|
19922
20521
|
if (!buf.text && !buf.thought) {
|
|
19923
20522
|
this.messageBuffers.delete(messageId);
|
|
19924
20523
|
continue;
|
|
@@ -19943,11 +20542,46 @@ var EventHandler = class {
|
|
|
19943
20542
|
}
|
|
19944
20543
|
this.messageBuffers.delete(messageId);
|
|
19945
20544
|
}
|
|
20545
|
+
this.sessionBufferIndex.delete(sessionId);
|
|
19946
20546
|
}
|
|
19947
20547
|
/** Flush all remaining buffers (e.g. on stop) */
|
|
19948
20548
|
flushAllBuffers() {
|
|
19949
|
-
for (const
|
|
19950
|
-
this.flushMessageBuffer(
|
|
20549
|
+
for (const sessionId of this.sessionBufferIndex.keys()) {
|
|
20550
|
+
this.flushMessageBuffer(sessionId, "stop");
|
|
20551
|
+
}
|
|
20552
|
+
}
|
|
20553
|
+
/**
|
|
20554
|
+
* Get or cache message metadata (role + parts) for a message.
|
|
20555
|
+
* Fetches from SDK only on first delta for a message, then serves from cache.
|
|
20556
|
+
*/
|
|
20557
|
+
async getOrCacheMessageMeta(sessionID, messageID, cwd) {
|
|
20558
|
+
const cached2 = this.messageMetaCache.get(messageID);
|
|
20559
|
+
if (cached2) return cached2;
|
|
20560
|
+
const message = await this.sdk.session.message({ sessionID, messageID, directory: cwd }, { throwOnError: true }).then((x) => x.data).catch(() => void 0);
|
|
20561
|
+
if (!message) return void 0;
|
|
20562
|
+
const parts = /* @__PURE__ */ new Map();
|
|
20563
|
+
for (const p of message.parts) {
|
|
20564
|
+
parts.set(p.id, { type: p.type, ignored: p.ignored });
|
|
20565
|
+
}
|
|
20566
|
+
const meta3 = { role: message.info.role, parts };
|
|
20567
|
+
this.messageMetaCache.set(messageID, meta3);
|
|
20568
|
+
if (this.messageMetaCache.size > 50) {
|
|
20569
|
+
const oldest = this.messageMetaCache.keys().next().value;
|
|
20570
|
+
if (oldest) this.messageMetaCache.delete(oldest);
|
|
20571
|
+
}
|
|
20572
|
+
return meta3;
|
|
20573
|
+
}
|
|
20574
|
+
/**
|
|
20575
|
+
* Flush previous message buffer for a session when messageId changes.
|
|
20576
|
+
*/
|
|
20577
|
+
flushPreviousBuffer(sessionId, currentMessageId) {
|
|
20578
|
+
const msgIds = this.sessionBufferIndex.get(sessionId);
|
|
20579
|
+
if (!msgIds) return;
|
|
20580
|
+
for (const messageId of msgIds) {
|
|
20581
|
+
if (messageId !== currentMessageId) {
|
|
20582
|
+
this.flushMessageBuffer(sessionId, "new_message");
|
|
20583
|
+
return;
|
|
20584
|
+
}
|
|
19951
20585
|
}
|
|
19952
20586
|
}
|
|
19953
20587
|
constructor(deps) {
|
|
@@ -19991,12 +20625,8 @@ var EventHandler = class {
|
|
|
19991
20625
|
*/
|
|
19992
20626
|
resolveSession(sessionId) {
|
|
19993
20627
|
const session = this.sessionManager.tryGet(sessionId);
|
|
19994
|
-
if (session) return
|
|
19995
|
-
|
|
19996
|
-
if (!root) return void 0;
|
|
19997
|
-
const child = this.sessionManager.tryGet(sessionId);
|
|
19998
|
-
if (child) return { sessionId: child.id, cwd: child.cwd };
|
|
19999
|
-
return void 0;
|
|
20628
|
+
if (!session) return void 0;
|
|
20629
|
+
return { sessionId: session.id, cwd: session.cwd };
|
|
20000
20630
|
}
|
|
20001
20631
|
// ─── Event Handling ──────────────────────────────────────────────
|
|
20002
20632
|
async handleEvent(event) {
|
|
@@ -20013,7 +20643,13 @@ var EventHandler = class {
|
|
|
20013
20643
|
this.sessionManager.registerDiscovered(id, parentID, title);
|
|
20014
20644
|
if (isNew) {
|
|
20015
20645
|
ocEvent("session.created.child", { id, parentID, title });
|
|
20016
|
-
|
|
20646
|
+
const agentMatch = title?.match(/@(\w+)\s+subagent/);
|
|
20647
|
+
const agentType = agentMatch?.[1];
|
|
20648
|
+
const description = title?.replace(/\s*\(@\w+\s+subagent\)\s*$/, "") ?? title;
|
|
20649
|
+
await this.announceChildSession(id, parentID, title, {
|
|
20650
|
+
agentType,
|
|
20651
|
+
description
|
|
20652
|
+
});
|
|
20017
20653
|
}
|
|
20018
20654
|
return;
|
|
20019
20655
|
}
|
|
@@ -20058,11 +20694,16 @@ var EventHandler = class {
|
|
|
20058
20694
|
const filepath = typeof metadata["filepath"] === "string" ? metadata["filepath"] : "";
|
|
20059
20695
|
const diff = typeof metadata["diff"] === "string" ? metadata["diff"] : "";
|
|
20060
20696
|
if (filepath && diff) {
|
|
20061
|
-
this.
|
|
20062
|
-
|
|
20063
|
-
|
|
20064
|
-
|
|
20065
|
-
|
|
20697
|
+
const fileResult = await this.sdk.file.read({ path: filepath, directory }).then((x) => x.data).catch(() => void 0);
|
|
20698
|
+
const original = typeof fileResult?.content === "string" ? fileResult.content : "";
|
|
20699
|
+
const newContent = applyPatch(original, diff);
|
|
20700
|
+
if (newContent !== false) {
|
|
20701
|
+
this.connection.writeTextFile({
|
|
20702
|
+
sessionId,
|
|
20703
|
+
path: filepath,
|
|
20704
|
+
content: newContent
|
|
20705
|
+
});
|
|
20706
|
+
}
|
|
20066
20707
|
}
|
|
20067
20708
|
}
|
|
20068
20709
|
await this.sdk.permission.reply({
|
|
@@ -20086,26 +20727,50 @@ var EventHandler = class {
|
|
|
20086
20727
|
if (!resolved) return;
|
|
20087
20728
|
const directory = resolved.cwd;
|
|
20088
20729
|
const sessionId = resolved.sessionId;
|
|
20089
|
-
ocEvent("question.asked", {
|
|
20730
|
+
ocEvent("question.asked", {
|
|
20731
|
+
id: q.id,
|
|
20732
|
+
sessionID: q.sessionID,
|
|
20733
|
+
questions: q.questions.length
|
|
20734
|
+
});
|
|
20090
20735
|
const prev = this.questionQueues.get(q.sessionID) ?? Promise.resolve();
|
|
20091
20736
|
const next = prev.then(async () => {
|
|
20092
|
-
const
|
|
20093
|
-
|
|
20094
|
-
|
|
20095
|
-
|
|
20096
|
-
|
|
20097
|
-
|
|
20737
|
+
const extResult = await Promise.race([
|
|
20738
|
+
this.connection.extMethod("questionAsked", {
|
|
20739
|
+
sessionId,
|
|
20740
|
+
questionId: q.id,
|
|
20741
|
+
questions: q.questions,
|
|
20742
|
+
...q.tool && { tool: q.tool }
|
|
20743
|
+
}),
|
|
20744
|
+
new Promise(
|
|
20745
|
+
(_, reject) => setTimeout(() => reject(new Error("questionAsked timeout")), QUESTION_TIMEOUT_MS)
|
|
20746
|
+
)
|
|
20747
|
+
]).catch((err) => {
|
|
20748
|
+
console.error("[event-handler] extMethod questionAsked failed:", err?.message ?? err);
|
|
20749
|
+
return void 0;
|
|
20750
|
+
});
|
|
20751
|
+
if (!extResult || !extResult.answers) {
|
|
20098
20752
|
await this.sdk.question.reject({ requestID: q.id, directory }).catch(() => {
|
|
20099
20753
|
});
|
|
20100
20754
|
ocEvent("question.rejected", { requestID: q.id });
|
|
20101
20755
|
return;
|
|
20102
20756
|
}
|
|
20757
|
+
const answers = extResult.answers;
|
|
20758
|
+
if (!Array.isArray(answers) || !answers.every((a) => Array.isArray(a) && a.every((v) => typeof v === "string"))) {
|
|
20759
|
+
console.error("[event-handler] invalid answers shape from client:", JSON.stringify(answers));
|
|
20760
|
+
await this.sdk.question.reject({ requestID: q.id, directory }).catch(() => {
|
|
20761
|
+
});
|
|
20762
|
+
ocEvent("question.rejected", {
|
|
20763
|
+
requestID: q.id,
|
|
20764
|
+
reason: "invalid_answers"
|
|
20765
|
+
});
|
|
20766
|
+
return;
|
|
20767
|
+
}
|
|
20103
20768
|
await this.sdk.question.reply({
|
|
20104
20769
|
requestID: q.id,
|
|
20105
20770
|
directory,
|
|
20106
|
-
answers
|
|
20771
|
+
answers
|
|
20107
20772
|
});
|
|
20108
|
-
ocEvent("question.reply", { requestID: q.id, answers
|
|
20773
|
+
ocEvent("question.reply", { requestID: q.id, answers });
|
|
20109
20774
|
}).catch((err) => {
|
|
20110
20775
|
console.error("[event-handler] question handling error:", err);
|
|
20111
20776
|
}).finally(() => {
|
|
@@ -20131,24 +20796,12 @@ var EventHandler = class {
|
|
|
20131
20796
|
const resolved = this.resolveSession(props.sessionID);
|
|
20132
20797
|
if (!resolved) return;
|
|
20133
20798
|
const sessionId = resolved.sessionId;
|
|
20134
|
-
|
|
20135
|
-
|
|
20136
|
-
);
|
|
20137
|
-
|
|
20138
|
-
|
|
20139
|
-
|
|
20140
|
-
const message = await this.sdk.session.message(
|
|
20141
|
-
{
|
|
20142
|
-
sessionID: props.sessionID,
|
|
20143
|
-
messageID: props.messageID,
|
|
20144
|
-
directory: resolved.cwd
|
|
20145
|
-
},
|
|
20146
|
-
{ throwOnError: true }
|
|
20147
|
-
).then((x) => x.data).catch(() => void 0);
|
|
20148
|
-
if (!message || message.info.role !== "assistant") return;
|
|
20149
|
-
const part = message.parts.find((p) => p.id === props.partID);
|
|
20150
|
-
if (!part) return;
|
|
20151
|
-
if (part.type === "text" && props.field === "text" && part.ignored !== true) {
|
|
20799
|
+
this.flushPreviousBuffer(sessionId, props.messageID);
|
|
20800
|
+
const meta3 = await this.getOrCacheMessageMeta(props.sessionID, props.messageID, resolved.cwd);
|
|
20801
|
+
if (!meta3 || meta3.role !== "assistant") return;
|
|
20802
|
+
const partMeta = meta3.parts.get(props.partID);
|
|
20803
|
+
if (!partMeta) return;
|
|
20804
|
+
if (partMeta.type === "text" && props.field === "text" && partMeta.ignored !== true) {
|
|
20152
20805
|
this.accumulateDelta(props.messageID, sessionId, "text", props.delta);
|
|
20153
20806
|
await this.sendToClient({
|
|
20154
20807
|
sessionId,
|
|
@@ -20161,7 +20814,7 @@ var EventHandler = class {
|
|
|
20161
20814
|
});
|
|
20162
20815
|
return;
|
|
20163
20816
|
}
|
|
20164
|
-
if (
|
|
20817
|
+
if (partMeta.type === "reasoning" && props.field === "text") {
|
|
20165
20818
|
this.accumulateDelta(props.messageID, sessionId, "thought", props.delta);
|
|
20166
20819
|
await this.sendToClient({
|
|
20167
20820
|
sessionId,
|
|
@@ -20182,19 +20835,20 @@ var EventHandler = class {
|
|
|
20182
20835
|
* Announce a child session to the ACP client.
|
|
20183
20836
|
*
|
|
20184
20837
|
* Sends a session_info_update with the child's own sessionId,
|
|
20185
|
-
* and _meta containing
|
|
20186
|
-
* can display it as a linked sub-session.
|
|
20838
|
+
* and _meta containing structured metadata about the subagent.
|
|
20187
20839
|
*/
|
|
20188
|
-
async announceChildSession(childSessionId, parentSessionId, title) {
|
|
20840
|
+
async announceChildSession(childSessionId, parentSessionId, title, meta3) {
|
|
20189
20841
|
await this.sendToClient({
|
|
20190
20842
|
sessionId: childSessionId,
|
|
20191
20843
|
update: {
|
|
20192
20844
|
sessionUpdate: "session_info_update",
|
|
20193
|
-
title,
|
|
20845
|
+
title: title ?? "Subagent",
|
|
20194
20846
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20195
20847
|
_meta: {
|
|
20196
20848
|
parentSessionId,
|
|
20197
|
-
isSubagent: true
|
|
20849
|
+
isSubagent: true,
|
|
20850
|
+
...meta3?.agentType && { agentType: meta3.agentType },
|
|
20851
|
+
...meta3?.description && { description: meta3.description }
|
|
20198
20852
|
}
|
|
20199
20853
|
}
|
|
20200
20854
|
}).catch(() => {
|
|
@@ -20204,6 +20858,19 @@ var EventHandler = class {
|
|
|
20204
20858
|
async handleToolPart(sessionId, part) {
|
|
20205
20859
|
if (!this.toolStarts.has(part.callID)) {
|
|
20206
20860
|
this.toolStarts.add(part.callID);
|
|
20861
|
+
const session = this.sessionManager.tryGet(sessionId);
|
|
20862
|
+
if (session?.parentID) {
|
|
20863
|
+
const tracker = this.childToolCounts.get(sessionId);
|
|
20864
|
+
if (tracker) {
|
|
20865
|
+
tracker.total++;
|
|
20866
|
+
} else {
|
|
20867
|
+
this.childToolCounts.set(sessionId, {
|
|
20868
|
+
completed: 0,
|
|
20869
|
+
total: 1,
|
|
20870
|
+
startTime: Date.now()
|
|
20871
|
+
});
|
|
20872
|
+
}
|
|
20873
|
+
}
|
|
20207
20874
|
await this.sendToClient({
|
|
20208
20875
|
sessionId,
|
|
20209
20876
|
update: {
|
|
@@ -20255,6 +20922,27 @@ var EventHandler = class {
|
|
|
20255
20922
|
case "completed": {
|
|
20256
20923
|
this.toolStarts.delete(part.callID);
|
|
20257
20924
|
this.bashSnapshots.delete(part.callID);
|
|
20925
|
+
if (part.tool === "task" && part.state.metadata) {
|
|
20926
|
+
const childSessionId = typeof part.state.metadata["sessionId"] === "string" ? part.state.metadata["sessionId"] : void 0;
|
|
20927
|
+
if (childSessionId && this.sessionManager.tryGet(childSessionId)) {
|
|
20928
|
+
const tracker = this.childToolCounts.get(childSessionId);
|
|
20929
|
+
await this.sendChildSessionCompleted(
|
|
20930
|
+
childSessionId,
|
|
20931
|
+
sessionId,
|
|
20932
|
+
tracker?.completed ?? 0,
|
|
20933
|
+
tracker ? Date.now() - tracker.startTime : 0
|
|
20934
|
+
).catch(() => {
|
|
20935
|
+
});
|
|
20936
|
+
this.childToolCounts.delete(childSessionId);
|
|
20937
|
+
}
|
|
20938
|
+
}
|
|
20939
|
+
const session = this.sessionManager.tryGet(sessionId);
|
|
20940
|
+
if (session?.parentID) {
|
|
20941
|
+
const tracker = this.childToolCounts.get(sessionId);
|
|
20942
|
+
if (tracker) {
|
|
20943
|
+
tracker.completed++;
|
|
20944
|
+
}
|
|
20945
|
+
}
|
|
20258
20946
|
const kind = toToolKind(part.tool);
|
|
20259
20947
|
const content = [
|
|
20260
20948
|
{
|
|
@@ -20269,6 +20957,26 @@ var EventHandler = class {
|
|
|
20269
20957
|
const newText = typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : "";
|
|
20270
20958
|
content.push({ type: "diff", path: filePath, oldText, newText });
|
|
20271
20959
|
}
|
|
20960
|
+
if (part.tool === "todowrite") {
|
|
20961
|
+
try {
|
|
20962
|
+
const todos = JSON.parse(part.state.output);
|
|
20963
|
+
if (Array.isArray(todos)) {
|
|
20964
|
+
await this.sendToClient({
|
|
20965
|
+
sessionId,
|
|
20966
|
+
update: {
|
|
20967
|
+
sessionUpdate: "plan",
|
|
20968
|
+
entries: todos.map((todo) => ({
|
|
20969
|
+
priority: "medium",
|
|
20970
|
+
status: todo.status === "cancelled" ? "completed" : todo.status,
|
|
20971
|
+
content: todo.content
|
|
20972
|
+
}))
|
|
20973
|
+
}
|
|
20974
|
+
}).catch(() => {
|
|
20975
|
+
});
|
|
20976
|
+
}
|
|
20977
|
+
} catch {
|
|
20978
|
+
}
|
|
20979
|
+
}
|
|
20272
20980
|
await this.sendToClient({
|
|
20273
20981
|
sessionId,
|
|
20274
20982
|
update: {
|
|
@@ -20317,6 +21025,32 @@ var EventHandler = class {
|
|
|
20317
21025
|
}
|
|
20318
21026
|
}
|
|
20319
21027
|
}
|
|
21028
|
+
/**
|
|
21029
|
+
* Notify the ACP client that a child session has completed.
|
|
21030
|
+
*/
|
|
21031
|
+
async sendChildSessionCompleted(childSessionId, parentSessionId, toolCallCount, durationMs) {
|
|
21032
|
+
const child = this.sessionManager.tryGet(childSessionId);
|
|
21033
|
+
const title = child?.title ?? "Subagent";
|
|
21034
|
+
const agentMatch = title.match(/@(\w+)\s+subagent/);
|
|
21035
|
+
const agentType = agentMatch?.[1];
|
|
21036
|
+
const description = title.replace(/\s*\(@\w+\s+subagent\)\s*$/, "");
|
|
21037
|
+
await this.sendToClient({
|
|
21038
|
+
sessionId: childSessionId,
|
|
21039
|
+
update: {
|
|
21040
|
+
sessionUpdate: "session_info_update",
|
|
21041
|
+
title,
|
|
21042
|
+
status: "completed",
|
|
21043
|
+
_meta: {
|
|
21044
|
+
parentSessionId,
|
|
21045
|
+
isSubagent: true,
|
|
21046
|
+
toolCallCount,
|
|
21047
|
+
durationMs,
|
|
21048
|
+
...agentType && { agentType },
|
|
21049
|
+
...description && { description }
|
|
21050
|
+
}
|
|
21051
|
+
}
|
|
21052
|
+
});
|
|
21053
|
+
}
|
|
20320
21054
|
bashOutput(part) {
|
|
20321
21055
|
if (part.tool !== "bash") return;
|
|
20322
21056
|
if (!("metadata" in part.state) || !part.state.metadata || typeof part.state.metadata !== "object") return;
|
|
@@ -20334,13 +21068,55 @@ function simpleHash(str) {
|
|
|
20334
21068
|
return hash2.toString(36);
|
|
20335
21069
|
}
|
|
20336
21070
|
|
|
21071
|
+
// src/auth-provider.ts
|
|
21072
|
+
var AuthProviderManager = class {
|
|
21073
|
+
sdk;
|
|
21074
|
+
constructor(sdk) {
|
|
21075
|
+
this.sdk = sdk;
|
|
21076
|
+
}
|
|
21077
|
+
async listProviders(directory) {
|
|
21078
|
+
ocCall("provider.list", { directory });
|
|
21079
|
+
const res = await this.sdk.provider.list({ directory });
|
|
21080
|
+
return res.data;
|
|
21081
|
+
}
|
|
21082
|
+
async authMethods(directory) {
|
|
21083
|
+
ocCall("provider.auth", { directory });
|
|
21084
|
+
const res = await this.sdk.provider.auth({ directory });
|
|
21085
|
+
return res.data;
|
|
21086
|
+
}
|
|
21087
|
+
async setApiKey(providerID, auth) {
|
|
21088
|
+
ocCall("auth.set", { providerID });
|
|
21089
|
+
const res = await this.sdk.auth.set({ providerID, auth });
|
|
21090
|
+
return res.data;
|
|
21091
|
+
}
|
|
21092
|
+
async removeAuth(providerID) {
|
|
21093
|
+
ocCall("auth.remove", { providerID });
|
|
21094
|
+
const res = await this.sdk.auth.remove({ providerID });
|
|
21095
|
+
return res.data;
|
|
21096
|
+
}
|
|
21097
|
+
async oauthAuthorize(providerID, method, inputs, directory) {
|
|
21098
|
+
ocCall("provider.oauth.authorize", { providerID, method });
|
|
21099
|
+
const res = await this.sdk.provider.oauth.authorize({ providerID, method, inputs, directory });
|
|
21100
|
+
return res.data;
|
|
21101
|
+
}
|
|
21102
|
+
async oauthCallback(providerID, method, code, directory) {
|
|
21103
|
+
ocCall("provider.oauth.callback", { providerID, method });
|
|
21104
|
+
const res = await this.sdk.provider.oauth.callback({ providerID, method, code, directory });
|
|
21105
|
+
return res.data;
|
|
21106
|
+
}
|
|
21107
|
+
};
|
|
21108
|
+
|
|
20337
21109
|
// src/agent.ts
|
|
21110
|
+
function isApiKeyError(err) {
|
|
21111
|
+
return err instanceof Error && err.name === "AI_LoadAPIKeyError";
|
|
21112
|
+
}
|
|
20338
21113
|
var Agent = class {
|
|
20339
21114
|
connection;
|
|
20340
21115
|
config;
|
|
20341
21116
|
sdk;
|
|
20342
21117
|
sessionManager;
|
|
20343
21118
|
eventHandler;
|
|
21119
|
+
authProvider;
|
|
20344
21120
|
/**
|
|
20345
21121
|
* Wrapper around connection.sessionUpdate that logs what's sent to the ACP client.
|
|
20346
21122
|
*/
|
|
@@ -20349,7 +21125,12 @@ var Agent = class {
|
|
|
20349
21125
|
if (updateType !== "agent_message_chunk" && updateType !== "agent_thought_chunk") {
|
|
20350
21126
|
acpOut(`sessionUpdate.${updateType}`, {
|
|
20351
21127
|
sessionId: params.sessionId.slice(0, 12),
|
|
20352
|
-
...updateType === "tool_call" || updateType === "tool_call_update" ? {
|
|
21128
|
+
...updateType === "tool_call" || updateType === "tool_call_update" ? {
|
|
21129
|
+
toolCallId: params.update.toolCallId,
|
|
21130
|
+
tool: params.update.title,
|
|
21131
|
+
kind: params.update.kind,
|
|
21132
|
+
status: params.update.status
|
|
21133
|
+
} : {}
|
|
20353
21134
|
});
|
|
20354
21135
|
}
|
|
20355
21136
|
return this.connection.sessionUpdate(params);
|
|
@@ -20358,6 +21139,7 @@ var Agent = class {
|
|
|
20358
21139
|
this.config = config2;
|
|
20359
21140
|
this.sdk = config2.sdk;
|
|
20360
21141
|
this.sessionManager = new SessionManager(config2.sdk);
|
|
21142
|
+
this.authProvider = new AuthProviderManager(config2.sdk);
|
|
20361
21143
|
}
|
|
20362
21144
|
init(connection) {
|
|
20363
21145
|
this.connection = connection;
|
|
@@ -20369,8 +21151,22 @@ var Agent = class {
|
|
|
20369
21151
|
this.eventHandler.start();
|
|
20370
21152
|
}
|
|
20371
21153
|
// ─── Protocol Lifecycle ───────────────────────────────────────────
|
|
20372
|
-
async initialize(
|
|
20373
|
-
acpIn("initialize", { protocolVersion:
|
|
21154
|
+
async initialize(params) {
|
|
21155
|
+
acpIn("initialize", { protocolVersion: params.protocolVersion });
|
|
21156
|
+
const authMethod = {
|
|
21157
|
+
description: "Run `opencode auth login` in the terminal",
|
|
21158
|
+
name: "Login with opencode",
|
|
21159
|
+
id: "opencode-login"
|
|
21160
|
+
};
|
|
21161
|
+
if (params.clientCapabilities?._meta?.["terminal-auth"] === true) {
|
|
21162
|
+
authMethod._meta = {
|
|
21163
|
+
"terminal-auth": {
|
|
21164
|
+
command: "opencode",
|
|
21165
|
+
args: ["auth", "login"],
|
|
21166
|
+
label: "OpenCode Login"
|
|
21167
|
+
}
|
|
21168
|
+
};
|
|
21169
|
+
}
|
|
20374
21170
|
return {
|
|
20375
21171
|
protocolVersion: 1,
|
|
20376
21172
|
agentCapabilities: {
|
|
@@ -20379,76 +21175,159 @@ var Agent = class {
|
|
|
20379
21175
|
promptCapabilities: { embeddedContext: true, image: true },
|
|
20380
21176
|
sessionCapabilities: { fork: {}, list: {}, resume: {} }
|
|
20381
21177
|
},
|
|
20382
|
-
authMethods: [
|
|
20383
|
-
{
|
|
20384
|
-
description: "Run `opencode auth login` in the terminal",
|
|
20385
|
-
name: "Login with opencode",
|
|
20386
|
-
id: "opencode-login"
|
|
20387
|
-
}
|
|
20388
|
-
],
|
|
21178
|
+
authMethods: [authMethod],
|
|
20389
21179
|
agentInfo: {
|
|
20390
21180
|
name: "Harmony-ACP",
|
|
20391
21181
|
version: "0.1.0"
|
|
20392
|
-
}
|
|
21182
|
+
},
|
|
21183
|
+
_meta: await this.buildProviderMeta()
|
|
20393
21184
|
};
|
|
20394
21185
|
}
|
|
20395
21186
|
async authenticate(_params) {
|
|
20396
21187
|
throw new Error("Authentication not implemented");
|
|
20397
21188
|
}
|
|
21189
|
+
// ─── Extension Methods ────────────────────────────────────────────
|
|
21190
|
+
async extMethod(method, params) {
|
|
21191
|
+
acpIn("extMethod", { method });
|
|
21192
|
+
if (!method.startsWith("provider/")) {
|
|
21193
|
+
throw new Error(`Unknown extMethod: ${method}`);
|
|
21194
|
+
}
|
|
21195
|
+
return this.handleProviderMethod(method, params);
|
|
21196
|
+
}
|
|
21197
|
+
async handleProviderMethod(method, params) {
|
|
21198
|
+
const sessionId = params.sessionId;
|
|
21199
|
+
const cwd = sessionId ? this.sessionManager.get(sessionId)?.cwd : this.config.cwd;
|
|
21200
|
+
switch (method) {
|
|
21201
|
+
case "provider/list": {
|
|
21202
|
+
if (!cwd) throw new Error("Session not found");
|
|
21203
|
+
const data = await this.authProvider.listProviders(cwd);
|
|
21204
|
+
acpOut("extMethod.response", { method: "provider/list" });
|
|
21205
|
+
return data;
|
|
21206
|
+
}
|
|
21207
|
+
case "provider/auth/methods": {
|
|
21208
|
+
if (!cwd) throw new Error("Session not found");
|
|
21209
|
+
const data = await this.authProvider.authMethods(cwd);
|
|
21210
|
+
acpOut("extMethod.response", { method: "provider/auth/methods" });
|
|
21211
|
+
return data;
|
|
21212
|
+
}
|
|
21213
|
+
case "provider/auth/set": {
|
|
21214
|
+
const providerID = params.providerID;
|
|
21215
|
+
const auth = params.auth;
|
|
21216
|
+
if (!providerID) throw new Error("Missing required parameter: providerID");
|
|
21217
|
+
if (!auth?.key) throw new Error("Missing required parameter: auth.key");
|
|
21218
|
+
await this.authProvider.setApiKey(providerID, auth);
|
|
21219
|
+
acpOut("extMethod.response", { method: "provider/auth/set", providerID });
|
|
21220
|
+
return { success: true };
|
|
21221
|
+
}
|
|
21222
|
+
case "provider/auth/remove": {
|
|
21223
|
+
const providerID = params.providerID;
|
|
21224
|
+
if (!providerID) throw new Error("Missing required parameter: providerID");
|
|
21225
|
+
await this.authProvider.removeAuth(providerID);
|
|
21226
|
+
acpOut("extMethod.response", { method: "provider/auth/remove", providerID });
|
|
21227
|
+
return { success: true };
|
|
21228
|
+
}
|
|
21229
|
+
case "provider/oauth/authorize": {
|
|
21230
|
+
const providerID = params.providerID;
|
|
21231
|
+
const oauthMethod = params.method;
|
|
21232
|
+
if (!providerID) throw new Error("Missing required parameter: providerID");
|
|
21233
|
+
const inputs = params.inputs;
|
|
21234
|
+
const data = await this.authProvider.oauthAuthorize(providerID, oauthMethod ?? 0, inputs, cwd);
|
|
21235
|
+
acpOut("extMethod.response", { method: "provider/oauth/authorize", providerID });
|
|
21236
|
+
return data;
|
|
21237
|
+
}
|
|
21238
|
+
case "provider/oauth/callback": {
|
|
21239
|
+
const providerID = params.providerID;
|
|
21240
|
+
const oauthMethod = params.method;
|
|
21241
|
+
const code = params.code;
|
|
21242
|
+
if (!providerID) throw new Error("Missing required parameter: providerID");
|
|
21243
|
+
await this.authProvider.oauthCallback(providerID, oauthMethod ?? 0, code, cwd);
|
|
21244
|
+
acpOut("extMethod.response", { method: "provider/oauth/callback", providerID });
|
|
21245
|
+
return { success: true };
|
|
21246
|
+
}
|
|
21247
|
+
default:
|
|
21248
|
+
throw new Error(`Unknown provider extMethod: ${method}`);
|
|
21249
|
+
}
|
|
21250
|
+
}
|
|
21251
|
+
async buildProviderMeta() {
|
|
21252
|
+
try {
|
|
21253
|
+
const data = await this.authProvider.listProviders(this.config.cwd);
|
|
21254
|
+
if (!data?.all) return void 0;
|
|
21255
|
+
const providers = data.all.map((p) => ({
|
|
21256
|
+
id: p.id,
|
|
21257
|
+
name: p.name,
|
|
21258
|
+
connected: data.connected?.includes(p.id) ?? false
|
|
21259
|
+
}));
|
|
21260
|
+
return { providers };
|
|
21261
|
+
} catch {
|
|
21262
|
+
return void 0;
|
|
21263
|
+
}
|
|
21264
|
+
}
|
|
20398
21265
|
// ─── Session Management ───────────────────────────────────────────
|
|
20399
21266
|
async newSession(params) {
|
|
20400
21267
|
acpIn("newSession", { cwd: params.cwd, mcpServers: params.mcpServers.length });
|
|
20401
|
-
|
|
20402
|
-
|
|
20403
|
-
|
|
20404
|
-
|
|
20405
|
-
params.cwd,
|
|
20406
|
-
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20410
|
-
|
|
20411
|
-
|
|
20412
|
-
|
|
20413
|
-
|
|
20414
|
-
|
|
20415
|
-
|
|
20416
|
-
|
|
20417
|
-
|
|
20418
|
-
|
|
20419
|
-
|
|
20420
|
-
|
|
20421
|
-
|
|
20422
|
-
|
|
21268
|
+
try {
|
|
21269
|
+
const directory = params.cwd;
|
|
21270
|
+
ocCall("session.create", { directory });
|
|
21271
|
+
const model = await this.defaultModel(directory);
|
|
21272
|
+
const state = await this.sessionManager.create(params.cwd, params.mcpServers, model);
|
|
21273
|
+
const sessionId = state.id;
|
|
21274
|
+
const result = await this.loadSessionMode({
|
|
21275
|
+
cwd: directory,
|
|
21276
|
+
mcpServers: params.mcpServers,
|
|
21277
|
+
sessionId
|
|
21278
|
+
});
|
|
21279
|
+
acpOut("newSession.response", { sessionId });
|
|
21280
|
+
return {
|
|
21281
|
+
sessionId,
|
|
21282
|
+
configOptions: result.configOptions,
|
|
21283
|
+
models: result.models,
|
|
21284
|
+
modes: result.modes,
|
|
21285
|
+
_meta: result._meta
|
|
21286
|
+
};
|
|
21287
|
+
} catch (e) {
|
|
21288
|
+
if (isApiKeyError(e)) throw RequestError.authRequired();
|
|
21289
|
+
throw e;
|
|
21290
|
+
}
|
|
20423
21291
|
}
|
|
20424
21292
|
async loadSession(params) {
|
|
20425
|
-
|
|
20426
|
-
|
|
20427
|
-
|
|
20428
|
-
|
|
20429
|
-
|
|
20430
|
-
|
|
20431
|
-
|
|
20432
|
-
|
|
20433
|
-
|
|
20434
|
-
const messages = await this.sdk.session.messages({ sessionID: sessionId, directory }).then((x) => x.data).catch(() => void 0);
|
|
20435
|
-
const lastUser = messages?.findLast((m) => m.info.role === "user")?.info;
|
|
20436
|
-
if (lastUser?.role === "user") {
|
|
20437
|
-
result.models.currentModelId = `${lastUser.model.providerID}/${lastUser.model.modelID}`;
|
|
20438
|
-
this.sessionManager.setModel(sessionId, {
|
|
20439
|
-
providerID: lastUser.model.providerID,
|
|
20440
|
-
modelID: lastUser.model.modelID
|
|
21293
|
+
try {
|
|
21294
|
+
const directory = params.cwd;
|
|
21295
|
+
const sessionId = params.sessionId;
|
|
21296
|
+
const model = await this.defaultModel(directory);
|
|
21297
|
+
await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model);
|
|
21298
|
+
const result = await this.loadSessionMode({
|
|
21299
|
+
cwd: directory,
|
|
21300
|
+
mcpServers: params.mcpServers,
|
|
21301
|
+
sessionId
|
|
20441
21302
|
});
|
|
20442
|
-
|
|
20443
|
-
|
|
20444
|
-
|
|
21303
|
+
const messages = await this.sdk.session.messages({ sessionID: sessionId, directory }).then((x) => x.data).catch(() => void 0);
|
|
21304
|
+
const lastUser = messages?.findLast((m) => m.info.role === "user")?.info;
|
|
21305
|
+
if (lastUser?.role === "user") {
|
|
21306
|
+
result.models.currentModelId = `${lastUser.model.providerID}/${lastUser.model.modelID}`;
|
|
21307
|
+
this.sessionManager.setModel(sessionId, {
|
|
21308
|
+
providerID: lastUser.model.providerID,
|
|
21309
|
+
modelID: lastUser.model.modelID
|
|
21310
|
+
});
|
|
21311
|
+
if (result.modes?.availableModes.some((m) => m.id === lastUser.agent)) {
|
|
21312
|
+
result.modes.currentModeId = lastUser.agent;
|
|
21313
|
+
this.sessionManager.setMode(sessionId, lastUser.agent);
|
|
21314
|
+
}
|
|
20445
21315
|
}
|
|
21316
|
+
for (const msg of messages ?? []) {
|
|
21317
|
+
await this.processMessage(msg);
|
|
21318
|
+
for (const part of msg.parts ?? []) {
|
|
21319
|
+
if (part.type === "tool" && part.tool === "task" && part.state?.status === "completed" && part.state?.metadata?.sessionId) {
|
|
21320
|
+
const childSessionId = part.state.metadata.sessionId;
|
|
21321
|
+
await this.replayChildSession(childSessionId, sessionId, directory);
|
|
21322
|
+
}
|
|
21323
|
+
}
|
|
21324
|
+
}
|
|
21325
|
+
await this.sendUsageUpdate(sessionId, directory);
|
|
21326
|
+
return result;
|
|
21327
|
+
} catch (e) {
|
|
21328
|
+
if (isApiKeyError(e)) throw RequestError.authRequired();
|
|
21329
|
+
throw e;
|
|
20446
21330
|
}
|
|
20447
|
-
for (const msg of messages ?? []) {
|
|
20448
|
-
await this.processMessage(msg);
|
|
20449
|
-
}
|
|
20450
|
-
await this.sendUsageUpdate(sessionId, directory);
|
|
20451
|
-
return result;
|
|
20452
21331
|
}
|
|
20453
21332
|
async listSessions(params) {
|
|
20454
21333
|
const limit = 100;
|
|
@@ -20470,38 +21349,48 @@ var Agent = class {
|
|
|
20470
21349
|
return response;
|
|
20471
21350
|
}
|
|
20472
21351
|
async unstable_forkSession(params) {
|
|
20473
|
-
|
|
20474
|
-
|
|
20475
|
-
|
|
20476
|
-
|
|
20477
|
-
|
|
20478
|
-
|
|
20479
|
-
|
|
20480
|
-
|
|
20481
|
-
|
|
20482
|
-
|
|
20483
|
-
|
|
20484
|
-
|
|
20485
|
-
|
|
20486
|
-
|
|
20487
|
-
|
|
21352
|
+
try {
|
|
21353
|
+
const directory = params.cwd;
|
|
21354
|
+
const mcpServers = params.mcpServers ?? [];
|
|
21355
|
+
const model = await this.defaultModel(directory);
|
|
21356
|
+
const forked = await this.sdk.session.fork({ sessionID: params.sessionId, directory }).then((x) => x.data);
|
|
21357
|
+
if (!forked) throw new Error("Fork session returned no data");
|
|
21358
|
+
const sessionId = forked.id;
|
|
21359
|
+
await this.sessionManager.load(sessionId, directory, mcpServers, model);
|
|
21360
|
+
const result = await this.loadSessionMode({
|
|
21361
|
+
cwd: directory,
|
|
21362
|
+
mcpServers,
|
|
21363
|
+
sessionId
|
|
21364
|
+
});
|
|
21365
|
+
const messages = await this.sdk.session.messages({ sessionID: sessionId, directory }).then((x) => x.data).catch(() => void 0);
|
|
21366
|
+
for (const msg of messages ?? []) {
|
|
21367
|
+
await this.processMessage(msg);
|
|
21368
|
+
}
|
|
21369
|
+
await this.sendUsageUpdate(sessionId, directory);
|
|
21370
|
+
return result;
|
|
21371
|
+
} catch (e) {
|
|
21372
|
+
if (isApiKeyError(e)) throw RequestError.authRequired();
|
|
21373
|
+
throw e;
|
|
20488
21374
|
}
|
|
20489
|
-
await this.sendUsageUpdate(sessionId, directory);
|
|
20490
|
-
return result;
|
|
20491
21375
|
}
|
|
20492
21376
|
async unstable_resumeSession(params) {
|
|
20493
|
-
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
20497
|
-
|
|
20498
|
-
|
|
20499
|
-
|
|
20500
|
-
|
|
20501
|
-
|
|
20502
|
-
|
|
20503
|
-
|
|
20504
|
-
|
|
21377
|
+
try {
|
|
21378
|
+
const directory = params.cwd;
|
|
21379
|
+
const sessionId = params.sessionId;
|
|
21380
|
+
const mcpServers = params.mcpServers ?? [];
|
|
21381
|
+
const model = await this.defaultModel(directory);
|
|
21382
|
+
await this.sessionManager.load(sessionId, directory, mcpServers, model);
|
|
21383
|
+
const result = await this.loadSessionMode({
|
|
21384
|
+
cwd: directory,
|
|
21385
|
+
mcpServers,
|
|
21386
|
+
sessionId
|
|
21387
|
+
});
|
|
21388
|
+
await this.sendUsageUpdate(sessionId, directory);
|
|
21389
|
+
return result;
|
|
21390
|
+
} catch (e) {
|
|
21391
|
+
if (isApiKeyError(e)) throw RequestError.authRequired();
|
|
21392
|
+
throw e;
|
|
21393
|
+
}
|
|
20505
21394
|
}
|
|
20506
21395
|
// ─── Prompt Execution ─────────────────────────────────────────────
|
|
20507
21396
|
async prompt(params) {
|
|
@@ -20514,7 +21403,7 @@ var Agent = class {
|
|
|
20514
21403
|
if (!session.model) {
|
|
20515
21404
|
this.sessionManager.setModel(session.id, model);
|
|
20516
21405
|
}
|
|
20517
|
-
const agent = session.modeId ??
|
|
21406
|
+
const agent = session.modeId ?? await this.defaultAgent(directory);
|
|
20518
21407
|
const parts = this.convertPromptParts(params.prompt);
|
|
20519
21408
|
const cmd = this.parseCommand(parts);
|
|
20520
21409
|
ocCall("session.prompt", { sessionID, agent, model: `${model.providerID}/${model.modelID}` });
|
|
@@ -20571,6 +21460,23 @@ var Agent = class {
|
|
|
20571
21460
|
_meta: {}
|
|
20572
21461
|
};
|
|
20573
21462
|
}
|
|
21463
|
+
if (cmd.name === "compact") {
|
|
21464
|
+
await this.sdk.session.summarize(
|
|
21465
|
+
{
|
|
21466
|
+
sessionID,
|
|
21467
|
+
directory,
|
|
21468
|
+
providerID: model.providerID,
|
|
21469
|
+
modelID: model.modelID
|
|
21470
|
+
},
|
|
21471
|
+
{ throwOnError: true }
|
|
21472
|
+
).catch(() => {
|
|
21473
|
+
});
|
|
21474
|
+
await this.sendUsageUpdate(sessionID, directory);
|
|
21475
|
+
return {
|
|
21476
|
+
stopReason: "end_turn",
|
|
21477
|
+
_meta: {}
|
|
21478
|
+
};
|
|
21479
|
+
}
|
|
20574
21480
|
const response = await this.sdk.session.prompt({
|
|
20575
21481
|
sessionID,
|
|
20576
21482
|
model: { providerID: model.providerID, modelID: model.modelID },
|
|
@@ -20599,38 +21505,129 @@ var Agent = class {
|
|
|
20599
21505
|
}
|
|
20600
21506
|
// ─── Configuration ────────────────────────────────────────────────
|
|
20601
21507
|
async setSessionMode(params) {
|
|
21508
|
+
const session = this.sessionManager.get(params.sessionId);
|
|
21509
|
+
const agents = await this.sdk.app.agents({ directory: session.cwd }).then((x) => x.data ?? []).catch(() => []);
|
|
21510
|
+
const availableModes = agents.filter((a) => a.mode !== "subagent" && !a.hidden);
|
|
21511
|
+
if (!availableModes.some((a) => a.name === params.modeId)) {
|
|
21512
|
+
throw new Error(`Agent not found: ${params.modeId}`);
|
|
21513
|
+
}
|
|
20602
21514
|
this.sessionManager.setMode(params.sessionId, params.modeId);
|
|
20603
21515
|
}
|
|
20604
21516
|
async unstable_setSessionModel(params) {
|
|
20605
21517
|
const session = this.sessionManager.get(params.sessionId);
|
|
20606
|
-
const
|
|
20607
|
-
|
|
21518
|
+
const providers = await this.sdk.config.providers({ directory: session.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
|
|
21519
|
+
const selection = parseModelSelection(params.modelId, providers);
|
|
21520
|
+
this.sessionManager.setModel(params.sessionId, selection.model);
|
|
21521
|
+
this.sessionManager.setVariant(params.sessionId, selection.variant);
|
|
21522
|
+
const currentProvider = providers.find((p) => p.id === selection.model.providerID);
|
|
21523
|
+
const currentModelInfo = currentProvider?.models?.[selection.model.modelID];
|
|
21524
|
+
const availableVariants = currentModelInfo?.variants ? Object.keys(currentModelInfo.variants) : [];
|
|
21525
|
+
return {
|
|
21526
|
+
_meta: buildVariantMeta(selection.model, selection.variant, availableVariants)
|
|
21527
|
+
};
|
|
20608
21528
|
}
|
|
20609
21529
|
async setSessionConfigOption(params) {
|
|
20610
21530
|
const session = this.sessionManager.get(params.sessionId);
|
|
20611
|
-
|
|
20612
|
-
|
|
20613
|
-
|
|
20614
|
-
|
|
21531
|
+
const providers = await this.sdk.config.providers({ directory: session.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
|
|
21532
|
+
const sortedProviders = [...providers].sort(
|
|
21533
|
+
(a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : a.name.toLowerCase() > b.name.toLowerCase() ? 1 : 0
|
|
21534
|
+
);
|
|
21535
|
+
if (params.configId === "model") {
|
|
21536
|
+
if (typeof params.value !== "string") throw RequestError.invalidParams("model value must be a string");
|
|
21537
|
+
const selection = parseModelSelection(params.value, providers);
|
|
21538
|
+
this.sessionManager.setModel(params.sessionId, selection.model);
|
|
21539
|
+
this.sessionManager.setVariant(params.sessionId, selection.variant);
|
|
21540
|
+
} else if (params.configId === "mode") {
|
|
21541
|
+
if (typeof params.value !== "string") throw RequestError.invalidParams("mode value must be a string");
|
|
21542
|
+
const agents2 = await this.sdk.app.agents({ directory: session.cwd }).then((x) => x.data ?? []).catch(() => []);
|
|
21543
|
+
const availableModes2 = agents2.filter((a) => a.mode !== "subagent" && !a.hidden);
|
|
21544
|
+
if (!availableModes2.some((a) => a.name === params.value)) {
|
|
21545
|
+
throw RequestError.invalidParams(JSON.stringify({ error: `Mode not found: ${params.value}` }));
|
|
21546
|
+
}
|
|
20615
21547
|
this.sessionManager.setMode(params.sessionId, params.value);
|
|
21548
|
+
} else {
|
|
21549
|
+
throw RequestError.invalidParams(JSON.stringify({ error: `Unknown config option: ${params.configId}` }));
|
|
21550
|
+
}
|
|
21551
|
+
const updatedSession = this.sessionManager.get(params.sessionId);
|
|
21552
|
+
const model = updatedSession.model ?? await this.defaultModel(session.cwd);
|
|
21553
|
+
const currentProvider = sortedProviders.find((p) => p.id === model.providerID);
|
|
21554
|
+
const currentModelInfo = currentProvider?.models?.[model.modelID];
|
|
21555
|
+
const availableVariants = currentModelInfo?.variants ? Object.keys(currentModelInfo.variants) : [];
|
|
21556
|
+
const variant = updatedSession.variant;
|
|
21557
|
+
const currentModelId = formatModelIdWithVariant(model, variant, availableVariants);
|
|
21558
|
+
const availableModels = sortedProviders.flatMap(
|
|
21559
|
+
(provider) => Object.values(provider.models).flatMap((modelInfo) => {
|
|
21560
|
+
const base = {
|
|
21561
|
+
modelId: `${provider.id}/${modelInfo.id}`,
|
|
21562
|
+
name: `${provider.name}/${modelInfo.name}`
|
|
21563
|
+
};
|
|
21564
|
+
if (!modelInfo.variants) return [base];
|
|
21565
|
+
const variants = Object.keys(modelInfo.variants).filter((v) => v !== "default");
|
|
21566
|
+
return [
|
|
21567
|
+
base,
|
|
21568
|
+
...variants.map((v) => ({
|
|
21569
|
+
modelId: `${provider.id}/${modelInfo.id}/${v}`,
|
|
21570
|
+
name: `${provider.name}/${modelInfo.name} (${v})`
|
|
21571
|
+
}))
|
|
21572
|
+
];
|
|
21573
|
+
})
|
|
21574
|
+
);
|
|
21575
|
+
const agents = await this.sdk.app.agents({ directory: session.cwd }).then((x) => x.data ?? []).catch(() => []);
|
|
21576
|
+
const availableModes = agents.filter((a) => a.mode !== "subagent" && !a.hidden).map((a) => ({ id: a.name, name: a.name, description: a.description }));
|
|
21577
|
+
const currentModeId = updatedSession.modeId ?? availableModes[0]?.id;
|
|
21578
|
+
const configOptions = [
|
|
21579
|
+
{
|
|
21580
|
+
id: "model",
|
|
21581
|
+
name: "Model",
|
|
21582
|
+
category: "model",
|
|
21583
|
+
type: "select",
|
|
21584
|
+
currentValue: currentModelId,
|
|
21585
|
+
options: availableModels.map((m) => ({ value: m.modelId, name: m.name }))
|
|
21586
|
+
}
|
|
21587
|
+
];
|
|
21588
|
+
if (currentModeId && availableModes.length > 0) {
|
|
21589
|
+
configOptions.push({
|
|
21590
|
+
id: "mode",
|
|
21591
|
+
name: "Session Mode",
|
|
21592
|
+
category: "mode",
|
|
21593
|
+
type: "select",
|
|
21594
|
+
currentValue: currentModeId,
|
|
21595
|
+
options: availableModes.map((m) => ({
|
|
21596
|
+
value: m.id,
|
|
21597
|
+
name: m.name,
|
|
21598
|
+
...m.description ? { description: m.description } : {}
|
|
21599
|
+
}))
|
|
21600
|
+
});
|
|
20616
21601
|
}
|
|
20617
|
-
|
|
20618
|
-
const currentModelId = `${model.providerID}/${model.modelID}`;
|
|
20619
|
-
return {
|
|
20620
|
-
configOptions: [
|
|
20621
|
-
{
|
|
20622
|
-
id: "model",
|
|
20623
|
-
name: "Model",
|
|
20624
|
-
category: "model",
|
|
20625
|
-
type: "select",
|
|
20626
|
-
currentValue: currentModelId,
|
|
20627
|
-
options: []
|
|
20628
|
-
// TODO: populate from providers
|
|
20629
|
-
}
|
|
20630
|
-
]
|
|
20631
|
-
};
|
|
21602
|
+
return { configOptions };
|
|
20632
21603
|
}
|
|
20633
21604
|
// ─── Internal Helpers ─────────────────────────────────────────────
|
|
21605
|
+
/**
|
|
21606
|
+
* Replay a child subagent session by fetching its messages and sending
|
|
21607
|
+
* them to the ACP client. Registers the child with the session manager.
|
|
21608
|
+
*/
|
|
21609
|
+
async replayChildSession(childSessionId, parentSessionId, directory) {
|
|
21610
|
+
if (this.sessionManager.tryGet(childSessionId)) return;
|
|
21611
|
+
const childMessages = await this.sdk.session.messages({ sessionID: childSessionId, directory }).then((x) => x.data).catch(() => void 0);
|
|
21612
|
+
if (!childMessages?.length) return;
|
|
21613
|
+
const title = `Subagent (${childSessionId.slice(0, 8)})`;
|
|
21614
|
+
this.sessionManager.registerDiscovered(childSessionId, parentSessionId, title);
|
|
21615
|
+
await this.sendToClient({
|
|
21616
|
+
sessionId: childSessionId,
|
|
21617
|
+
update: {
|
|
21618
|
+
sessionUpdate: "session_info_update",
|
|
21619
|
+
title,
|
|
21620
|
+
_meta: {
|
|
21621
|
+
parentSessionId,
|
|
21622
|
+
isSubagent: true
|
|
21623
|
+
}
|
|
21624
|
+
}
|
|
21625
|
+
}).catch(() => {
|
|
21626
|
+
});
|
|
21627
|
+
for (const msg of childMessages) {
|
|
21628
|
+
await this.processMessage({ ...msg, info: { ...msg.info, sessionID: childSessionId } });
|
|
21629
|
+
}
|
|
21630
|
+
}
|
|
20634
21631
|
async defaultModel(cwd) {
|
|
20635
21632
|
const configured = this.config.defaultModel;
|
|
20636
21633
|
if (configured) return configured;
|
|
@@ -20649,65 +21646,137 @@ var Agent = class {
|
|
|
20649
21646
|
return { providerID: "opencode", modelID: models[0] };
|
|
20650
21647
|
}
|
|
20651
21648
|
}
|
|
20652
|
-
const allModels = providers.flatMap(
|
|
20653
|
-
(p) => Object.keys(p.models).map((m) => ({ providerID: p.id, modelID: m }))
|
|
20654
|
-
);
|
|
21649
|
+
const allModels = providers.flatMap((p) => Object.keys(p.models).map((m) => ({ providerID: p.id, modelID: m })));
|
|
20655
21650
|
if (allModels.length > 0) return allModels[0];
|
|
20656
21651
|
return { providerID: "opencode", modelID: "big-pickle" };
|
|
20657
21652
|
}
|
|
21653
|
+
async defaultAgent(cwd) {
|
|
21654
|
+
const agents = await this.sdk.app.agents({ directory: cwd }).then((x) => x.data ?? []).catch(() => []);
|
|
21655
|
+
const primary = agents.filter((a) => a.mode !== "subagent" && !a.hidden);
|
|
21656
|
+
return primary.length > 0 ? primary[0].name : "build";
|
|
21657
|
+
}
|
|
20658
21658
|
async loadSessionMode(params) {
|
|
20659
21659
|
const model = await this.defaultModel(params.cwd);
|
|
20660
21660
|
const providers = await this.sdk.config.providers({ directory: params.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
|
|
20661
|
-
const
|
|
20662
|
-
(
|
|
20663
|
-
|
|
20664
|
-
|
|
20665
|
-
|
|
21661
|
+
const sortedProviders = [...providers].sort(
|
|
21662
|
+
(a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : a.name.toLowerCase() > b.name.toLowerCase() ? 1 : 0
|
|
21663
|
+
);
|
|
21664
|
+
const availableModels = sortedProviders.flatMap(
|
|
21665
|
+
(provider) => Object.values(provider.models).flatMap((modelInfo) => {
|
|
21666
|
+
const base = {
|
|
21667
|
+
modelId: `${provider.id}/${modelInfo.id}`,
|
|
21668
|
+
name: `${provider.name}/${modelInfo.name}`
|
|
21669
|
+
};
|
|
21670
|
+
if (!modelInfo.variants) return [base];
|
|
21671
|
+
const variants = Object.keys(modelInfo.variants).filter((v) => v !== "default");
|
|
21672
|
+
return [
|
|
21673
|
+
base,
|
|
21674
|
+
...variants.map((v) => ({
|
|
21675
|
+
modelId: `${provider.id}/${modelInfo.id}/${v}`,
|
|
21676
|
+
name: `${provider.name}/${modelInfo.name} (${v})`
|
|
21677
|
+
}))
|
|
21678
|
+
];
|
|
21679
|
+
})
|
|
21680
|
+
);
|
|
21681
|
+
const currentProvider = sortedProviders.find((p) => p.id === model.providerID);
|
|
21682
|
+
const currentModelInfo = currentProvider?.models[model.modelID];
|
|
21683
|
+
const availableVariants = currentModelInfo?.variants ? Object.keys(currentModelInfo.variants) : [];
|
|
21684
|
+
const variant = this.sessionManager.getVariant(params.sessionId);
|
|
21685
|
+
if (variant && !availableVariants.includes(variant)) {
|
|
21686
|
+
this.sessionManager.setVariant(params.sessionId, void 0);
|
|
21687
|
+
}
|
|
21688
|
+
const currentModelId = formatModelIdWithVariant(model, variant, availableVariants);
|
|
21689
|
+
const agents = await this.sdk.app.agents({ directory: params.cwd }).then((x) => x.data ?? []).catch(() => []);
|
|
21690
|
+
const availableModes = agents.filter((a) => a.mode !== "subagent" && !a.hidden).map((a) => ({ id: a.name, name: a.name, description: a.description }));
|
|
21691
|
+
const currentModeId = this.sessionManager.getMode(params.sessionId) ?? (availableModes.length > 0 ? availableModes[0].id : void 0);
|
|
21692
|
+
if (currentModeId && !this.sessionManager.getMode(params.sessionId)) {
|
|
21693
|
+
this.sessionManager.setMode(params.sessionId, currentModeId);
|
|
21694
|
+
}
|
|
21695
|
+
const modes = currentModeId ? { availableModes, currentModeId } : void 0;
|
|
21696
|
+
await Promise.all(
|
|
21697
|
+
params.mcpServers.map(async (server) => {
|
|
21698
|
+
const config2 = "url" in server ? {
|
|
21699
|
+
url: server.url,
|
|
21700
|
+
type: "remote",
|
|
21701
|
+
headers: (server.headers ?? []).reduce(
|
|
21702
|
+
(acc, h) => {
|
|
21703
|
+
acc[h.name] = h.value;
|
|
21704
|
+
return acc;
|
|
21705
|
+
},
|
|
21706
|
+
{}
|
|
21707
|
+
)
|
|
21708
|
+
} : {
|
|
21709
|
+
type: "local",
|
|
21710
|
+
command: [server.command, ...server.args],
|
|
21711
|
+
environment: (server.env ?? []).reduce(
|
|
21712
|
+
(acc, v) => {
|
|
21713
|
+
acc[v.name] = v.value;
|
|
21714
|
+
return acc;
|
|
21715
|
+
},
|
|
21716
|
+
{}
|
|
21717
|
+
)
|
|
21718
|
+
};
|
|
21719
|
+
await this.sdk.mcp.add({ directory: params.cwd, name: server.name, config: config2 }).catch(() => {
|
|
21720
|
+
});
|
|
21721
|
+
})
|
|
20666
21722
|
);
|
|
20667
|
-
const currentModelId = `${model.providerID}/${model.modelID}`;
|
|
20668
|
-
for (const server of params.mcpServers) {
|
|
20669
|
-
const config2 = "type" in server ? { url: server.url, type: "remote", headers: {} } : { type: "local", command: [server.command, ...server.args] };
|
|
20670
|
-
await this.sdk.mcp.add({ directory: params.cwd, name: server.name, config: config2 }).catch(() => {
|
|
20671
|
-
});
|
|
20672
|
-
}
|
|
20673
21723
|
const commands = await this.sdk.command.list({ directory: params.cwd }).then((x) => x.data ?? []).catch(() => []);
|
|
21724
|
+
const availableCommands = commands.map((c) => ({
|
|
21725
|
+
name: c.name,
|
|
21726
|
+
description: c.description ?? ""
|
|
21727
|
+
}));
|
|
21728
|
+
if (!availableCommands.some((c) => c.name === "compact")) {
|
|
21729
|
+
availableCommands.push({ name: "compact", description: "compact the session" });
|
|
21730
|
+
}
|
|
20674
21731
|
setTimeout(() => {
|
|
20675
21732
|
this.sendToClient({
|
|
20676
21733
|
sessionId: params.sessionId,
|
|
20677
21734
|
update: {
|
|
20678
21735
|
sessionUpdate: "available_commands_update",
|
|
20679
|
-
availableCommands
|
|
20680
|
-
name: c.name,
|
|
20681
|
-
description: c.description ?? ""
|
|
20682
|
-
}))
|
|
21736
|
+
availableCommands
|
|
20683
21737
|
}
|
|
20684
21738
|
}).catch(() => {
|
|
20685
21739
|
});
|
|
20686
21740
|
}, 0);
|
|
21741
|
+
const configOptions = [
|
|
21742
|
+
{
|
|
21743
|
+
id: "model",
|
|
21744
|
+
name: "Model",
|
|
21745
|
+
category: "model",
|
|
21746
|
+
type: "select",
|
|
21747
|
+
currentValue: currentModelId,
|
|
21748
|
+
options: availableModels.map((m) => ({
|
|
21749
|
+
value: m.modelId,
|
|
21750
|
+
name: m.name
|
|
21751
|
+
}))
|
|
21752
|
+
}
|
|
21753
|
+
];
|
|
21754
|
+
if (modes) {
|
|
21755
|
+
configOptions.push({
|
|
21756
|
+
id: "mode",
|
|
21757
|
+
name: "Session Mode",
|
|
21758
|
+
category: "mode",
|
|
21759
|
+
type: "select",
|
|
21760
|
+
currentValue: modes.currentModeId,
|
|
21761
|
+
options: modes.availableModes.map((m) => ({
|
|
21762
|
+
value: m.id,
|
|
21763
|
+
name: m.name,
|
|
21764
|
+
...m.description ? { description: m.description } : {}
|
|
21765
|
+
}))
|
|
21766
|
+
});
|
|
21767
|
+
}
|
|
20687
21768
|
return {
|
|
20688
21769
|
sessionId: params.sessionId,
|
|
20689
21770
|
models: { currentModelId, availableModels },
|
|
20690
|
-
modes
|
|
20691
|
-
|
|
20692
|
-
|
|
20693
|
-
{
|
|
20694
|
-
id: "model",
|
|
20695
|
-
name: "Model",
|
|
20696
|
-
category: "model",
|
|
20697
|
-
type: "select",
|
|
20698
|
-
currentValue: currentModelId,
|
|
20699
|
-
options: availableModels.map((m) => ({
|
|
20700
|
-
value: m.modelId,
|
|
20701
|
-
name: m.name
|
|
20702
|
-
}))
|
|
20703
|
-
}
|
|
20704
|
-
],
|
|
20705
|
-
_meta: {}
|
|
21771
|
+
modes,
|
|
21772
|
+
configOptions,
|
|
21773
|
+
_meta: buildVariantMeta(model, variant, availableVariants)
|
|
20706
21774
|
};
|
|
20707
21775
|
}
|
|
20708
21776
|
async processMessage(message) {
|
|
20709
21777
|
if (message.info.role !== "assistant" && message.info.role !== "user") return;
|
|
20710
21778
|
const sessionId = message.info.sessionID;
|
|
21779
|
+
const messageChunk = message.info.role === "user" ? "user_message_chunk" : "agent_message_chunk";
|
|
20711
21780
|
for (const part of message.parts) {
|
|
20712
21781
|
if (part.type === "tool") {
|
|
20713
21782
|
await this.replayToolPart(sessionId, part);
|
|
@@ -20716,7 +21785,7 @@ var Agent = class {
|
|
|
20716
21785
|
await this.sendToClient({
|
|
20717
21786
|
sessionId,
|
|
20718
21787
|
update: {
|
|
20719
|
-
sessionUpdate:
|
|
21788
|
+
sessionUpdate: messageChunk,
|
|
20720
21789
|
messageId: message.info.id,
|
|
20721
21790
|
content: {
|
|
20722
21791
|
type: "text",
|
|
@@ -20726,6 +21795,59 @@ var Agent = class {
|
|
|
20726
21795
|
}
|
|
20727
21796
|
}).catch(() => {
|
|
20728
21797
|
});
|
|
21798
|
+
} else if (part.type === "file" && part.url) {
|
|
21799
|
+
const url2 = part.url;
|
|
21800
|
+
const filename = part.filename ?? "file";
|
|
21801
|
+
const mime = part.mime || "application/octet-stream";
|
|
21802
|
+
if (url2.startsWith("file://")) {
|
|
21803
|
+
await this.sendToClient({
|
|
21804
|
+
sessionId,
|
|
21805
|
+
update: {
|
|
21806
|
+
sessionUpdate: messageChunk,
|
|
21807
|
+
messageId: message.info.id,
|
|
21808
|
+
content: { type: "resource_link", uri: url2, name: filename, mimeType: mime }
|
|
21809
|
+
}
|
|
21810
|
+
}).catch(() => {
|
|
21811
|
+
});
|
|
21812
|
+
} else if (url2.startsWith("data:")) {
|
|
21813
|
+
const base64Match = url2.match(/^data:([^;]+);base64,(.*)$/);
|
|
21814
|
+
const dataMime = base64Match?.[1];
|
|
21815
|
+
const base64Data = base64Match?.[2] ?? "";
|
|
21816
|
+
const effectiveMime = dataMime || mime;
|
|
21817
|
+
if (effectiveMime.startsWith("image/")) {
|
|
21818
|
+
await this.sendToClient({
|
|
21819
|
+
sessionId,
|
|
21820
|
+
update: {
|
|
21821
|
+
sessionUpdate: messageChunk,
|
|
21822
|
+
messageId: message.info.id,
|
|
21823
|
+
content: {
|
|
21824
|
+
type: "image",
|
|
21825
|
+
mimeType: effectiveMime,
|
|
21826
|
+
data: base64Data,
|
|
21827
|
+
uri: (0, import_url2.pathToFileURL)(filename).href
|
|
21828
|
+
}
|
|
21829
|
+
}
|
|
21830
|
+
}).catch(() => {
|
|
21831
|
+
});
|
|
21832
|
+
} else {
|
|
21833
|
+
const isText = effectiveMime.startsWith("text/") || effectiveMime === "application/json";
|
|
21834
|
+
const fileUri = (0, import_url2.pathToFileURL)(filename).href;
|
|
21835
|
+
const resource = isText ? {
|
|
21836
|
+
uri: fileUri,
|
|
21837
|
+
mimeType: effectiveMime,
|
|
21838
|
+
text: Buffer.from(base64Data, "base64").toString("utf-8")
|
|
21839
|
+
} : { uri: fileUri, mimeType: effectiveMime, blob: base64Data };
|
|
21840
|
+
await this.sendToClient({
|
|
21841
|
+
sessionId,
|
|
21842
|
+
update: {
|
|
21843
|
+
sessionUpdate: messageChunk,
|
|
21844
|
+
messageId: message.info.id,
|
|
21845
|
+
content: { type: "resource", resource }
|
|
21846
|
+
}
|
|
21847
|
+
}).catch(() => {
|
|
21848
|
+
});
|
|
21849
|
+
}
|
|
21850
|
+
}
|
|
20729
21851
|
} else if (part.type === "reasoning" && part.text) {
|
|
20730
21852
|
await this.sendToClient({
|
|
20731
21853
|
sessionId,
|
|
@@ -20756,9 +21878,7 @@ var Agent = class {
|
|
|
20756
21878
|
switch (part.state.status) {
|
|
20757
21879
|
case "completed": {
|
|
20758
21880
|
const kind = toToolKind(part.tool);
|
|
20759
|
-
const content = [
|
|
20760
|
-
{ type: "content", content: { type: "text", text: part.state.output } }
|
|
20761
|
-
];
|
|
21881
|
+
const content = [{ type: "content", content: { type: "text", text: part.state.output } }];
|
|
20762
21882
|
if (kind === "edit") {
|
|
20763
21883
|
const input = part.state.input;
|
|
20764
21884
|
content.push({
|
|
@@ -20768,6 +21888,26 @@ var Agent = class {
|
|
|
20768
21888
|
newText: typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : ""
|
|
20769
21889
|
});
|
|
20770
21890
|
}
|
|
21891
|
+
if (part.tool === "todowrite") {
|
|
21892
|
+
try {
|
|
21893
|
+
const todos = JSON.parse(part.state.output);
|
|
21894
|
+
if (Array.isArray(todos)) {
|
|
21895
|
+
await this.sendToClient({
|
|
21896
|
+
sessionId,
|
|
21897
|
+
update: {
|
|
21898
|
+
sessionUpdate: "plan",
|
|
21899
|
+
entries: todos.map((todo) => ({
|
|
21900
|
+
priority: "medium",
|
|
21901
|
+
status: todo.status === "cancelled" ? "completed" : todo.status,
|
|
21902
|
+
content: todo.content
|
|
21903
|
+
}))
|
|
21904
|
+
}
|
|
21905
|
+
}).catch(() => {
|
|
21906
|
+
});
|
|
21907
|
+
}
|
|
21908
|
+
} catch {
|
|
21909
|
+
}
|
|
21910
|
+
}
|
|
20771
21911
|
await this.sendToClient({
|
|
20772
21912
|
sessionId,
|
|
20773
21913
|
update: {
|
|
@@ -20813,14 +21953,32 @@ var Agent = class {
|
|
|
20813
21953
|
const msg = lastAssistant.info;
|
|
20814
21954
|
if (!msg.providerID || !msg.modelID) return;
|
|
20815
21955
|
const totalCost = assistantMessages.reduce((sum, m) => sum + m.info.cost, 0);
|
|
21956
|
+
const children = this.sessionManager.getChildren(sessionId);
|
|
21957
|
+
let childCost = 0;
|
|
21958
|
+
for (const childId of children) {
|
|
21959
|
+
const childMessages = await this.sdk.session.messages({ sessionID: childId, directory }).then((x) => x.data).catch(() => void 0);
|
|
21960
|
+
if (childMessages) {
|
|
21961
|
+
childCost += childMessages.filter((m) => m.info.role === "assistant").reduce((sum, m) => sum + (m.info.cost ?? 0), 0);
|
|
21962
|
+
}
|
|
21963
|
+
}
|
|
21964
|
+
const providers = await this.sdk.config.providers({ directory }).then((x) => x.data?.providers ?? []).catch(() => []);
|
|
21965
|
+
const provider = providers.find((p) => p.id === msg.providerID);
|
|
21966
|
+
const model = provider?.models[msg.modelID];
|
|
21967
|
+
const size = model?.limit?.context ?? 0;
|
|
20816
21968
|
await this.sendToClient({
|
|
20817
21969
|
sessionId,
|
|
20818
21970
|
update: {
|
|
20819
21971
|
sessionUpdate: "usage_update",
|
|
20820
21972
|
used: msg.tokens.input + (msg.tokens.cache?.read ?? 0),
|
|
20821
|
-
size
|
|
20822
|
-
|
|
20823
|
-
|
|
21973
|
+
size,
|
|
21974
|
+
cost: { amount: totalCost + childCost, currency: "USD" },
|
|
21975
|
+
...childCost > 0 && {
|
|
21976
|
+
_meta: {
|
|
21977
|
+
parentCost: totalCost,
|
|
21978
|
+
childCost,
|
|
21979
|
+
childSessionCount: children.length
|
|
21980
|
+
}
|
|
21981
|
+
}
|
|
20824
21982
|
}
|
|
20825
21983
|
}).catch(() => {
|
|
20826
21984
|
});
|
|
@@ -20856,11 +22014,26 @@ var Agent = class {
|
|
|
20856
22014
|
const resource = part.resource;
|
|
20857
22015
|
if ("text" in resource && resource.text) {
|
|
20858
22016
|
result.push({ type: "text", text: resource.text });
|
|
22017
|
+
} else if ("blob" in resource && resource.blob && resource.mimeType) {
|
|
22018
|
+
const parsed = parseUri(resource.uri ?? "");
|
|
22019
|
+
const filename = parsed.type === "file" ? parsed.filename : "file";
|
|
22020
|
+
result.push({
|
|
22021
|
+
type: "file",
|
|
22022
|
+
url: `data:${resource.mimeType};base64,${resource.blob}`,
|
|
22023
|
+
filename,
|
|
22024
|
+
mime: resource.mimeType
|
|
22025
|
+
});
|
|
20859
22026
|
}
|
|
20860
22027
|
break;
|
|
20861
22028
|
}
|
|
20862
|
-
case "resource_link":
|
|
22029
|
+
case "resource_link": {
|
|
22030
|
+
const parsed = parseUri(part.uri);
|
|
22031
|
+
if (parsed.type === "file") {
|
|
22032
|
+
if (part.name) parsed.filename = part.name;
|
|
22033
|
+
result.push(parsed);
|
|
22034
|
+
}
|
|
20863
22035
|
break;
|
|
22036
|
+
}
|
|
20864
22037
|
}
|
|
20865
22038
|
}
|
|
20866
22039
|
return result;
|
|
@@ -20921,6 +22094,38 @@ var Agent = class {
|
|
|
20921
22094
|
};
|
|
20922
22095
|
}
|
|
20923
22096
|
};
|
|
22097
|
+
function formatModelIdWithVariant(model, variant, availableVariants) {
|
|
22098
|
+
const base = `${model.providerID}/${model.modelID}`;
|
|
22099
|
+
if (!variant || !availableVariants.includes(variant)) return base;
|
|
22100
|
+
return `${base}/${variant}`;
|
|
22101
|
+
}
|
|
22102
|
+
function buildVariantMeta(model, variant, availableVariants) {
|
|
22103
|
+
return {
|
|
22104
|
+
opencode: {
|
|
22105
|
+
modelId: `${model.providerID}/${model.modelID}`,
|
|
22106
|
+
variant: variant ?? null,
|
|
22107
|
+
availableVariants
|
|
22108
|
+
}
|
|
22109
|
+
};
|
|
22110
|
+
}
|
|
22111
|
+
function parseModelSelection(modelId, providers) {
|
|
22112
|
+
const segments = modelId.split("/");
|
|
22113
|
+
const providerID = segments[0];
|
|
22114
|
+
const rest = segments.slice(1).join("/");
|
|
22115
|
+
const provider = providers.find((p) => p.id === providerID);
|
|
22116
|
+
if (!provider) return { model: { providerID, modelID: rest } };
|
|
22117
|
+
if (provider.models[rest]) return { model: { providerID, modelID: rest } };
|
|
22118
|
+
const lastSlash = rest.lastIndexOf("/");
|
|
22119
|
+
if (lastSlash > 0) {
|
|
22120
|
+
const candidateBase = rest.slice(0, lastSlash);
|
|
22121
|
+
const candidateVariant = rest.slice(lastSlash + 1);
|
|
22122
|
+
const baseInfo = provider.models[candidateBase];
|
|
22123
|
+
if (baseInfo?.variants && candidateVariant in baseInfo.variants) {
|
|
22124
|
+
return { model: { providerID, modelID: candidateBase }, variant: candidateVariant };
|
|
22125
|
+
}
|
|
22126
|
+
}
|
|
22127
|
+
return { model: { providerID, modelID: rest } };
|
|
22128
|
+
}
|
|
20924
22129
|
|
|
20925
22130
|
// src/index.ts
|
|
20926
22131
|
function parseArgs(args) {
|