fix(agent-bridge): add validate-only upstream-ca/test route (#3488) (#3600)

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-10 18:16:57 -03:00
committed by GitHub
parent 60d2f05d0e
commit 9ef4a61e9a
4 changed files with 158 additions and 1 deletions

View File

@@ -8,6 +8,7 @@
- **fix(api):** `/v1/completions` now returns the legacy OpenAI Completions shape (`object:"text_completion"`, `choices[].text`) instead of chat payloads (`choices[].message|delta.content`) — the endpoint routes internally through the chat pipeline, so legacy Completion clients like TabbyML's `openai/completion` backend crashed with `missing field "text"`. The response (both non-streaming JSON and the SSE stream) is now translated back to the text-completion shape; `[DONE]` and error bodies pass through unchanged. ([#3571](https://github.com/diegosouzapw/OmniRoute/issues/3571))
- **fix(usage):** the z.ai/GLM coding-plan quota card no longer shows "Monthly 0%" — coding plans have no monthly cap (only 5-hour windows), so the quota API reports the `TIME_LIMIT` ("Monthly") entry with `total=0`, and the `total>0 ? … : 0` fallback rendered a misleading 0% remaining (which can skew downstream model-choice). With no absolute cap the remaining percentage now falls back to the percentage-derived value (full/100% when 0% used). ([#3580](https://github.com/diegosouzapw/OmniRoute/issues/3580))
- **docs(discovery):** mark `DISCOVERY_TOOL_DESIGN.md`'s API Endpoints table with an explicit "⚠️ Not yet implemented — Phase 2" banner — the discovery routes are a design proposal (Phase-1 stub only), and the banner makes clear the `KNOWN_STALE_DOC_REFS` gate suppression is intentional, not stale drift. ([#3498](https://github.com/diegosouzapw/OmniRoute/issues/3498))
- **fix(agent-bridge):** add the missing `POST /api/tools/agent-bridge/upstream-ca/test` route — the UpstreamCaField "Test" button POSTed to it but it didn't exist (404). The new validate-only route checks the CA file exists and is a parseable PEM certificate (returns the subject/expiry) **without** persisting the path or activating it; it inherits the `/api/tools/agent-bridge/` LOCAL_ONLY classification. ([#3488](https://github.com/diegosouzapw/OmniRoute/issues/3488))
---

View File

@@ -28,7 +28,6 @@ const KNOWN_MISSING = new Set([
"/api/gamification/badges", // profile/page.tsx — idem
"/api/gamification/badges/earned", // profile/page.tsx — idem
"/api/settings/obsidian/webdav", // ObsidianSourceCard.tsx — só existe /api/settings/obsidian
"/api/tools/agent-bridge/upstream-ca/test", // UpstreamCaField.tsx — rota inexistente
]);
function walk(dir, acc = []) {

View File

@@ -0,0 +1,63 @@
/**
* POST /api/tools/agent-bridge/upstream-ca/test
*
* Validate-only (dry-run) counterpart of POST /api/tools/agent-bridge/upstream-ca:
* checks the upstream CA file exists and is a parseable PEM certificate, WITHOUT
* persisting the path or activating it via configureUpstreamCa(). Backs the
* UpstreamCaField "Test" button, which previously 404'd (#3488).
*
* LOCAL_ONLY: covered by the "/api/tools/agent-bridge/" prefix in routeGuard.ts.
*/
import crypto from "crypto";
import fs from "fs";
import { AgentBridgeUpstreamCaPostSchema } from "@/shared/schemas/agentBridge";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import { createErrorResponse } from "@/lib/api/errorResponse";
export async function POST(request: Request): Promise<Response> {
let body: unknown;
try {
body = await request.json();
} catch {
return createErrorResponse({ status: 400, message: "Invalid JSON body" });
}
const parsed = AgentBridgeUpstreamCaPostSchema.safeParse(body);
if (!parsed.success) {
return createErrorResponse({
status: 400,
message: "Invalid request body",
details: parsed.error.flatten(),
});
}
const { path: caPath } = parsed.data;
if (!fs.existsSync(caPath)) {
return createErrorResponse({ status: 400, message: `Upstream CA file not found: ${caPath}` });
}
let pem: string;
try {
pem = fs.readFileSync(caPath, "utf8");
} catch (err) {
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : String(err));
return createErrorResponse({ status: 400, message: `Unable to read upstream CA file: ${msg}` });
}
if (!pem.includes("-----BEGIN CERTIFICATE-----")) {
return createErrorResponse({
status: 400,
message: "File is not a PEM certificate (missing a -----BEGIN CERTIFICATE----- block).",
});
}
try {
const cert = new crypto.X509Certificate(pem);
return Response.json({ ok: true, path: caPath, subject: cert.subject, validTo: cert.validTo });
} catch (err) {
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : String(err));
return createErrorResponse({ status: 400, message: `Invalid certificate: ${msg}` });
}
}

View File

@@ -0,0 +1,94 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { POST } from "../../src/app/api/tools/agent-bridge/upstream-ca/test/route.ts";
// #3488 — UpstreamCaField's "Test" button POSTed to /api/tools/agent-bridge/upstream-ca/test,
// which did not exist (404). The new validate-only route checks the CA file exists and is a
// parseable PEM certificate WITHOUT persisting/activating it.
// A throwaway self-signed cert (CN=OmniRoute Test CA), valid to 2036.
const TEST_CA_PEM = `-----BEGIN CERTIFICATE-----
MIIDGTCCAgGgAwIBAgIUISgNKO/v/z0FdUIPoCD4dwgKbacwDQYJKoZIhvcNAQEL
BQAwHDEaMBgGA1UEAwwRT21uaVJvdXRlIFRlc3QgQ0EwHhcNMjYwNjEwMjExNDMx
WhcNMzYwNjA3MjExNDMxWjAcMRowGAYDVQQDDBFPbW5pUm91dGUgVGVzdCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALkubKCA7sgOph0nsKhQZNoH
UaQo+mrodWJ+23yVnxPygBQQay6okO1w5U6yxweinyCC0jB87Y386q30cqYK6NCf
HbAgkNRelhxeoU71DztIjIaKZCTlra5CCjVxVzvIOu4PoP8UgoLy/jOLM15XiM5B
entgjw62qXGRGak2Thiac+dHRzKJAIPxRDnWDrgQFsduNtSb1sGbivjUjLLEabm0
gokYlJpNCYJvS31qvL37aeV8igjt8hsReVEb5qm5RiiAepM9B3gvKvn0fsKvxT2a
rmqgySF+o2aTi+PW3+ZySoWoUL6b7GSA/CpF6Mc3u2qM/DvU4Kr0K8Y5EE+PsYUC
AwEAAaNTMFEwHQYDVR0OBBYEFD/qt9vsjOHNvlfT6Z4j9myR4GJJMB8GA1UdIwQY
MBaAFD/qt9vsjOHNvlfT6Z4j9myR4GJJMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggEBAH0WP40mF66cqUxQjamHS2BScRkn5E+SvwoZD12ZvQO/kgj/
wbWu5mZOf5cpuqHrOmfOC+6itIkZb7v6d0CRM19xcoAg1mVWFFSk0iroFCw0qltN
kB5WPvKHI6JRkr7HdUTD1qW9ljveMPfZ/Fm9ZM6QhCOLifzNTP2GMTVyIMAvig6B
TgmL/sn4dw4C2UTMQioMMXHSeJ90OD4Pv3mqX16JKrRSICoTExBoEIF23kWWJL6m
RL5Jiv1pdbFujHL8l9KPI2xmsWtkKutxOL2O5zpdUxP4noNVInDqEmbriK6CKY4y
hWHoQhtd4zf9H6+NIi38SPTCAmCjgU7iVq6mWoE=
-----END CERTIFICATE-----
`;
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-ca-test-"));
const validCaPath = path.join(dir, "valid-ca.pem");
const nonPemPath = path.join(dir, "not-a-cert.txt");
fs.writeFileSync(validCaPath, TEST_CA_PEM);
fs.writeFileSync(nonPemPath, "this is not a certificate");
test.after(() => fs.rmSync(dir, { recursive: true, force: true }));
function postJson(body: unknown): Request {
return new Request("http://localhost/api/tools/agent-bridge/upstream-ca/test", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}
test("#3488 valid PEM cert → 200 ok with subject", async () => {
const res = await POST(postJson({ path: validCaPath }));
assert.equal(res.status, 200);
const json = await res.json();
assert.equal(json.ok, true);
assert.match(json.subject, /OmniRoute Test CA/);
});
test("#3488 does NOT persist the CA path (validate-only)", async () => {
await POST(postJson({ path: validCaPath }));
// The persisted path file used by the real POST route must not be created by /test.
// We can't import the constant without side effects, so assert the dry-run returns a
// shape with no activation marker and leaves no global state — the absence of an
// `activated`/`persisted` field is the contract.
const res = await POST(postJson({ path: validCaPath }));
const json = await res.json();
assert.equal(json.persisted, undefined);
assert.equal(json.activated, undefined);
});
test("#3488 non-existent path → 400", async () => {
const res = await POST(postJson({ path: path.join(dir, "nope.pem") }));
assert.equal(res.status, 400);
});
test("#3488 file that is not a PEM cert → 400", async () => {
const res = await POST(postJson({ path: nonPemPath }));
assert.equal(res.status, 400);
});
test("#3488 invalid body (missing path) → 400", async () => {
const res = await POST(postJson({}));
assert.equal(res.status, 400);
});
test("#3488 invalid JSON body → 400", async () => {
const req = new Request("http://localhost/api/tools/agent-bridge/upstream-ca/test", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: "{not json",
});
const res = await POST(req);
assert.equal(res.status, 400);
});