@skylandnpm/octopus-proxy 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-N4L5M6MC.js → chunk-HWMHCHRB.js} +290 -202
- package/dist/cli.js +4 -3
- package/dist/main.js +1 -1
- package/package.json +1 -1
- package/proxy.example.json +1 -1
package/README.md
CHANGED
|
@@ -72,6 +72,6 @@ npm run publish
|
|
|
72
72
|
|
|
73
73
|
## 编码工具账号与 OpenCode 服务
|
|
74
74
|
|
|
75
|
-
OpenCode 使用 external HTTP
|
|
75
|
+
OpenCode 2.0.19 使用 external HTTP 模式。`tool.command` 应为 `/Users/luhuili/.opencode/bin/opencode`,`tool.baseUrl` 明确指向回环服务,`tool.password` 必填并由 onboard 自动生成。Proxy 可拉起未监听的服务,也可连接以同一密码预先启动的服务;启动日志保存在 `stateDirectory/opencode-serve.log`。如需只连接自行管理的服务,可设置 `autoStart: false`。旧顶层配置、managed 模式和 v1 API 不再兼容;Proxy 不停止或重启已有 OpenCode 服务。
|
|
76
76
|
|
|
77
77
|
Claw 的“编码工具账号”窗口支持 Codex API Key/设备码、ZCode 原生 Z.AI OAuth、OpenCode 原生 API Key/OAuth。凭据由编码工具保存,模型列表从原生接口读取。详细行为与验证边界见 [编码工具账号](../../docs/tool-authentication.md)。
|
|
@@ -11549,13 +11549,13 @@ var TOOL_DESCRIPTORS = {
|
|
|
11549
11549
|
opencode: {
|
|
11550
11550
|
kind: "opencode",
|
|
11551
11551
|
label: "OpenCode",
|
|
11552
|
-
defaultCommand: "opencode",
|
|
11552
|
+
defaultCommand: "/Users/luhuili/.opencode/bin/opencode",
|
|
11553
11553
|
serverArgs: [],
|
|
11554
11554
|
versionArgs: ["--version"],
|
|
11555
|
-
baseline: "
|
|
11555
|
+
baseline: "2.0.19",
|
|
11556
11556
|
versionPolicy: "exact",
|
|
11557
11557
|
sessionPattern: /^ses_[A-Za-z0-9]+$/,
|
|
11558
|
-
searchDirs: ["/
|
|
11558
|
+
searchDirs: ["/Users/luhuili/.opencode/bin"]
|
|
11559
11559
|
},
|
|
11560
11560
|
codex: {
|
|
11561
11561
|
kind: "codex",
|
|
@@ -11604,10 +11604,12 @@ var ToolSchema = z.object({
|
|
|
11604
11604
|
baseUrl: z.string().url().optional(),
|
|
11605
11605
|
mode: z.enum(["managed", "external"]).optional(),
|
|
11606
11606
|
autoStart: z.boolean().optional(),
|
|
11607
|
-
password: z.string().optional()
|
|
11607
|
+
password: z.string().min(20).optional()
|
|
11608
11608
|
}).superRefine((tool, context) => {
|
|
11609
|
-
if (tool.kind === "opencode" && tool.
|
|
11610
|
-
|
|
11609
|
+
if (tool.kind === "opencode" && !tool.password) context.addIssue({ code: z.ZodIssueCode.custom, path: ["password"], message: "OpenCode v2 \u5FC5\u987B\u914D\u7F6E\u670D\u52A1\u5BC6\u7801\uFF1B\u8BF7\u91CD\u65B0\u8FD0\u884C onboard" });
|
|
11610
|
+
if (tool.kind === "opencode" && tool.mode === "managed") context.addIssue({ code: z.ZodIssueCode.custom, path: ["mode"], message: "OpenCode v2 \u4EC5\u652F\u6301 external \u6A21\u5F0F" });
|
|
11611
|
+
if (tool.kind !== "opencode" && tool.mode === "external") context.addIssue({ code: z.ZodIssueCode.custom, path: ["mode"], message: "external mode is only supported for opencode" });
|
|
11612
|
+
}).transform((tool) => ({ ...tool, mode: tool.kind === "opencode" ? "external" : "managed", autoStart: tool.autoStart ?? true }));
|
|
11611
11613
|
var ConfigSchema = z.object({
|
|
11612
11614
|
hubUrl: z.string().url(),
|
|
11613
11615
|
botId: z.string().uuid(),
|
|
@@ -11625,11 +11627,6 @@ async function loadConfig(path) {
|
|
|
11625
11627
|
});
|
|
11626
11628
|
if ((info.mode & 63) !== 0) throw new Error("Proxy configuration contains a secret; run chmod 600 on it");
|
|
11627
11629
|
const raw = JSON.parse(await readFile(path, "utf8"));
|
|
11628
|
-
if (raw.opencode instanceof Object && raw.tool === void 0) {
|
|
11629
|
-
const { command, baseUrl, mode, autoStart, password } = raw.opencode;
|
|
11630
|
-
raw.tool = { kind: "opencode", command, baseUrl, mode, autoStart, password };
|
|
11631
|
-
delete raw.opencode;
|
|
11632
|
-
}
|
|
11633
11630
|
const config = ConfigSchema.parse(raw);
|
|
11634
11631
|
config.stateDirectory = expandHome(config.stateDirectory);
|
|
11635
11632
|
config.tool.command = expandHome(config.tool.command || describeTool(config.tool.kind).defaultCommand);
|
|
@@ -11698,10 +11695,6 @@ var Operations = [
|
|
|
11698
11695
|
"sessions.revert",
|
|
11699
11696
|
"sessions.unrevert",
|
|
11700
11697
|
"sessions.summarize",
|
|
11701
|
-
"sessions.share",
|
|
11702
|
-
"sessions.unshare",
|
|
11703
|
-
"sessions.todos",
|
|
11704
|
-
"sessions.children",
|
|
11705
11698
|
"sessions.command",
|
|
11706
11699
|
"sessions.shell",
|
|
11707
11700
|
"sessions.status",
|
|
@@ -11720,9 +11713,6 @@ var Operations = [
|
|
|
11720
11713
|
"configuration.mcpAdd",
|
|
11721
11714
|
"configuration.mcpConnect",
|
|
11722
11715
|
"configuration.mcpDisconnect",
|
|
11723
|
-
"configuration.auth",
|
|
11724
|
-
"configuration.oauthAuthorize",
|
|
11725
|
-
"configuration.oauthCallback",
|
|
11726
11716
|
"terminal.list",
|
|
11727
11717
|
"terminal.create",
|
|
11728
11718
|
"terminal.resize",
|
|
@@ -12215,7 +12205,7 @@ var AuthFlows = class {
|
|
|
12215
12205
|
}
|
|
12216
12206
|
};
|
|
12217
12207
|
function isAuthentication(operation) {
|
|
12218
|
-
return operation.startsWith("auth.")
|
|
12208
|
+
return operation.startsWith("auth.");
|
|
12219
12209
|
}
|
|
12220
12210
|
|
|
12221
12211
|
// src/journal.ts
|
|
@@ -14169,11 +14159,11 @@ var ProjectConfig = class {
|
|
|
14169
14159
|
return work;
|
|
14170
14160
|
}
|
|
14171
14161
|
async apply(project, patch) {
|
|
14172
|
-
const states = await this.request(project, "GET", "/session/
|
|
14173
|
-
if (Object.
|
|
14162
|
+
const states = unwrap(await this.request(project, "GET", "/api/session/active"));
|
|
14163
|
+
if (Object.keys(states ?? {}).length) throw new Error("Wait for active tasks and approvals before changing project configuration");
|
|
14174
14164
|
if (!this.validator) {
|
|
14175
|
-
const doc = await this.request(project, "GET", "/
|
|
14176
|
-
this.validator = new import__.Ajv2020({ strict: false, validateFormats: false }).compile({ $ref: "#/components/schemas/Config", components: doc.components });
|
|
14165
|
+
const doc = await this.request(project, "GET", "/openapi.json");
|
|
14166
|
+
this.validator = new import__.Ajv2020({ strict: false, validateFormats: false }).compile({ $ref: "#/components/schemas/Config.InfoEncoded", components: doc.components });
|
|
14177
14167
|
}
|
|
14178
14168
|
if (!this.validator(patch)) throw new Error("Invalid OpenCode configuration: " + JSON.stringify(this.validator.errors));
|
|
14179
14169
|
let name = "opencode.json", original = "", exists = false;
|
|
@@ -14214,12 +14204,20 @@ var ProjectConfig = class {
|
|
|
14214
14204
|
});
|
|
14215
14205
|
}
|
|
14216
14206
|
}
|
|
14217
|
-
await this.request(project, "POST", "/
|
|
14218
|
-
return this.request(project, "GET", "/config");
|
|
14207
|
+
await this.request(project, "POST", "/api/location/reload");
|
|
14208
|
+
return unwrap(await this.request(project, "GET", "/api/config"));
|
|
14219
14209
|
}
|
|
14220
14210
|
};
|
|
14211
|
+
function unwrap(value) {
|
|
14212
|
+
return value && typeof value === "object" && "data" in value ? value.data : value;
|
|
14213
|
+
}
|
|
14221
14214
|
|
|
14222
14215
|
// src/opencode.ts
|
|
14216
|
+
var UNSUPPORTED = /* @__PURE__ */ new Set([
|
|
14217
|
+
"runtime.stop",
|
|
14218
|
+
"runtime.restart",
|
|
14219
|
+
"runtime.logs"
|
|
14220
|
+
]);
|
|
14223
14221
|
var OpenCodeAdapter = class {
|
|
14224
14222
|
constructor(runtime, workspace, uploads, emit) {
|
|
14225
14223
|
this.runtime = runtime;
|
|
@@ -14230,23 +14228,26 @@ var OpenCodeAdapter = class {
|
|
|
14230
14228
|
this.media = new RemoteMedia(workspace, () => uploads.limits.imageBytes);
|
|
14231
14229
|
}
|
|
14232
14230
|
supportedOperations() {
|
|
14233
|
-
return Operations.filter((
|
|
14231
|
+
return Operations.filter((operation) => !UNSUPPORTED.has(operation));
|
|
14234
14232
|
}
|
|
14235
14233
|
auth = new AuthFlows();
|
|
14236
|
-
authDirty = /* @__PURE__ */ new Set();
|
|
14237
|
-
authRefreshing = /* @__PURE__ */ new Map();
|
|
14238
14234
|
streams = /* @__PURE__ */ new Map();
|
|
14239
14235
|
terminals = /* @__PURE__ */ new Map();
|
|
14240
|
-
// OpenCode 1.1.32 only buffers output while no client is attached.
|
|
14241
|
-
// Keep a bounded recent tail for panel remounts; never persist terminal text.
|
|
14242
14236
|
terminalOutput = /* @__PURE__ */ new Map();
|
|
14243
14237
|
resolving = /* @__PURE__ */ new Set();
|
|
14244
14238
|
config;
|
|
14245
14239
|
media;
|
|
14246
14240
|
async request(project, method, path, body, signal) {
|
|
14247
|
-
const root = await this.workspace.root(project)
|
|
14248
|
-
url
|
|
14249
|
-
|
|
14241
|
+
const root = await this.workspace.root(project);
|
|
14242
|
+
const url = new URL(path, this.runtime.config.tool.baseUrl);
|
|
14243
|
+
if (path === "/api/session") url.searchParams.set("directory", root);
|
|
14244
|
+
else if (/^\/api\/(?:agent|model|provider|mcp|command|config|pty|permission\/request|form|integration|location|experimental\/mcp)(?:\/|$)/.test(path)) url.searchParams.set("location[directory]", root);
|
|
14245
|
+
const response = await fetch(url, {
|
|
14246
|
+
method,
|
|
14247
|
+
headers: { ...this.runtime.headers(), ...body === void 0 ? {} : { "Content-Type": "application/json" } },
|
|
14248
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
14249
|
+
signal: signal ?? AbortSignal.timeout(35e3)
|
|
14250
|
+
}).catch((error) => {
|
|
14250
14251
|
if (method !== "GET") {
|
|
14251
14252
|
const unknown = new Error("Transport interrupted; inspect remote state before retrying");
|
|
14252
14253
|
unknown.name = "UnknownResultError";
|
|
@@ -14259,11 +14260,15 @@ var OpenCodeAdapter = class {
|
|
|
14259
14260
|
const text = await response.text();
|
|
14260
14261
|
return text ? JSON.parse(text) : null;
|
|
14261
14262
|
}
|
|
14263
|
+
async data(project, method, path, body, signal) {
|
|
14264
|
+
return unwrap2(await this.request(project, method, path, body, signal));
|
|
14265
|
+
}
|
|
14262
14266
|
async session(project, id) {
|
|
14263
14267
|
if (!/^ses_[A-Za-z0-9]+$/.test(id)) throw new Error("Invalid session id");
|
|
14264
|
-
const session = await this.
|
|
14265
|
-
|
|
14266
|
-
|
|
14268
|
+
const session = await this.data(project, "GET", `/api/session/${encodeURIComponent(id)}`);
|
|
14269
|
+
const directory = session?.location?.directory ?? session?.directory;
|
|
14270
|
+
if (typeof directory !== "string" || await realpath4(directory) !== await this.workspace.root(project)) throw new Error("Session belongs to another project");
|
|
14271
|
+
return normalizeSession(session);
|
|
14267
14272
|
}
|
|
14268
14273
|
identifier(value) {
|
|
14269
14274
|
if (typeof value !== "string" || !/^[A-Za-z0-9_.:-]+$/.test(value)) throw new Error("Invalid resource identifier");
|
|
@@ -14281,164 +14286,193 @@ var OpenCodeAdapter = class {
|
|
|
14281
14286
|
if (!id) throw new Error("Session required");
|
|
14282
14287
|
await this.session(project, id);
|
|
14283
14288
|
}
|
|
14284
|
-
const base = `/session/${encodeURIComponent(id ?? "")}`;
|
|
14289
|
+
const base = `/api/session/${encodeURIComponent(id ?? "")}`;
|
|
14290
|
+
if (op === "sessions.prompt") return this.prompt(project, id, p, base);
|
|
14291
|
+
if (op === "sessions.command" || op === "sessions.shell") {
|
|
14292
|
+
await this.switchSelection(project, base, p);
|
|
14293
|
+
if (op === "sessions.shell") return this.data(project, "POST", `${base}/shell`, { ...p.id ? { id: p.id } : {}, command: p.command });
|
|
14294
|
+
return this.data(project, "POST", `${base}/command`, { name: p.name ?? p.command, text: p.text ?? p.arguments ?? "", delivery: "queue", ...Array.isArray(p.files) ? { files: p.files } : {}, ...Array.isArray(p.agents) ? { agents: p.agents } : {}, ...Array.isArray(p.skills) ? { skills: p.skills } : {} });
|
|
14295
|
+
}
|
|
14296
|
+
if (op === "permissions.list") return this.permissions(project, id);
|
|
14297
|
+
if (op === "questions.list") return this.forms(project, id);
|
|
14298
|
+
if (op === "permissions.reply" || op === "questions.reply" || op === "questions.reject") return this.resolveInteraction(project, id, op, p);
|
|
14299
|
+
if (op === "configuration.models") return this.models(project);
|
|
14300
|
+
if (op === "configuration.patch") return redact(await this.config.update(project, p));
|
|
14301
|
+
if (op === "configuration.mcpAdd") {
|
|
14302
|
+
this.identifier(p.name);
|
|
14303
|
+
await this.config.update(project, { mcp: { servers: { [String(p.name)]: p.config } } });
|
|
14304
|
+
return this.data(project, "GET", "/api/mcp");
|
|
14305
|
+
}
|
|
14306
|
+
if (op === "configuration.mcpConnect" || op === "configuration.mcpDisconnect") {
|
|
14307
|
+
return this.data(project, "POST", `/api/experimental/mcp/${this.identifier(p.name)}/${op.endsWith("Disconnect") ? "disconnect" : "connect"}`);
|
|
14308
|
+
}
|
|
14309
|
+
if (op === "terminal.create") return this.data(project, "POST", "/api/pty", { ...p, cwd: await this.workspace.path(project, typeof p.cwd === "string" ? p.cwd : ".") });
|
|
14310
|
+
if (op.startsWith("terminal.") && !["terminal.list", "terminal.create"].includes(op)) return this.terminal(project, op, p);
|
|
14285
14311
|
const routes = {
|
|
14286
|
-
"sessions.list": ["GET", "/session"],
|
|
14287
|
-
"sessions.status": ["GET", "/session/
|
|
14288
|
-
"sessions.create": ["POST", "/session"],
|
|
14312
|
+
"sessions.list": ["GET", "/api/session"],
|
|
14313
|
+
"sessions.status": ["GET", "/api/session/active"],
|
|
14314
|
+
"sessions.create": ["POST", "/api/session", p],
|
|
14289
14315
|
"sessions.get": ["GET", base],
|
|
14290
14316
|
"sessions.messages": ["GET", `${base}/message`],
|
|
14291
|
-
"sessions.abort": ["POST", `${base}/
|
|
14317
|
+
"sessions.abort": ["POST", `${base}/interrupt`],
|
|
14292
14318
|
"sessions.delete": ["DELETE", base],
|
|
14293
|
-
"sessions.rename": ["PATCH", base],
|
|
14294
|
-
"sessions.fork": ["POST", `${base}/fork
|
|
14319
|
+
"sessions.rename": ["PATCH", base, { title: p.title }],
|
|
14320
|
+
"sessions.fork": ["POST", `${base}/fork`, p.before || p.messageID ? { before: p.before ?? p.messageID } : {}],
|
|
14295
14321
|
"sessions.diff": ["GET", `${base}/diff`],
|
|
14296
|
-
"sessions.revert": ["POST", `${base}/revert
|
|
14297
|
-
"sessions.unrevert": ["
|
|
14298
|
-
"sessions.summarize": ["POST", `${base}/
|
|
14299
|
-
"
|
|
14300
|
-
"
|
|
14301
|
-
"
|
|
14302
|
-
"
|
|
14303
|
-
"
|
|
14304
|
-
"
|
|
14305
|
-
"permissions.list": ["GET", "/permission"],
|
|
14306
|
-
"questions.list": ["GET", "/question"],
|
|
14307
|
-
"configuration.get": ["GET", "/config"],
|
|
14308
|
-
"configuration.patch": ["PATCH", "/config"],
|
|
14309
|
-
"configuration.models": ["GET", "/config/providers"],
|
|
14310
|
-
"configuration.providers": ["GET", "/provider"],
|
|
14311
|
-
"configuration.agents": ["GET", "/agent"],
|
|
14312
|
-
"configuration.commands": ["GET", "/command"],
|
|
14313
|
-
"configuration.mcp": ["GET", "/mcp"],
|
|
14314
|
-
"configuration.mcpAdd": ["POST", "/mcp"],
|
|
14315
|
-
"terminal.list": ["GET", "/pty"],
|
|
14316
|
-
"terminal.create": ["POST", "/pty"]
|
|
14322
|
+
"sessions.revert": ["POST", `${base}/revert/stage`, p],
|
|
14323
|
+
"sessions.unrevert": ["DELETE", `${base}/revert`],
|
|
14324
|
+
"sessions.summarize": ["POST", `${base}/compact`, {}],
|
|
14325
|
+
"configuration.get": ["GET", "/api/config"],
|
|
14326
|
+
"configuration.providers": ["GET", "/api/provider"],
|
|
14327
|
+
"configuration.agents": ["GET", "/api/agent"],
|
|
14328
|
+
"configuration.commands": ["GET", "/api/command"],
|
|
14329
|
+
"configuration.mcp": ["GET", "/api/mcp"],
|
|
14330
|
+
"terminal.list": ["GET", "/api/pty"]
|
|
14317
14331
|
};
|
|
14318
|
-
if (op === "configuration.models") {
|
|
14319
|
-
await this.refreshAuth(project);
|
|
14320
|
-
const result2 = await this.request(project, "GET", "/provider");
|
|
14321
|
-
return redact({ providers: result2.all.filter((provider) => result2.connected.includes(provider.id)) });
|
|
14322
|
-
}
|
|
14323
|
-
if (op === "sessions.prompt") {
|
|
14324
|
-
const parts = [];
|
|
14325
|
-
if (typeof p.text === "string" && p.text.trim()) parts.push({ type: "text", text: screenshotPrompt(p.text) });
|
|
14326
|
-
for (const uploadId of Array.isArray(p.attachments) ? p.attachments : []) {
|
|
14327
|
-
const upload = await this.uploads.attachment(project, String(uploadId), id);
|
|
14328
|
-
if (upload.mime.startsWith("image/") && upload.mime !== "image/svg+xml" || upload.mime === "application/pdf") parts.push({ type: "file", mime: upload.mime, filename: upload.name, url: pathToFileURL(await this.workspace.path(project, upload.destination)).href });
|
|
14329
|
-
else parts.push({ type: "text", text: `Attached file: ${upload.destination}. Read this file if relevant to the task.` });
|
|
14330
|
-
}
|
|
14331
|
-
if (!parts.length) throw new Error("Message or attachments required");
|
|
14332
|
-
return this.request(project, "POST", `${base}/prompt_async`, { parts, ...p.agent ? { agent: p.agent } : {}, ...p.model ? { model: p.model } : {}, ...p.messageID ? { messageID: p.messageID } : {} });
|
|
14333
|
-
}
|
|
14334
|
-
if (op === "permissions.reply" || op === "questions.reply" || op === "questions.reject") {
|
|
14335
|
-
const prefix = op.startsWith("permissions") ? "permission" : "question";
|
|
14336
|
-
const requestId = this.identifier(p.requestId), key = `${prefix}:${requestId}`;
|
|
14337
|
-
if (this.resolving.has(key)) throw new Error("Request is already being resolved by another client");
|
|
14338
|
-
this.resolving.add(key);
|
|
14339
|
-
try {
|
|
14340
|
-
const pending = await this.request(project, "GET", `/${prefix}`);
|
|
14341
|
-
const item = pending.find((x) => x.id === p.requestId);
|
|
14342
|
-
if (!item) throw new Error("Request is no longer pending");
|
|
14343
|
-
if (id && id !== item.sessionID) throw new Error("Request belongs to another session");
|
|
14344
|
-
await this.session(project, item.sessionID);
|
|
14345
|
-
const body = prefix === "permission" ? { reply: p.reply, message: p.message } : { answers: p.answers };
|
|
14346
|
-
return await this.request(project, "POST", `/${prefix}/${requestId}/${op.endsWith("reject") ? "reject" : "reply"}`, body);
|
|
14347
|
-
} finally {
|
|
14348
|
-
this.resolving.delete(key);
|
|
14349
|
-
}
|
|
14350
|
-
}
|
|
14351
|
-
if (op === "terminal.create") return this.request(project, "POST", "/pty", { ...p, cwd: await this.workspace.path(project, typeof p.cwd === "string" ? p.cwd : ".") });
|
|
14352
|
-
if (op === "configuration.patch") return redact(await this.config.update(project, p));
|
|
14353
|
-
if (op === "configuration.mcpAdd") {
|
|
14354
|
-
this.identifier(p.name);
|
|
14355
|
-
await this.config.update(project, { mcp: { [String(p.name)]: p.config } });
|
|
14356
|
-
return this.request(project, "GET", "/mcp");
|
|
14357
|
-
}
|
|
14358
|
-
if (op.startsWith("terminal.") && !routes[op]) return this.terminal(project, op, p);
|
|
14359
|
-
if (op === "configuration.mcpConnect" || op === "configuration.mcpDisconnect") return this.request(project, "POST", `/mcp/${this.identifier(p.name)}/${op.endsWith("Disconnect") ? "disconnect" : "connect"}`);
|
|
14360
|
-
if (op === "configuration.auth") return this.request(project, "PUT", `/auth/${this.identifier(p.providerId)}`, p.credentials);
|
|
14361
|
-
if (op === "configuration.oauthAuthorize" || op === "configuration.oauthCallback") return this.request(project, "POST", `/provider/${this.identifier(p.providerId)}/oauth/${op.endsWith("Authorize") ? "authorize" : "callback"}`, p.input);
|
|
14362
14332
|
const route = routes[op];
|
|
14363
14333
|
if (!route) throw new Error(`Unsupported OpenCode operation: ${op}`);
|
|
14364
|
-
const result = await this.
|
|
14365
|
-
if (op === "sessions.list")
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
} catch {
|
|
14371
|
-
return false;
|
|
14372
|
-
}
|
|
14373
|
-
}));
|
|
14374
|
-
return result.filter((_, index) => matches[index]);
|
|
14375
|
-
}
|
|
14376
|
-
if (["configuration.get", "configuration.models", "configuration.providers"].includes(op)) return redact(result);
|
|
14377
|
-
if (op === "sessions.messages" && Array.isArray(result)) return this.media.messages(project, id, result);
|
|
14378
|
-
if (op.startsWith("sessions.")) return this.media.normalize(project, result);
|
|
14334
|
+
const result = await this.data(project, route[0], route[1], route[2]);
|
|
14335
|
+
if (op === "sessions.list") return (Array.isArray(result) ? result : []).map(normalizeSession);
|
|
14336
|
+
if (op === "sessions.status") return Object.fromEntries(Object.entries(result ?? {}).map(([key, value]) => [key, { type: value?.type === "running" ? "busy" : value?.type ?? "idle" }]));
|
|
14337
|
+
if (op === "sessions.messages") return this.media.messages(project, id, (Array.isArray(result) ? result : []).map(normalizeMessage));
|
|
14338
|
+
if (["configuration.get", "configuration.providers"].includes(op)) return redact(result);
|
|
14339
|
+
if (op.startsWith("sessions.")) return this.media.normalize(project, normalizeSession(result));
|
|
14379
14340
|
return result;
|
|
14380
14341
|
}
|
|
14381
|
-
async
|
|
14382
|
-
|
|
14383
|
-
const
|
|
14384
|
-
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
})
|
|
14391
|
-
|
|
14342
|
+
async prompt(project, session, p, base) {
|
|
14343
|
+
const text = typeof p.text === "string" ? screenshotPrompt(p.text) : "";
|
|
14344
|
+
const files = [];
|
|
14345
|
+
for (const uploadId of Array.isArray(p.attachments) ? p.attachments : []) {
|
|
14346
|
+
const upload = await this.uploads.attachment(project, String(uploadId), session);
|
|
14347
|
+
files.push({ uri: pathToFileURL(await this.workspace.path(project, upload.destination)).href, name: upload.name });
|
|
14348
|
+
}
|
|
14349
|
+
if (!text.trim() && !files.length) throw new Error("Message or attachments required");
|
|
14350
|
+
await this.switchSelection(project, base, p);
|
|
14351
|
+
return this.data(project, "POST", `${base}/prompt`, { text, files, delivery: "queue", ...p.messageID ? { id: p.messageID } : {} });
|
|
14352
|
+
}
|
|
14353
|
+
async switchSelection(project, base, payload) {
|
|
14354
|
+
if (payload.agent) await this.data(project, "POST", `${base}/agent`, { agent: payload.agent });
|
|
14355
|
+
if (!payload.model) return;
|
|
14356
|
+
let model;
|
|
14357
|
+
if (typeof payload.model === "string") {
|
|
14358
|
+
const split = payload.model.indexOf("/");
|
|
14359
|
+
if (split < 1 || split === payload.model.length - 1) throw new Error("Invalid model identifier");
|
|
14360
|
+
model = { providerID: payload.model.slice(0, split), id: payload.model.slice(split + 1) };
|
|
14361
|
+
} else if (typeof payload.model === "object") {
|
|
14362
|
+
const input = payload.model;
|
|
14363
|
+
model = { providerID: input.providerID, id: input.modelID ?? input.id, ...input.variant ? { variant: input.variant } : {} };
|
|
14364
|
+
} else throw new Error("Invalid model identifier");
|
|
14365
|
+
await this.data(project, "POST", `${base}/model`, { model });
|
|
14366
|
+
}
|
|
14367
|
+
async models(project) {
|
|
14368
|
+
const models = await this.data(project, "GET", "/api/model");
|
|
14369
|
+
const providers = /* @__PURE__ */ new Map();
|
|
14370
|
+
for (const model of Array.isArray(models) ? models : []) {
|
|
14371
|
+
if (model.enabled === false || model.status === "deprecated") continue;
|
|
14372
|
+
const modelId = model.id ?? model.modelID;
|
|
14373
|
+
if (typeof model.providerID !== "string" || typeof modelId !== "string") continue;
|
|
14374
|
+
const provider = providers.get(model.providerID) ?? { id: model.providerID, models: {} };
|
|
14375
|
+
provider.models[modelId] = { ...model, id: modelId };
|
|
14376
|
+
providers.set(model.providerID, provider);
|
|
14377
|
+
}
|
|
14378
|
+
return redact({ providers: [...providers.values()] });
|
|
14379
|
+
}
|
|
14380
|
+
async permissions(project, session) {
|
|
14381
|
+
const entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/permission` : "/api/permission/request");
|
|
14382
|
+
return (Array.isArray(entries) ? entries : []).map((entry) => ({ ...entry, permission: entry.action, patterns: entry.resources ?? [], replies: ["once", "always", "reject"] }));
|
|
14383
|
+
}
|
|
14384
|
+
async forms(project, session) {
|
|
14385
|
+
const entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/form` : "/api/form");
|
|
14386
|
+
return (Array.isArray(entries) ? entries : []).map(formToQuestion);
|
|
14387
|
+
}
|
|
14388
|
+
async resolveInteraction(project, session, operation, payload) {
|
|
14389
|
+
const kind = operation.startsWith("permissions") ? "permission" : "form";
|
|
14390
|
+
const requestId = this.identifier(payload.requestId);
|
|
14391
|
+
const key = `${kind}:${requestId}`;
|
|
14392
|
+
if (this.resolving.has(key)) throw new Error("Request is already being resolved by another client");
|
|
14393
|
+
this.resolving.add(key);
|
|
14392
14394
|
try {
|
|
14393
|
-
await
|
|
14395
|
+
const pending = kind === "permission" ? await this.permissions(project) : await this.forms(project);
|
|
14396
|
+
const item = pending.find((entry) => entry.id === payload.requestId);
|
|
14397
|
+
if (!item) throw new Error("Request is no longer pending");
|
|
14398
|
+
if (session && session !== item.sessionID) throw new Error("Request belongs to another session");
|
|
14399
|
+
await this.session(project, item.sessionID);
|
|
14400
|
+
const base = `/api/session/${encodeURIComponent(item.sessionID)}/${kind}/${requestId}`;
|
|
14401
|
+
if (kind === "permission") {
|
|
14402
|
+
const decision = payload.reply === "deny" ? "reject" : payload.reply;
|
|
14403
|
+
return this.data(project, "POST", `${base}/reply`, { decision, ...payload.message ? { message: payload.message } : {} });
|
|
14404
|
+
}
|
|
14405
|
+
if (operation === "questions.reject") return this.data(project, "DELETE", base);
|
|
14406
|
+
const fields = item.fields;
|
|
14407
|
+
const answers = Array.isArray(payload.answers) ? payload.answers : [];
|
|
14408
|
+
const answer = Object.fromEntries(fields.map((field, index) => [field.key, formValue(field, answers[index] ?? [])]));
|
|
14409
|
+
return this.data(project, "POST", `${base}/reply`, { answer });
|
|
14394
14410
|
} finally {
|
|
14395
|
-
this.
|
|
14411
|
+
this.resolving.delete(key);
|
|
14396
14412
|
}
|
|
14397
14413
|
}
|
|
14398
14414
|
async authentication(project, operation, payload) {
|
|
14399
14415
|
if (operation === "auth.poll") return this.auth.poll(project, payload);
|
|
14400
14416
|
if (operation === "auth.cancel") return this.auth.cancel(project, payload.flowId);
|
|
14401
|
-
await this.
|
|
14402
|
-
const
|
|
14403
|
-
const providers = await this.request(project, "GET", "/provider");
|
|
14404
|
-
for (const provider2 of providers.all) if (!methods[provider2.id]?.length) methods[provider2.id] = [{ type: "api", label: "API Key" }];
|
|
14417
|
+
const integrations = await this.data(project, "GET", "/api/integration");
|
|
14418
|
+
const available = Array.isArray(integrations) ? integrations : [];
|
|
14405
14419
|
if (operation === "auth.status") {
|
|
14406
|
-
const
|
|
14407
|
-
|
|
14408
|
-
label: `${
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
return { status: providers.
|
|
14412
|
-
}
|
|
14413
|
-
const methodId = String(payload.method ?? "")
|
|
14414
|
-
const
|
|
14415
|
-
const
|
|
14416
|
-
|
|
14420
|
+
const methods = available.flatMap((integration2) => (integration2.methods ?? []).flatMap((method2, index2) => {
|
|
14421
|
+
if (!["key", "oauth"].includes(method2.type)) return [];
|
|
14422
|
+
return [{ id: `${integration2.id}:${index2}`, label: `${integration2.name ?? integration2.id} \xB7 ${method2.label ?? (method2.type === "key" ? "API Key" : "OAuth")}`, fields: method2.type === "key" ? [{ id: "key", label: "API Key", secret: true }, ...formFields(method2.form) ?? []] : formFields(method2.form) }];
|
|
14423
|
+
}));
|
|
14424
|
+
const providers = available.filter((integration2) => integration2.connections?.length).map((integration2) => integration2.id);
|
|
14425
|
+
return { status: providers.length ? "authenticated" : "unauthenticated", message: providers.length ? "OpenCode \u5DF2\u914D\u7F6E\u4F9B\u5E94\u5546" : "OpenCode \u672A\u914D\u7F6E\u53EF\u7528\u4F9B\u5E94\u5546", providers, methods };
|
|
14426
|
+
}
|
|
14427
|
+
const methodId = String(payload.method ?? "");
|
|
14428
|
+
const split = methodId.lastIndexOf(":");
|
|
14429
|
+
const integration = available.find((entry) => entry.id === methodId.slice(0, split));
|
|
14430
|
+
const index = Number(methodId.slice(split + 1));
|
|
14431
|
+
const method = integration?.methods?.[index];
|
|
14432
|
+
if (split < 0 || !method || !["key", "oauth"].includes(method.type)) throw new Error("\u4E0D\u652F\u6301\u7684 OpenCode \u767B\u5F55\u65B9\u5F0F");
|
|
14417
14433
|
const input = payload.input ?? {};
|
|
14418
14434
|
const controller = new AbortController();
|
|
14419
|
-
|
|
14435
|
+
let attemptId = "";
|
|
14436
|
+
const flow = this.auth.create(project, async () => {
|
|
14437
|
+
controller.abort();
|
|
14438
|
+
if (attemptId) await this.request(project, "DELETE", `/api/integration/${this.identifier(integration.id)}/connect/oauth/${this.identifier(attemptId)}`).catch(() => {
|
|
14439
|
+
});
|
|
14440
|
+
});
|
|
14441
|
+
const complete = () => this.auth.update(project, flow.flowId, { status: "completed", message: "\u8BA4\u8BC1\u4FE1\u606F\u5DF2\u7531 OpenCode \u4FDD\u5B58" });
|
|
14442
|
+
const wait = async () => {
|
|
14443
|
+
while (!controller.signal.aborted) {
|
|
14444
|
+
const state = await this.data(project, "GET", `/api/integration/${this.identifier(integration.id)}/connect/oauth/${this.identifier(attemptId)}`, void 0, controller.signal);
|
|
14445
|
+
if (state.status === "complete") {
|
|
14446
|
+
complete();
|
|
14447
|
+
return;
|
|
14448
|
+
}
|
|
14449
|
+
if (["failed", "expired"].includes(state.status)) throw new Error("OpenCode OAuth failed");
|
|
14450
|
+
await new Promise((resolve4, reject) => {
|
|
14451
|
+
const timer = setTimeout(resolve4, 1e3);
|
|
14452
|
+
controller.signal.addEventListener("abort", () => {
|
|
14453
|
+
clearTimeout(timer);
|
|
14454
|
+
reject(controller.signal.reason);
|
|
14455
|
+
}, { once: true });
|
|
14456
|
+
});
|
|
14457
|
+
}
|
|
14458
|
+
};
|
|
14420
14459
|
const run3 = async () => {
|
|
14421
|
-
if (method.type === "
|
|
14460
|
+
if (method.type === "key") {
|
|
14422
14461
|
if (typeof input.key !== "string" || !input.key.trim()) throw new Error("API Key required");
|
|
14423
|
-
await this.request(project, "
|
|
14424
|
-
|
|
14425
|
-
await this.refreshAuth(project);
|
|
14426
|
-
this.auth.update(project, flow.flowId, { status: "completed", message: this.authDirty.has(project) ? "\u8BA4\u8BC1\u4FE1\u606F\u5DF2\u4FDD\u5B58\uFF1B\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u540E\u5237\u65B0\u6A21\u578B\u5217\u8868" : "\u8BA4\u8BC1\u4FE1\u606F\u5DF2\u7531 OpenCode \u4FDD\u5B58" });
|
|
14462
|
+
await this.request(project, "POST", `/api/integration/${this.identifier(integration.id)}/connect/key`, { key: input.key, ...method.label ? { label: method.label } : {}, ...Object.keys(input).length > 1 ? { answer: input } : {} }, controller.signal);
|
|
14463
|
+
complete();
|
|
14427
14464
|
return;
|
|
14428
14465
|
}
|
|
14429
|
-
const
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
for (const entry of this.runtime.config.projects) this.authDirty.add(entry.id);
|
|
14434
|
-
await this.refreshAuth(project);
|
|
14435
|
-
this.auth.update(project, flow.flowId, { status: "completed", message: this.authDirty.has(project) ? "\u767B\u5F55\u6210\u529F\uFF1B\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u540E\u5237\u65B0\u6A21\u578B\u5217\u8868" : "\u767B\u5F55\u6210\u529F" });
|
|
14436
|
-
};
|
|
14437
|
-
if (result.method === "auto") await callback();
|
|
14438
|
-
else {
|
|
14466
|
+
const attempt = await this.data(project, "POST", `/api/integration/${this.identifier(integration.id)}/connect/oauth`, { methodID: method.id, ...method.label ? { label: method.label } : {}, ...Object.keys(input).length ? { answer: input } : {} }, controller.signal);
|
|
14467
|
+
attemptId = attempt.attemptID;
|
|
14468
|
+
this.auth.update(project, flow.flowId, { url: attempt.url, message: attempt.instructions });
|
|
14469
|
+
if (attempt.mode === "code") {
|
|
14439
14470
|
this.auth.update(project, flow.flowId, { status: "input", fields: [{ id: "code", label: "\u6388\u6743\u7801\u6216\u56DE\u8C03\u5730\u5740", secret: true }] });
|
|
14440
|
-
this.auth.get(project, flow.flowId).submit = (values) =>
|
|
14441
|
-
|
|
14471
|
+
this.auth.get(project, flow.flowId).submit = async (values) => {
|
|
14472
|
+
await this.request(project, "POST", `/api/integration/${this.identifier(integration.id)}/connect/oauth/${this.identifier(attemptId)}/complete`, { code: values.code }, controller.signal);
|
|
14473
|
+
await wait();
|
|
14474
|
+
};
|
|
14475
|
+
} else await wait();
|
|
14442
14476
|
};
|
|
14443
14477
|
void run3().catch(() => this.auth.update(project, flow.flowId, { status: "failed", message: "OpenCode \u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8BA4\u8BC1\u4FE1\u606F\u4E0E\u4F9B\u5E94\u5546\u914D\u7F6E\u540E\u91CD\u8BD5" }));
|
|
14444
14478
|
return { ...flow };
|
|
@@ -14448,11 +14482,10 @@ var OpenCodeAdapter = class {
|
|
|
14448
14482
|
const controller = new AbortController();
|
|
14449
14483
|
this.streams.set(project, controller);
|
|
14450
14484
|
const loop = async () => {
|
|
14485
|
+
const root = await this.workspace.root(project);
|
|
14451
14486
|
while (!controller.signal.aborted) {
|
|
14452
14487
|
try {
|
|
14453
|
-
const
|
|
14454
|
-
url.searchParams.set("directory", await this.workspace.root(project));
|
|
14455
|
-
const response = await fetch(url, { headers: this.runtime.headers(), signal: controller.signal });
|
|
14488
|
+
const response = await fetch(new URL("/api/event", this.runtime.config.tool.baseUrl), { headers: this.runtime.headers(), signal: controller.signal });
|
|
14456
14489
|
if (!response.ok || !response.body) throw new Error("Event connection unavailable");
|
|
14457
14490
|
this.emit(project, "octopus.resync", {});
|
|
14458
14491
|
let buffer = "";
|
|
@@ -14464,10 +14497,10 @@ var OpenCodeAdapter = class {
|
|
|
14464
14497
|
while ((end = buffer.indexOf("\n\n")) >= 0) {
|
|
14465
14498
|
const block = buffer.slice(0, end);
|
|
14466
14499
|
buffer = buffer.slice(end + 2);
|
|
14467
|
-
const data = block.split("\n").filter((
|
|
14500
|
+
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
14468
14501
|
if (data) {
|
|
14469
14502
|
const event = JSON.parse(data);
|
|
14470
|
-
this.emit(project, event.type, await this.media.normalize(project, event.
|
|
14503
|
+
if (!event.location?.directory || event.location.directory === root) this.emit(project, event.type === "server.connected" ? "octopus.resync" : event.type, await this.media.normalize(project, event.data ?? {}));
|
|
14471
14504
|
}
|
|
14472
14505
|
}
|
|
14473
14506
|
if (buffer.length > Math.max(4 * 1024 * 1024, Math.ceil(this.uploads.limits.fileBytes * 4 / 3) + 1024 * 1024)) throw new Error("Event too large");
|
|
@@ -14482,15 +14515,15 @@ var OpenCodeAdapter = class {
|
|
|
14482
14515
|
}
|
|
14483
14516
|
async terminal(project, op, p) {
|
|
14484
14517
|
const id = this.identifier(p.id), key = `${project}:${id}`;
|
|
14485
|
-
await this.
|
|
14518
|
+
await this.data(project, "GET", `/api/pty/${id}`);
|
|
14486
14519
|
if (op === "terminal.remove") {
|
|
14487
|
-
const result = await this.
|
|
14520
|
+
const result = await this.data(project, "DELETE", `/api/pty/${id}`);
|
|
14488
14521
|
this.terminals.get(key)?.close();
|
|
14489
14522
|
this.terminals.delete(key);
|
|
14490
14523
|
this.terminalOutput.delete(key);
|
|
14491
14524
|
return result;
|
|
14492
14525
|
}
|
|
14493
|
-
if (op === "terminal.resize") return this.
|
|
14526
|
+
if (op === "terminal.resize") return this.data(project, "PUT", `/api/pty/${id}`, { size: { cols: p.cols, rows: p.rows } });
|
|
14494
14527
|
if (op === "terminal.attach") {
|
|
14495
14528
|
const history = this.terminalOutput.get(key) ?? "";
|
|
14496
14529
|
this.emit(project, "terminal.reset", { id: p.id });
|
|
@@ -14498,19 +14531,18 @@ var OpenCodeAdapter = class {
|
|
|
14498
14531
|
const previous = this.terminals.get(key);
|
|
14499
14532
|
this.terminals.delete(key);
|
|
14500
14533
|
previous?.close();
|
|
14501
|
-
const url = new URL(`/pty/${id}/connect`, this.runtime.config.tool.baseUrl);
|
|
14502
|
-
url.protocol = "ws:";
|
|
14503
|
-
url.searchParams.set("directory", await this.workspace.root(project));
|
|
14534
|
+
const url = new URL(`/api/pty/${id}/connect`, this.runtime.config.tool.baseUrl);
|
|
14535
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
14536
|
+
url.searchParams.set("location[directory]", await this.workspace.root(project));
|
|
14504
14537
|
const socket2 = new WebSocket(url, { headers: this.runtime.headers() });
|
|
14505
14538
|
this.terminals.set(key, socket2);
|
|
14506
14539
|
socket2.on("message", (data) => {
|
|
14507
|
-
if (this.terminals.get(key)
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
}
|
|
14540
|
+
if (this.terminals.get(key) !== socket2) return;
|
|
14541
|
+
const text = data.toString(), tail = ((this.terminalOutput.get(key) ?? "") + text).slice(-1048576);
|
|
14542
|
+
this.terminalOutput.delete(key);
|
|
14543
|
+
this.terminalOutput.set(key, tail);
|
|
14544
|
+
if (this.terminalOutput.size > 16) this.terminalOutput.delete(this.terminalOutput.keys().next().value);
|
|
14545
|
+
this.emit(project, "terminal.output", { id: p.id, data: text });
|
|
14514
14546
|
});
|
|
14515
14547
|
socket2.on("close", () => {
|
|
14516
14548
|
if (this.terminals.get(key) === socket2) {
|
|
@@ -14534,13 +14566,76 @@ var OpenCodeAdapter = class {
|
|
|
14534
14566
|
}
|
|
14535
14567
|
close() {
|
|
14536
14568
|
this.auth.close();
|
|
14537
|
-
for (const
|
|
14538
|
-
for (const
|
|
14569
|
+
for (const controller of this.streams.values()) controller.abort();
|
|
14570
|
+
for (const terminal of this.terminals.values()) terminal.close();
|
|
14539
14571
|
}
|
|
14540
14572
|
};
|
|
14573
|
+
function unwrap2(value) {
|
|
14574
|
+
return value && typeof value === "object" && "data" in value ? value.data : value;
|
|
14575
|
+
}
|
|
14576
|
+
function normalizeSession(value) {
|
|
14577
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
14578
|
+
return { ...value, ...value.location?.directory ? { directory: value.location.directory } : {} };
|
|
14579
|
+
}
|
|
14580
|
+
function normalizeMessage(message) {
|
|
14581
|
+
if (message?.info && Array.isArray(message.parts)) return message;
|
|
14582
|
+
const role = message?.type === "user" ? "user" : "assistant";
|
|
14583
|
+
const info = { ...message, role };
|
|
14584
|
+
delete info.type;
|
|
14585
|
+
delete info.text;
|
|
14586
|
+
delete info.files;
|
|
14587
|
+
delete info.content;
|
|
14588
|
+
if (message?.model) info.model = { ...message.model, modelID: message.model.id };
|
|
14589
|
+
const parts = [];
|
|
14590
|
+
if (typeof message?.text === "string" && message.text) parts.push({ type: "text", text: message.text });
|
|
14591
|
+
for (const file of message?.files ?? []) parts.push({ type: "file", filename: file.name, url: file.uri });
|
|
14592
|
+
if (message?.type === "shell") parts.push({ type: "tool", tool: "shell", state: { status: message.time?.completed ? "completed" : "running", input: { command: message.command }, output: message.output } });
|
|
14593
|
+
if (message?.type === "compaction") {
|
|
14594
|
+
info.summary = true;
|
|
14595
|
+
parts.push({ type: "text", text: message.summary });
|
|
14596
|
+
}
|
|
14597
|
+
if (message?.type === "agent-switched") parts.push({ type: "text", text: `Agent: ${message.agent}` });
|
|
14598
|
+
if (message?.type === "model-switched") parts.push({ type: "text", text: `Model: ${message.model?.providerID}/${message.model?.id}` });
|
|
14599
|
+
for (const part of message?.content ?? []) {
|
|
14600
|
+
if (part.type === "tool") {
|
|
14601
|
+
const state = { ...part.state };
|
|
14602
|
+
if (state.content !== void 0 && state.output === void 0) state.output = state.content;
|
|
14603
|
+
parts.push({ ...part, tool: part.name ?? part.tool, state });
|
|
14604
|
+
} else parts.push(part);
|
|
14605
|
+
}
|
|
14606
|
+
return { info, parts };
|
|
14607
|
+
}
|
|
14608
|
+
function formToQuestion(form) {
|
|
14609
|
+
const fields = (form.fields ?? []).filter((field) => !field.hidden);
|
|
14610
|
+
return {
|
|
14611
|
+
...form,
|
|
14612
|
+
fields,
|
|
14613
|
+
questions: fields.map((field) => ({
|
|
14614
|
+
key: field.key,
|
|
14615
|
+
header: field.title ?? form.title ?? field.key,
|
|
14616
|
+
question: field.description ?? field.title ?? field.key,
|
|
14617
|
+
...field.options ? { options: field.options } : {},
|
|
14618
|
+
multiple: field.type === "multiselect",
|
|
14619
|
+
custom: field.custom ?? !field.options,
|
|
14620
|
+
isSecret: false
|
|
14621
|
+
}))
|
|
14622
|
+
};
|
|
14623
|
+
}
|
|
14624
|
+
function formValue(field, values) {
|
|
14625
|
+
if (field.type === "multiselect") return values;
|
|
14626
|
+
const value = values[0];
|
|
14627
|
+
if (field.type === "boolean") return value === true || value === "true";
|
|
14628
|
+
if (field.type === "number" || field.type === "integer") return Number(value);
|
|
14629
|
+
return value ?? "";
|
|
14630
|
+
}
|
|
14631
|
+
function formFields(form) {
|
|
14632
|
+
if (!form) return void 0;
|
|
14633
|
+
const fields = Array.isArray(form) ? form : form.fields;
|
|
14634
|
+
return (fields ?? []).filter((field) => !field.hidden).map((field) => ({ id: field.key, label: field.title ?? field.description ?? field.key, secret: Boolean(field.secret), ...field.options ? { options: field.options.map((option) => ({ label: option.label ?? option.value ?? String(option), value: option.value ?? String(option) })) } : {} }));
|
|
14635
|
+
}
|
|
14541
14636
|
function redact(value) {
|
|
14542
14637
|
if (Array.isArray(value)) return value.map(redact);
|
|
14543
|
-
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([
|
|
14638
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, /password|apikey|token|secret|authorization|^key$/i.test(key.replace(/[-_]/g, "")) ? "[redacted]" : redact(item)]));
|
|
14544
14639
|
return value;
|
|
14545
14640
|
}
|
|
14546
14641
|
|
|
@@ -14566,11 +14661,11 @@ var Runtime = class {
|
|
|
14566
14661
|
}
|
|
14567
14662
|
async health() {
|
|
14568
14663
|
try {
|
|
14569
|
-
const response = await fetch(new URL("/
|
|
14664
|
+
const response = await fetch(new URL("/api/info", this.config.tool.baseUrl), { headers: this.headers(), signal: AbortSignal.timeout(2e3) });
|
|
14570
14665
|
if (!response.ok) return false;
|
|
14571
14666
|
const data = await response.json();
|
|
14572
14667
|
this.version = data.version ?? this.version;
|
|
14573
|
-
return
|
|
14668
|
+
return true;
|
|
14574
14669
|
} catch {
|
|
14575
14670
|
return false;
|
|
14576
14671
|
}
|
|
@@ -14593,7 +14688,7 @@ var Runtime = class {
|
|
|
14593
14688
|
}
|
|
14594
14689
|
async launch() {
|
|
14595
14690
|
const url = new URL(this.config.tool.baseUrl);
|
|
14596
|
-
const endpoint = new URL("/
|
|
14691
|
+
const endpoint = new URL("/api/info", url);
|
|
14597
14692
|
let response;
|
|
14598
14693
|
try {
|
|
14599
14694
|
response = await fetch(endpoint, { headers: this.headers(), signal: AbortSignal.timeout(2e3) });
|
|
@@ -14601,20 +14696,13 @@ var Runtime = class {
|
|
|
14601
14696
|
if (error instanceof Error && error.name === "TimeoutError") throw new Error(`OpenCode \u5730\u5740 ${url.origin} \u54CD\u5E94\u8D85\u65F6\uFF1B\u8BF7\u68C0\u67E5\u670D\u52A1\u540E\u91CD\u8BD5\uFF0CProxy \u4E0D\u4F1A\u91CD\u590D\u542F\u52A8`);
|
|
14602
14697
|
}
|
|
14603
14698
|
if (response) {
|
|
14604
|
-
|
|
14605
|
-
if (response.ok && health?.healthy === true) return this.status();
|
|
14699
|
+
if (response.ok) return this.status();
|
|
14606
14700
|
throw new Error(`OpenCode \u5730\u5740 ${url.origin} \u5DF2\u6709 HTTP \u670D\u52A1\uFF0C\u4F46\u5065\u5EB7\u68C0\u67E5\u5931\u8D25\uFF08HTTP ${response.status}\uFF09\uFF1B\u8BF7\u68C0\u67E5\u670D\u52A1\u5BC6\u7801\u6216\u7AEF\u53E3`);
|
|
14607
14701
|
}
|
|
14608
14702
|
await mkdir4(this.config.stateDirectory, { recursive: true, mode: 448 });
|
|
14609
14703
|
const logPath = join5(this.config.stateDirectory, "opencode-serve.log");
|
|
14610
14704
|
const log = await open4(logPath, "a", 384);
|
|
14611
|
-
const env = { ...process.env };
|
|
14612
|
-
delete env.OPENCODE_SERVER_PASSWORD;
|
|
14613
|
-
delete env.OPENCODE_SERVER_USERNAME;
|
|
14614
|
-
if (this.config.tool.password) {
|
|
14615
|
-
env.OPENCODE_SERVER_PASSWORD = this.config.tool.password;
|
|
14616
|
-
env.OPENCODE_SERVER_USERNAME = "opencode";
|
|
14617
|
-
}
|
|
14705
|
+
const env = { ...process.env, OPENCODE_SERVER_PASSWORD: this.config.tool.password, OPENCODE_SERVER_USERNAME: "opencode" };
|
|
14618
14706
|
let child;
|
|
14619
14707
|
try {
|
|
14620
14708
|
child = spawn(this.config.tool.command, ["serve", "--hostname", url.hostname === "[::1]" ? "::1" : url.hostname, "--port", url.port || "80"], {
|
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
runDaemon,
|
|
23
23
|
supportedPlatform,
|
|
24
24
|
systemdUnitPath
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-HWMHCHRB.js";
|
|
26
26
|
|
|
27
27
|
// src/cli.ts
|
|
28
28
|
import { spawn } from "node:child_process";
|
|
@@ -439,6 +439,7 @@ async function runDoctor(opts) {
|
|
|
439
439
|
|
|
440
440
|
// src/onboard.ts
|
|
441
441
|
import { createInterface } from "node:readline/promises";
|
|
442
|
+
import { randomBytes } from "node:crypto";
|
|
442
443
|
import { stdin, stdout } from "node:process";
|
|
443
444
|
import { access as access3, constants as constants2, mkdir as mkdir4, stat as stat3 } from "node:fs/promises";
|
|
444
445
|
import { execFile as execFile5 } from "node:child_process";
|
|
@@ -475,7 +476,7 @@ var validateProjectId = (value, previousProjects) => validId(value) ?? (previous
|
|
|
475
476
|
var validBotId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value) ? void 0 : "\u8BF7\u8F93\u5165\u673A\u5668\u4EBA UUID";
|
|
476
477
|
var run5 = promisify5(execFile5);
|
|
477
478
|
var MACOS_TOOL_EXAMPLES = {
|
|
478
|
-
opencode: "/
|
|
479
|
+
opencode: "/Users/luhuili/.opencode/bin/opencode",
|
|
479
480
|
codex: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
|
480
481
|
zcode: "/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs",
|
|
481
482
|
pi: "/opt/homebrew/bin/pi"
|
|
@@ -625,7 +626,7 @@ async function runOnboard(opts) {
|
|
|
625
626
|
stateDirectory,
|
|
626
627
|
...loopbackHttp ? { allowInsecureLocalhost: true } : {},
|
|
627
628
|
projects: projects.map((project) => ({ id: project.id, name: project.name, directory: project.directory, specDirectories: project.specDirectories ?? ["specs", "docs"] })),
|
|
628
|
-
tool: { kind: toolKind, command: toolCommand || describeTool(toolKind).defaultCommand, ...toolKind === "opencode" ? { baseUrl, mode: "external"
|
|
629
|
+
tool: { kind: toolKind, command: toolCommand || describeTool(toolKind).defaultCommand, ...toolKind === "opencode" ? { baseUrl, mode: "external", password: toolKind === existing?.tool.kind && existing.tool.password ? existing.tool.password : randomBytes(32).toString("base64url") } : {}, autoStart: toolKind === existing?.tool.kind ? existing.tool.autoStart : true }
|
|
629
630
|
});
|
|
630
631
|
await loadConfig(opts.configPath);
|
|
631
632
|
console.log(`\u5DF2\u5199\u5165\u914D\u7F6E\uFF1A${opts.configPath}\uFF080600\uFF09`);
|
package/dist/main.js
CHANGED
package/package.json
CHANGED
package/proxy.example.json
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
"botId": "00000000-0000-4000-8000-000000000000",
|
|
5
5
|
"stateDirectory": "/home/developer/.local/state/octopus",
|
|
6
6
|
"projects": [{ "id": "example", "name": "Example project", "directory": "/home/developer/projects/example", "specDirectories": ["specs", "docs"] }],
|
|
7
|
-
"tool": { "kind": "opencode", "command": "/
|
|
7
|
+
"tool": { "kind": "opencode", "command": "/Users/luhuili/.opencode/bin/opencode", "baseUrl": "http://127.0.0.1:4096", "mode": "external", "password": "replace-with-a-random-secret-at-least-20-characters" }
|
|
8
8
|
}
|