mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
fix(opencode): preserve tool validation errors in v2 (#6519)
Failed bridge requests discarded the daemon error body before OpenCode could show the validation explanation to the agent. Preserve that message and status while retaining the HTTP fallback. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -9,6 +9,10 @@ import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createTestLogger } from "../../../../test-utils/test-logger.js";
|
||||
import type { PaseoToolCatalog } from "../../tools/types.js";
|
||||
import { createPaseoToolCatalog } from "../../tools/paseo-tools.js";
|
||||
import { AgentManager } from "../../agent-manager.js";
|
||||
import { AgentStorage } from "../../agent-storage.js";
|
||||
import { ProviderSnapshotManager } from "../../provider-snapshot-manager.js";
|
||||
import {
|
||||
OpenCodeBridge,
|
||||
loadOpenCodeBridgePluginArtifact,
|
||||
@@ -291,7 +295,10 @@ describe("OpenCodeBridge", () => {
|
||||
).resolves.toMatchObject({ content: [{ type: "text", text: "child result" }] });
|
||||
await expect(
|
||||
tools.get("paseo_echo_context")!.execute({ value: "blocked" }, { sessionID: "disabled" }),
|
||||
).rejects.toThrow("HTTP 403");
|
||||
).rejects.toMatchObject({
|
||||
message: "Paseo tools are disabled for this session",
|
||||
status: 403,
|
||||
});
|
||||
await dispose();
|
||||
} finally {
|
||||
release();
|
||||
@@ -300,6 +307,72 @@ describe("OpenCodeBridge", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("v2 plugin preserves create_agent validation errors from the daemon", async () => {
|
||||
const paseoHome = await mkdtemp(path.join(tmpdir(), "paseo-opencode-v2-errors-"));
|
||||
temporaryDirectories.push(paseoHome);
|
||||
const logger = createTestLogger();
|
||||
const providerSnapshotManager = new ProviderSnapshotManager({ logger });
|
||||
const catalog = createPaseoToolCatalog({
|
||||
agentManager: new AgentManager({ logger }),
|
||||
agentStorage: new AgentStorage(paseoHome, logger),
|
||||
providerSnapshotManager,
|
||||
logger,
|
||||
});
|
||||
const bridge = new OpenCodeBridge({ paseoHome, logger });
|
||||
bridge.setManifestCatalog(catalog);
|
||||
await bridge.start();
|
||||
const release = bridge.bindSession({ sessionId: "caller", env: {}, tools: catalog });
|
||||
try {
|
||||
const env = await bridge.decorateV2ServerEnv({});
|
||||
const { plugins } = JSON.parse(env.OPENCODE_CONFIG_CONTENT);
|
||||
const plugin = plugins[0];
|
||||
const module = await import(
|
||||
pathToFileURL(path.join(fileURLToPath(plugin.package), "server.js")).href
|
||||
);
|
||||
const tools = new Map<string, V2TestTool>();
|
||||
const dispose = await module.default.setup({
|
||||
options: plugin.options,
|
||||
tool: {
|
||||
transform: async (transform: (editor: { add(tool: V2TestTool): void }) => void) => {
|
||||
transform({ add: (tool) => tools.set(tool.name, tool) });
|
||||
return { dispose: async () => undefined };
|
||||
},
|
||||
},
|
||||
session: {
|
||||
context: async () => [],
|
||||
get: async () => ({ parentID: "" }),
|
||||
hook: async () => ({ dispose: async () => undefined }),
|
||||
},
|
||||
} satisfies V2TestPluginContext);
|
||||
try {
|
||||
const input = { provider: "codex", title: "Invalid provider", initialPrompt: "Hello" };
|
||||
const response = await fetch(
|
||||
`${plugin.options.baseUrl}/_internal/opencode/sessions/caller/tools/create_agent`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${plugin.options.token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
},
|
||||
);
|
||||
expect(response.status).toBe(500);
|
||||
const payload = await response.json();
|
||||
expect(payload.error).toContain("provider must be provider/model");
|
||||
await expect(
|
||||
tools.get("paseo_create_agent")!.execute(input, { sessionID: "caller" }),
|
||||
).rejects.toMatchObject({ message: payload.error, status: 500 });
|
||||
} finally {
|
||||
await dispose();
|
||||
}
|
||||
} finally {
|
||||
release();
|
||||
await bridge.close();
|
||||
await providerSnapshotManager.shutdown();
|
||||
}
|
||||
});
|
||||
|
||||
test("v2 plugin returns a tool image as OpenCode file content", async () => {
|
||||
const paseoHome = await mkdtemp(path.join(tmpdir(), "paseo-opencode-v2-image-"));
|
||||
temporaryDirectories.push(paseoHome);
|
||||
|
||||
@@ -13,7 +13,16 @@ export default {
|
||||
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
||||
});
|
||||
if (allowMissing && response.status === 404) return null;
|
||||
if (!response.ok) throw new Error(`Paseo tool bridge returned HTTP ${response.status}`);
|
||||
if (!response.ok) {
|
||||
const payload = await response.json().catch(() => null);
|
||||
const message =
|
||||
typeof payload?.error === "string" && payload.error
|
||||
? payload.error
|
||||
: `Paseo tool bridge returned HTTP ${response.status}`;
|
||||
const error = new Error(message);
|
||||
error.status = response.status;
|
||||
throw error;
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
async function scope(sessionID) {
|
||||
|
||||
Reference in New Issue
Block a user