feat: handle opencode permission requests
This commit is contained in:
+86
-3
@@ -3,6 +3,8 @@ import {
|
||||
createOpencodeClient,
|
||||
type OpencodeClient,
|
||||
} from "@opencode-ai/sdk/v2";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { config } from "../config.js";
|
||||
import { logger } from "../logger.js";
|
||||
@@ -29,6 +31,8 @@ type RuntimeModelOverride = {
|
||||
modelID: string;
|
||||
};
|
||||
|
||||
export type PermissionReply = "once" | "always" | "reject";
|
||||
|
||||
export class OpencodeRuntimeAdapter {
|
||||
private clientPromise: Promise<OpencodeClient> | null = null;
|
||||
private closeServer: (() => void) | null = null;
|
||||
@@ -129,6 +133,34 @@ export class OpencodeRuntimeAdapter {
|
||||
return response.stream;
|
||||
}
|
||||
|
||||
async replyPermission(options: {
|
||||
requestId: string;
|
||||
sessionId?: string;
|
||||
reply: PermissionReply;
|
||||
message?: string;
|
||||
}) {
|
||||
const client = await this.ensureClient();
|
||||
if ("permission" in client && client.permission?.reply) {
|
||||
const response = await client.permission.reply({
|
||||
requestID: options.requestId,
|
||||
reply: options.reply,
|
||||
message: options.message,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
if ("permission" in client && client.permission?.respond && options.sessionId) {
|
||||
const response = await client.permission.respond({
|
||||
sessionID: options.sessionId,
|
||||
permissionID: options.requestId,
|
||||
response: options.reply,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
throw new Error("opencode permission reply API is unavailable");
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
this.closeServer?.();
|
||||
this.closeServer = null;
|
||||
@@ -174,9 +206,7 @@ export class OpencodeRuntimeAdapter {
|
||||
hostname: config.OPENCODE_HOSTNAME,
|
||||
port: config.OPENCODE_PORT,
|
||||
timeout: config.OPENCODE_TIMEOUT_MS,
|
||||
config: {
|
||||
model: config.OPENCODE_MODEL,
|
||||
},
|
||||
config: buildOpencodeConfig(),
|
||||
});
|
||||
} catch (error) {
|
||||
if (isMissingOpencodeCli(error)) {
|
||||
@@ -207,6 +237,59 @@ export class OpencodeRuntimeAdapter {
|
||||
|
||||
export const opencodeRuntime = new OpencodeRuntimeAdapter();
|
||||
|
||||
function buildOpencodeConfig(): Record<string, unknown> {
|
||||
return deepMerge(
|
||||
deepMerge(readProjectOpencodeConfig(), readEnvOpencodeConfig()),
|
||||
{
|
||||
model: config.OPENCODE_MODEL,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function readProjectOpencodeConfig(): Record<string, unknown> {
|
||||
const path = resolve(process.cwd(), "opencode.json");
|
||||
if (!existsSync(path)) {
|
||||
return {};
|
||||
}
|
||||
return parseConfigJson(readFileSync(path, "utf8"), path);
|
||||
}
|
||||
|
||||
function readEnvOpencodeConfig(): Record<string, unknown> {
|
||||
const content = process.env.OPENCODE_CONFIG_CONTENT;
|
||||
if (!content?.trim()) {
|
||||
return {};
|
||||
}
|
||||
return parseConfigJson(content, "OPENCODE_CONFIG_CONTENT");
|
||||
}
|
||||
|
||||
function parseConfigJson(content: string, source: string): Record<string, unknown> {
|
||||
const parsed = JSON.parse(content) as unknown;
|
||||
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
||||
throw new Error(`${source} must contain a JSON object`);
|
||||
}
|
||||
return parsed as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function deepMerge(
|
||||
left: Record<string, unknown>,
|
||||
right: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
const next = { ...left };
|
||||
for (const [key, value] of Object.entries(right)) {
|
||||
const existing = next[key];
|
||||
if (isPlainObject(existing) && isPlainObject(value)) {
|
||||
next[key] = deepMerge(existing, value);
|
||||
} else {
|
||||
next[key] = value;
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isMissingOpencodeCli(error: unknown): error is NodeJS.ErrnoException {
|
||||
return (
|
||||
typeof error === "object" &&
|
||||
|
||||
Reference in New Issue
Block a user