Upgrade the runtime and native plugin API to OpenCode 2.0.24. Restore session-scoped learning, credential renewal, typed project queries and controlled frontend actions from the legacy Agent. Replace host Shell execution with an approved Landlock/seccomp executor and persistent per-session workspaces. Add typed Bocha search and Tianditu geocoding bridges using the owning session credential. Require a working kernel sandbox before startup and persist workspace files in deployments. Validation completed before this commit: TypeScript checks, 150 Bun tests, Docker build and UID 10001 sandbox execution, native Shell approval and session recovery. Tianditu succeeded; Bocha currently reports exhausted account quota. Unrelated investigation-report contract work is excluded.
34 lines
2.1 KiB
TypeScript
34 lines
2.1 KiB
TypeScript
import { type Express } from "express";
|
|
import { z } from "zod";
|
|
import { ExternalInformationClient, geocodeInput, webSearchInput } from "../product/external-information-client.js";
|
|
import { isTrustedInternalBridgeRequest } from "../product/internal-bridge-auth.js";
|
|
import { getRuntimeSessionContext } from "../runtime/sessionContext.js";
|
|
|
|
export function registerExternalInformationBridge(app: Express, options: {
|
|
internalToken: string;
|
|
client?: ExternalInformationClient;
|
|
}) {
|
|
const client = options.client ?? new ExternalInformationClient();
|
|
for (const name of ["web-search", "geocode"] as const) {
|
|
app.post(`/internal/tools/${name}`, async (req, res) => {
|
|
if (!isTrustedInternalBridgeRequest(req, options.internalToken)) { res.status(403).json({ message: "forbidden" }); return; }
|
|
const parsed = z.object({ session_id: z.string().min(1).max(128), input: name === "web-search" ? webSearchInput : geocodeInput }).strict().safeParse(req.body);
|
|
if (!parsed.success) { res.status(400).json({ message: "invalid external information input" }); return; }
|
|
const context = getRuntimeSessionContext(parsed.data.session_id);
|
|
if (!context || context.learningMode === "review") { res.status(403).json({ message: "active session context required" }); return; }
|
|
const controller = new AbortController();
|
|
const cancel = () => { if (!res.writableEnded) controller.abort(); };
|
|
res.once("close", cancel);
|
|
try {
|
|
const result = name === "web-search"
|
|
? await client.webSearch(context.sessionId, webSearchInput.parse(parsed.data.input), controller.signal)
|
|
: await client.geocode(context.sessionId, geocodeInput.parse(parsed.data.input), controller.signal);
|
|
if (!controller.signal.aborted) res.json(result);
|
|
} catch (error) {
|
|
const status = error && typeof error === "object" && "status" in error && typeof error.status === "number" ? error.status : 502;
|
|
if (!controller.signal.aborted) res.status(status).json({ message: error instanceof Error ? error.message : "external information unavailable" });
|
|
} finally { res.removeListener("close", cancel); }
|
|
});
|
|
}
|
|
}
|