@skylandnpm/octopus-proxy 0.1.72 → 0.1.73
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/dist/{chunk-CGHUM2TC.js → chunk-SSY3TLHA.js} +92 -17
- package/dist/cli.js +1 -1
- package/dist/main.js +1 -1
- package/package.json +1 -1
|
@@ -11034,6 +11034,24 @@ var UNSUPPORTED = /* @__PURE__ */ new Set([
|
|
|
11034
11034
|
"runtime.restart",
|
|
11035
11035
|
"runtime.logs"
|
|
11036
11036
|
]);
|
|
11037
|
+
var SESSION_ID = /^ses_[A-Za-z0-9]+$/;
|
|
11038
|
+
var PERMISSION_ID = /^per_[A-Za-z0-9]+$/;
|
|
11039
|
+
var OpenCodeHttpError = class extends Error {
|
|
11040
|
+
constructor(status, body) {
|
|
11041
|
+
super(`OpenCode ${status}: ${body}`);
|
|
11042
|
+
this.status = status;
|
|
11043
|
+
this.body = body;
|
|
11044
|
+
}
|
|
11045
|
+
};
|
|
11046
|
+
function isPermissionMetadataError(error) {
|
|
11047
|
+
if (!(error instanceof OpenCodeHttpError) || error.status !== 400) return false;
|
|
11048
|
+
try {
|
|
11049
|
+
const body = JSON.parse(error.body);
|
|
11050
|
+
return body?._tag === "InvalidRequestError" && body.kind === "Body" && typeof body.message === "string" && /^Expected JSON value\s+at \["data"\]\[\d+\]\["metadata"\]\["[^"]+"\]$/.test(body.message);
|
|
11051
|
+
} catch {
|
|
11052
|
+
return false;
|
|
11053
|
+
}
|
|
11054
|
+
}
|
|
11037
11055
|
var OpenCodeAdapter = class {
|
|
11038
11056
|
constructor(runtime, workspace, uploads, emit) {
|
|
11039
11057
|
this.runtime = runtime;
|
|
@@ -11050,6 +11068,7 @@ var OpenCodeAdapter = class {
|
|
|
11050
11068
|
streams = /* @__PURE__ */ new Map();
|
|
11051
11069
|
terminals = /* @__PURE__ */ new Map();
|
|
11052
11070
|
terminalOutput = /* @__PURE__ */ new Map();
|
|
11071
|
+
permissionEvents = /* @__PURE__ */ new Map();
|
|
11053
11072
|
resolving = /* @__PURE__ */ new Set();
|
|
11054
11073
|
config;
|
|
11055
11074
|
media;
|
|
@@ -11072,7 +11091,7 @@ var OpenCodeAdapter = class {
|
|
|
11072
11091
|
}
|
|
11073
11092
|
throw error;
|
|
11074
11093
|
});
|
|
11075
|
-
if (!response.ok) throw new
|
|
11094
|
+
if (!response.ok) throw new OpenCodeHttpError(response.status, (await response.text()).slice(0, 1500));
|
|
11076
11095
|
if (response.status === 204) return null;
|
|
11077
11096
|
const text = await response.text();
|
|
11078
11097
|
return text ? JSON.parse(text) : null;
|
|
@@ -11081,7 +11100,7 @@ var OpenCodeAdapter = class {
|
|
|
11081
11100
|
return unwrap2(await this.request(project, method, path, body, signal));
|
|
11082
11101
|
}
|
|
11083
11102
|
async session(project, id) {
|
|
11084
|
-
if (
|
|
11103
|
+
if (!SESSION_ID.test(id)) throw new Error("Invalid session id");
|
|
11085
11104
|
const session = await this.data(project, "GET", `/api/session/${encodeURIComponent(id)}`);
|
|
11086
11105
|
const directory = session?.location?.directory ?? session?.directory;
|
|
11087
11106
|
if (typeof directory !== "string" || await realpath4(directory) !== await this.workspace.root(project)) throw new Error("Session belongs to another project");
|
|
@@ -11099,22 +11118,26 @@ var OpenCodeAdapter = class {
|
|
|
11099
11118
|
const op = command.operation, p = command.payload, id = command.sessionId;
|
|
11100
11119
|
if (op.startsWith("auth.")) return this.authentication(project, op, p);
|
|
11101
11120
|
const needsSession = op.startsWith("sessions.") && !["sessions.list", "sessions.create"].includes(op);
|
|
11121
|
+
let session;
|
|
11102
11122
|
if (needsSession) {
|
|
11103
11123
|
if (!id) throw new Error("Session required");
|
|
11104
|
-
await this.session(project, id);
|
|
11124
|
+
session = await this.session(project, id);
|
|
11105
11125
|
}
|
|
11106
11126
|
const base = `/api/session/${encodeURIComponent(id ?? "")}`;
|
|
11107
11127
|
if (op === "configuration.permissionModes") return { defaultMode: "ask", modes: [
|
|
11108
11128
|
{ id: "ask", label: "\u8BF7\u6C42\u6279\u51C6", description: "\u6BCF\u6B21\u4F7F\u7528\u5DE5\u5177\u524D\u8BF7\u6C42\u6279\u51C6" },
|
|
11109
11129
|
{ id: "full", label: "\u5B8C\u5168\u8BBF\u95EE", description: "\u81EA\u52A8\u5141\u8BB8\u5DE5\u5177\u64CD\u4F5C\uFF1B\u4ECD\u9075\u5B88\u4E3B\u673A\u548C\u5DE5\u5177\u81EA\u8EAB\u7684\u8BBF\u95EE\u9650\u5236" }
|
|
11110
11130
|
] };
|
|
11111
|
-
if (op === "sessions.prompt") return this.prompt(project, id, p, base);
|
|
11131
|
+
if (op === "sessions.prompt") return this.prompt(project, id, p, base, session?.model);
|
|
11112
11132
|
if (op === "sessions.command" || op === "sessions.shell") {
|
|
11113
11133
|
await this.switchSelection(project, base, p);
|
|
11114
11134
|
if (op === "sessions.shell") return this.data(project, "POST", `${base}/shell`, { ...p.id ? { id: p.id } : {}, command: p.command });
|
|
11115
11135
|
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 } : {} });
|
|
11116
11136
|
}
|
|
11117
|
-
if (op === "permissions.list")
|
|
11137
|
+
if (op === "permissions.list") {
|
|
11138
|
+
if (id) await this.session(project, id);
|
|
11139
|
+
return this.permissions(project, id);
|
|
11140
|
+
}
|
|
11118
11141
|
if (op === "questions.list") return this.forms(project, id);
|
|
11119
11142
|
if (op === "permissions.reply" || op === "questions.reply" || op === "questions.reject") return this.resolveInteraction(project, id, op, p);
|
|
11120
11143
|
if (op === "configuration.models") return this.models(project);
|
|
@@ -11163,18 +11186,32 @@ var OpenCodeAdapter = class {
|
|
|
11163
11186
|
if (op.startsWith("sessions.")) return this.media.normalize(project, normalizeSession(result));
|
|
11164
11187
|
return result;
|
|
11165
11188
|
}
|
|
11166
|
-
async prompt(project, session, p, base) {
|
|
11189
|
+
async prompt(project, session, p, base, sessionModel) {
|
|
11167
11190
|
const text = typeof p.text === "string" ? screenshotPrompt(p.text) : "";
|
|
11168
11191
|
const files = [];
|
|
11192
|
+
let hasImage = false;
|
|
11169
11193
|
for (const uploadId of Array.isArray(p.attachments) ? p.attachments : []) {
|
|
11170
11194
|
const upload = await this.uploads.attachment(project, String(uploadId), session);
|
|
11171
11195
|
files.push({ uri: pathToFileURL(await this.workspace.path(project, upload.destination)).href, name: upload.name });
|
|
11196
|
+
hasImage ||= upload.mime.startsWith("image/");
|
|
11172
11197
|
}
|
|
11173
11198
|
if (!text.trim() && !files.length) throw new Error("Message or attachments required");
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11199
|
+
const model = this.modelSelection(p);
|
|
11200
|
+
if (hasImage) await this.checkImageInput(project, model ?? sessionModel);
|
|
11201
|
+
await this.switchSelection(project, base, p, model);
|
|
11202
|
+
const admitted = await this.data(project, "POST", `${base}/prompt`, { text, files, delivery: "queue", ...p.messageID ? { id: p.messageID } : {} });
|
|
11203
|
+
return typeof admitted?.id === "string" ? { id: admitted.id } : null;
|
|
11204
|
+
}
|
|
11205
|
+
async checkImageInput(project, model) {
|
|
11206
|
+
if (typeof model?.providerID !== "string" || typeof model.id !== "string") return;
|
|
11207
|
+
const catalog = await this.data(project, "GET", "/api/model", void 0, AbortSignal.timeout(2e3)).catch(() => null);
|
|
11208
|
+
const entry = Array.isArray(catalog) ? catalog.find((item) => item.providerID === model.providerID && (item.id ?? item.modelID) === model.id) : void 0;
|
|
11209
|
+
const input = entry?.capabilities?.input;
|
|
11210
|
+
if (Array.isArray(input) && !input.includes("image")) {
|
|
11211
|
+
throw new Error(`OpenCode \u6A21\u578B ${model.providerID}/${model.id} \u4E0D\u652F\u6301\u56FE\u7247\u8F93\u5165\uFF0C\u8BF7\u5207\u6362\u6A21\u578B\u6216\u68C0\u67E5\u8BE5\u6A21\u578B\u7684\u8F93\u5165\u914D\u7F6E\u3002`);
|
|
11212
|
+
}
|
|
11213
|
+
}
|
|
11214
|
+
async switchSelection(project, base, payload, model = this.modelSelection(payload)) {
|
|
11178
11215
|
if (payload.permissionMode !== void 0) {
|
|
11179
11216
|
if (!["ask", "full"].includes(String(payload.permissionMode))) throw new Error("Unsupported permission mode");
|
|
11180
11217
|
const states = await this.data(project, "GET", "/api/session/active");
|
|
@@ -11183,6 +11220,9 @@ var OpenCodeAdapter = class {
|
|
|
11183
11220
|
await this.data(project, "PATCH", base, { permissions: [{ action: "*", resource: "*", effect: payload.permissionMode === "full" ? "allow" : "ask" }] });
|
|
11184
11221
|
}
|
|
11185
11222
|
if (payload.agent) await this.data(project, "POST", `${base}/agent`, { agent: payload.agent });
|
|
11223
|
+
if (model) await this.data(project, "POST", `${base}/model`, { model });
|
|
11224
|
+
}
|
|
11225
|
+
modelSelection(payload) {
|
|
11186
11226
|
if (!payload.model) return;
|
|
11187
11227
|
let model;
|
|
11188
11228
|
if (typeof payload.model === "string") {
|
|
@@ -11195,7 +11235,7 @@ var OpenCodeAdapter = class {
|
|
|
11195
11235
|
} else throw new Error("Invalid model identifier");
|
|
11196
11236
|
const level = selectedReasoningLevel(payload, "model.variant");
|
|
11197
11237
|
if (level) model.variant = level;
|
|
11198
|
-
|
|
11238
|
+
return model;
|
|
11199
11239
|
}
|
|
11200
11240
|
async models(project) {
|
|
11201
11241
|
const models = await this.data(project, "GET", "/api/model");
|
|
@@ -11214,9 +11254,29 @@ var OpenCodeAdapter = class {
|
|
|
11214
11254
|
return redact({ providers: [...providers.values()] });
|
|
11215
11255
|
}
|
|
11216
11256
|
async permissions(project, session) {
|
|
11217
|
-
|
|
11257
|
+
let entries;
|
|
11258
|
+
try {
|
|
11259
|
+
entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/permission` : "/api/permission/request");
|
|
11260
|
+
} catch (error) {
|
|
11261
|
+
if (!isPermissionMetadataError(error)) throw error;
|
|
11262
|
+
entries = [...this.permissionEvents.get(project)?.values() ?? []].filter((entry) => !session || entry.sessionID === session);
|
|
11263
|
+
if (!entries.length) throw error;
|
|
11264
|
+
}
|
|
11218
11265
|
return (Array.isArray(entries) ? entries : []).map((entry) => ({ ...entry, permission: entry.action, patterns: entry.resources ?? [], replies: ["once", "always", "reject"] }));
|
|
11219
11266
|
}
|
|
11267
|
+
trackPermission(project, type, data) {
|
|
11268
|
+
if (typeof data?.sessionID !== "string" || !SESSION_ID.test(data.sessionID)) return;
|
|
11269
|
+
if (type === "permission.asked") {
|
|
11270
|
+
if (typeof data.id !== "string" || !PERMISSION_ID.test(data.id) || typeof data.action !== "string" || !Array.isArray(data.resources) || !data.resources.every((resource) => typeof resource === "string")) return;
|
|
11271
|
+
let pending = this.permissionEvents.get(project);
|
|
11272
|
+
if (!pending) this.permissionEvents.set(project, pending = /* @__PURE__ */ new Map());
|
|
11273
|
+
pending.set(data.id, data);
|
|
11274
|
+
if (pending.size > 256) pending.delete(pending.keys().next().value);
|
|
11275
|
+
} else if (type === "permission.replied") {
|
|
11276
|
+
const pending = this.permissionEvents.get(project);
|
|
11277
|
+
if (pending?.get(data.requestID)?.sessionID === data.sessionID) pending.delete(data.requestID);
|
|
11278
|
+
}
|
|
11279
|
+
}
|
|
11220
11280
|
async forms(project, session) {
|
|
11221
11281
|
const entries = await this.data(project, "GET", session ? `/api/session/${encodeURIComponent(session)}/form` : "/api/form");
|
|
11222
11282
|
return (Array.isArray(entries) ? entries : []).map(formToQuestion);
|
|
@@ -11228,15 +11288,18 @@ var OpenCodeAdapter = class {
|
|
|
11228
11288
|
if (this.resolving.has(key)) throw new Error("Request is already being resolved by another client");
|
|
11229
11289
|
this.resolving.add(key);
|
|
11230
11290
|
try {
|
|
11231
|
-
|
|
11291
|
+
if (kind === "permission" && session) await this.session(project, session);
|
|
11292
|
+
const pending = kind === "permission" ? await this.permissions(project, session) : await this.forms(project);
|
|
11232
11293
|
const item = pending.find((entry) => entry.id === payload.requestId);
|
|
11233
11294
|
if (!item) throw new Error("Request is no longer pending");
|
|
11234
11295
|
if (session && session !== item.sessionID) throw new Error("Request belongs to another session");
|
|
11235
|
-
await this.session(project, item.sessionID);
|
|
11296
|
+
if (kind !== "permission" || !session) await this.session(project, item.sessionID);
|
|
11236
11297
|
const base = `/api/session/${encodeURIComponent(item.sessionID)}/${kind}/${requestId}`;
|
|
11237
11298
|
if (kind === "permission") {
|
|
11238
11299
|
const decision = payload.reply === "deny" ? "reject" : payload.reply;
|
|
11239
|
-
|
|
11300
|
+
const result = await this.data(project, "POST", `${base}/reply`, { decision, ...payload.message ? { message: payload.message } : {} });
|
|
11301
|
+
this.permissionEvents.get(project)?.delete(String(payload.requestId));
|
|
11302
|
+
return result;
|
|
11240
11303
|
}
|
|
11241
11304
|
if (operation === "questions.reject") return this.data(project, "DELETE", base);
|
|
11242
11305
|
const fields = item.fields;
|
|
@@ -11336,7 +11399,10 @@ var OpenCodeAdapter = class {
|
|
|
11336
11399
|
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
11337
11400
|
if (data) {
|
|
11338
11401
|
const event = JSON.parse(data);
|
|
11339
|
-
if (!event.location?.directory || event.location.directory === root)
|
|
11402
|
+
if (!event.location?.directory || event.location.directory === root) {
|
|
11403
|
+
if (event.location?.directory === root) this.trackPermission(project, event.type, event.data);
|
|
11404
|
+
this.emit(project, event.type === "server.connected" ? "octopus.resync" : event.type, await this.media.normalize(project, event.data ?? {}));
|
|
11405
|
+
}
|
|
11340
11406
|
}
|
|
11341
11407
|
}
|
|
11342
11408
|
if (buffer.length > Math.max(4 * 1024 * 1024, Math.ceil(this.uploads.limits.fileBytes * 4 / 3) + 1024 * 1024)) throw new Error("Event too large");
|
|
@@ -11344,6 +11410,7 @@ var OpenCodeAdapter = class {
|
|
|
11344
11410
|
} catch {
|
|
11345
11411
|
if (!controller.signal.aborted) this.emit(project, "octopus.disconnected", {});
|
|
11346
11412
|
}
|
|
11413
|
+
if (!controller.signal.aborted) this.permissionEvents.delete(project);
|
|
11347
11414
|
if (!controller.signal.aborted) await new Promise((resolve5) => setTimeout(resolve5, 2e3));
|
|
11348
11415
|
}
|
|
11349
11416
|
};
|
|
@@ -11352,6 +11419,7 @@ var OpenCodeAdapter = class {
|
|
|
11352
11419
|
removeProject(project) {
|
|
11353
11420
|
this.streams.get(project)?.abort();
|
|
11354
11421
|
this.streams.delete(project);
|
|
11422
|
+
this.permissionEvents.delete(project);
|
|
11355
11423
|
for (const [key, terminal] of this.terminals) {
|
|
11356
11424
|
if (!key.startsWith(`${project}:`)) continue;
|
|
11357
11425
|
terminal.close();
|
|
@@ -11412,6 +11480,7 @@ var OpenCodeAdapter = class {
|
|
|
11412
11480
|
}
|
|
11413
11481
|
close() {
|
|
11414
11482
|
this.auth.close();
|
|
11483
|
+
this.permissionEvents.clear();
|
|
11415
11484
|
for (const controller of this.streams.values()) controller.abort();
|
|
11416
11485
|
for (const terminal of this.terminals.values()) terminal.close();
|
|
11417
11486
|
}
|
|
@@ -11434,7 +11503,13 @@ function normalizeMessage(message) {
|
|
|
11434
11503
|
if (message?.model) info.model = { ...message.model, modelID: message.model.id };
|
|
11435
11504
|
const parts = [];
|
|
11436
11505
|
if (typeof message?.text === "string" && message.text) parts.push({ type: "text", text: message.text });
|
|
11437
|
-
for (const file of message?.files ?? [])
|
|
11506
|
+
for (const file of message?.files ?? []) {
|
|
11507
|
+
if (typeof file.data === "string" && /^image\/(?:png|jpeg|gif|webp)$/.test(file.mime)) {
|
|
11508
|
+
parts.push({ type: "image", filename: file.name, mime: file.mime, base64: file.data });
|
|
11509
|
+
} else {
|
|
11510
|
+
parts.push({ type: "file", filename: file.name, mime: file.mime, url: file.uri });
|
|
11511
|
+
}
|
|
11512
|
+
}
|
|
11438
11513
|
if (message?.type === "shell") parts.push({ type: "tool", tool: "shell", state: { status: message.time?.completed ? "completed" : "running", input: { command: message.command }, output: message.output } });
|
|
11439
11514
|
if (message?.type === "compaction") {
|
|
11440
11515
|
info.summary = true;
|
package/dist/cli.js
CHANGED
package/dist/main.js
CHANGED