fix(agent): stabilize sandboxed streaming workflows
This commit is contained in:
+51
-9
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
createOpencode,
|
||||
type OpencodeClient,
|
||||
type OutputFormat,
|
||||
} from "@opencode-ai/sdk/v2";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
@@ -38,6 +39,10 @@ type RuntimeModelOverride = {
|
||||
modelID: string;
|
||||
};
|
||||
|
||||
type RuntimePromptOptions = {
|
||||
format?: OutputFormat;
|
||||
};
|
||||
|
||||
export type PermissionReply = "once" | "always" | "reject";
|
||||
export type QuestionAnswers = string[][];
|
||||
|
||||
@@ -180,7 +185,12 @@ export class OpencodeRuntimeAdapter {
|
||||
return this.messages(sessionId);
|
||||
}
|
||||
|
||||
async prompt(sessionId: string, text: string, model?: RuntimeModelOverride) {
|
||||
async prompt(
|
||||
sessionId: string,
|
||||
text: string,
|
||||
model?: RuntimeModelOverride,
|
||||
options?: RuntimePromptOptions,
|
||||
) {
|
||||
const client = await this.ensureClient();
|
||||
const startedAt = Date.now();
|
||||
logDevelopmentDebug(
|
||||
@@ -194,6 +204,7 @@ export class OpencodeRuntimeAdapter {
|
||||
await client.session.prompt({
|
||||
sessionID: sessionId,
|
||||
model,
|
||||
...(options?.format ? { format: options.format } : {}),
|
||||
parts: [{ type: "text", text }],
|
||||
});
|
||||
logDevelopmentDebug(
|
||||
@@ -299,35 +310,41 @@ export class OpencodeRuntimeAdapter {
|
||||
);
|
||||
}
|
||||
|
||||
async subscribeEvents() {
|
||||
async subscribeEvents(directory?: string) {
|
||||
const client = await this.ensureClient();
|
||||
const response = await client.event.subscribe();
|
||||
const response = await client.event.subscribe(
|
||||
directory ? { directory } : undefined,
|
||||
);
|
||||
return response.stream;
|
||||
}
|
||||
|
||||
async replyPermission(options: {
|
||||
requestId: string;
|
||||
sessionId?: string;
|
||||
directory?: string;
|
||||
reply: PermissionReply;
|
||||
message?: string;
|
||||
}) {
|
||||
const client = await this.ensureClient();
|
||||
const directory = await this.resolveInteractionDirectory(options);
|
||||
if ("permission" in client && client.permission?.reply) {
|
||||
const response = await client.permission.reply({
|
||||
requestID: options.requestId,
|
||||
directory,
|
||||
reply: options.reply,
|
||||
message: options.message,
|
||||
});
|
||||
return response.data;
|
||||
return requireData(response.data, "permission.reply");
|
||||
}
|
||||
|
||||
if ("permission" in client && client.permission?.respond && options.sessionId) {
|
||||
const response = await client.permission.respond({
|
||||
sessionID: options.sessionId,
|
||||
permissionID: options.requestId,
|
||||
directory,
|
||||
response: options.reply,
|
||||
});
|
||||
return response.data;
|
||||
return requireData(response.data, "permission.respond");
|
||||
}
|
||||
|
||||
throw new Error("opencode permission reply API is unavailable");
|
||||
@@ -336,16 +353,19 @@ export class OpencodeRuntimeAdapter {
|
||||
async replyQuestion(options: {
|
||||
requestId: string;
|
||||
sessionId?: string;
|
||||
directory?: string;
|
||||
answers: QuestionAnswers;
|
||||
}) {
|
||||
const client = await this.ensureClient();
|
||||
const directory = await this.resolveInteractionDirectory(options);
|
||||
if ("question" in client && client.question?.reply) {
|
||||
try {
|
||||
const response = await client.question.reply({
|
||||
requestID: options.requestId,
|
||||
directory,
|
||||
answers: options.answers,
|
||||
});
|
||||
return response.data;
|
||||
return requireData(response.data, "question.reply");
|
||||
} catch (error) {
|
||||
if (!options.sessionId) {
|
||||
throw error;
|
||||
@@ -360,6 +380,7 @@ export class OpencodeRuntimeAdapter {
|
||||
reply?: (parameters: {
|
||||
sessionID: string;
|
||||
requestID: string;
|
||||
directory?: string;
|
||||
questionV2Reply: { answers: QuestionAnswers };
|
||||
}) => Promise<{ data: unknown }>;
|
||||
};
|
||||
@@ -371,11 +392,12 @@ export class OpencodeRuntimeAdapter {
|
||||
const response = await v2Question.reply({
|
||||
sessionID: options.sessionId,
|
||||
requestID: options.requestId,
|
||||
directory,
|
||||
questionV2Reply: {
|
||||
answers: options.answers,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
return requireData(response.data, "question.v2.reply");
|
||||
}
|
||||
|
||||
throw new Error("opencode question reply API is unavailable");
|
||||
@@ -384,14 +406,17 @@ export class OpencodeRuntimeAdapter {
|
||||
async rejectQuestion(options: {
|
||||
requestId: string;
|
||||
sessionId?: string;
|
||||
directory?: string;
|
||||
}) {
|
||||
const client = await this.ensureClient();
|
||||
const directory = await this.resolveInteractionDirectory(options);
|
||||
if ("question" in client && client.question?.reject) {
|
||||
try {
|
||||
const response = await client.question.reject({
|
||||
requestID: options.requestId,
|
||||
directory,
|
||||
});
|
||||
return response.data;
|
||||
return requireData(response.data, "question.reject");
|
||||
} catch (error) {
|
||||
if (!options.sessionId) {
|
||||
throw error;
|
||||
@@ -406,6 +431,7 @@ export class OpencodeRuntimeAdapter {
|
||||
reject?: (parameters: {
|
||||
sessionID: string;
|
||||
requestID: string;
|
||||
directory?: string;
|
||||
}) => Promise<{ data: unknown }>;
|
||||
};
|
||||
};
|
||||
@@ -416,13 +442,29 @@ export class OpencodeRuntimeAdapter {
|
||||
const response = await v2Question.reject({
|
||||
sessionID: options.sessionId,
|
||||
requestID: options.requestId,
|
||||
directory,
|
||||
});
|
||||
return response.data;
|
||||
return requireData(response.data, "question.v2.reject");
|
||||
}
|
||||
|
||||
throw new Error("opencode question reject API is unavailable");
|
||||
}
|
||||
|
||||
private async resolveInteractionDirectory(options: {
|
||||
sessionId?: string;
|
||||
directory?: string;
|
||||
}): Promise<string | undefined> {
|
||||
const directory = options.directory?.trim();
|
||||
if (directory) {
|
||||
return directory;
|
||||
}
|
||||
if (!options.sessionId) {
|
||||
return undefined;
|
||||
}
|
||||
const session = await this.getSession(options.sessionId);
|
||||
return session.directory;
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
if (this.toolOutputCleanupTimer) {
|
||||
clearInterval(this.toolOutputCleanupTimer);
|
||||
|
||||
Reference in New Issue
Block a user