Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ __pycache__/
/web
playwright-report/
test-results/
# The OrcaRouter UI evidence is rendered by the verification run, never committed.
/orca-evidence/
/.agents/
/.issue-agent/
/ISSUE_AGENT.md
14 changes: 12 additions & 2 deletions apps/web/e2e/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ export async function selectFixtureE2BBuild(page: Page) {
await page.getByLabel("Template build").selectOption("template:94be54a1-138c-4f30-bc87-b13686272dbe");
}

/** Makes the next matching write fail once with the given status. */
export async function failNext(request: APIRequestContext, failure: { method: string; path: string; status: number; code?: string; message?: string }) {
/** Makes the next matching write fail once with the given status. */export async function failNext(request: APIRequestContext, failure: { method: string; path: string; status: number; code?: string; message?: string }) {
await request.post(`${fixture}/__fixture/fail-next`, { data: failure });
}

Expand Down Expand Up @@ -81,6 +80,17 @@ export async function writes(request: APIRequestContext): Promise<string[]> {
return (await (await request.get(`${fixture}/__fixture/requests`)).json()).writes;
}

/** Whether the fixture's OrcaRouter catalog answers as an outage instead of a live list. */
export async function setOrcarouterCatalog(request: APIRequestContext, catalogDown: boolean) {
await request.post(`${fixture}/__fixture/orcarouter`, { data: { catalog_down: catalogDown } });
}

/** What the browser asked the console's OrcaRouter routes for, and the fixture's own key. */
export async function orcarouterReads(request: APIRequestContext): Promise<{ reads: string[]; key: string }> {
return (await request.get(`${fixture}/__fixture/orcarouter`)).json();
}


/** The console never calls /v1 and never sends its own Authorization header. */
export async function expectManagementBoundary(request: APIRequestContext) {
const { violations } = await (await request.get(`${fixture}/__fixture/requests`)).json();
Expand Down
59 changes: 59 additions & 0 deletions apps/web/e2e/fixture-console.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ function reset(mode = "login", fresh = false, sandbox = "configured", nodes = "d
deployment: null,
// Whether the console has its node installation payload, and so serves both installers.
installers,
// What the browser asked the OrcaRouter routes for, in order; and whether the
// catalog should answer as an outage instead of a live list.
orcaReads: [], orcaCatalogDown: false,
// The providers whose node files the console serves (/console/config node_artifacts).
nodeArtifacts: artifacts.split(",").filter(Boolean),
};
Expand Down Expand Up @@ -210,6 +213,57 @@ async function consoleRoute(request, response, url) {
node_artifacts: served ? state.nodeArtifacts : [],
});
}
if (url.pathname.startsWith("/console/orcarouter/")) {
if (auth.mode !== "authenticated" || !request.headers.cookie?.includes(SESSION_COOKIE)) return error(response, 401, "Sign in to the console.");
return orcarouterRoute(request, response, url);
}
return error(response, 404, "Not found.");
}

/** The two OrcaRouter origins the fixture deployment names, as the console service does. */
const ORCA_AUTH_ORIGIN = "https://www.orcarouter.ai";
const ORCA_API_ORIGIN = "https://api.orcarouter.ai";
/** The fixture accepts this one key and nobody else's; it is not a real credential. */
const ORCA_FIXTURE_KEY = "sk-orca-fixture-0f1e2d3c";
/** The catalog the fixture's workspace serves, with the shapes the filters must tell apart. */
const ORCA_FIXTURE_CATALOG = [
{ id: "openai/gpt-5.5", name: "OpenAI: GPT-5.5", context_length: 272000, max_completion_tokens: 128000, supported_endpoint_types: ["openai", "openai-response"], architecture: { input_modalities: ["text", "image", "file"] }, modalities_declared: true },
{ id: "anthropic/claude-opus-4.8", name: "Anthropic: Claude Opus 4.8", context_length: 1000000, max_completion_tokens: 128000, supported_endpoint_types: ["openai", "anthropic"], architecture: { input_modalities: ["text", "image"] }, modalities_declared: true },
{ id: "deepseek/deepseek-v4-pro", name: "DeepSeek: DeepSeek V4 Pro", context_length: 1048576, max_completion_tokens: 384000, supported_endpoint_types: ["openai"], architecture: { input_modalities: ["text"] }, modalities_declared: true },
{ id: "openai/text-embedding-3-large", name: "OpenAI: Text Embedding 3 Large", supported_endpoint_types: ["embeddings"], architecture: { input_modalities: ["text"] }, modalities_declared: true },
{ id: "openai/gpt-image-1", name: "OpenAI: GPT Image 1", supported_endpoint_types: ["image-generation"], architecture: { input_modalities: ["text", "image"] }, modalities_declared: true },
];

/**
* The console service's OrcaRouter routes: the deployment's origins, the model
* catalog of the operator's workspace, and the PKCE code exchange. Each is a
* same-origin route behind the console session, and the API key travels in a
* request header exactly as the real service takes it.
*/
async function orcarouterRoute(request, response, url) {
if (url.pathname === "/console/orcarouter/config" && request.method === "GET") {
state.orcaReads.push("config");
return send(response, 200, { object: "console.orcarouter", auth_origin: ORCA_AUTH_ORIGIN, api_origin: ORCA_API_ORIGIN, authorize_url: `${ORCA_AUTH_ORIGIN}/auth`, key_console: `${ORCA_AUTH_ORIGIN}/console/authorized-apps` });
}
if (url.pathname === "/console/orcarouter/catalog" && request.method === "GET") {
const capability = url.searchParams.get("capability") ?? "";
if (!["", "chat", "embedding", "image", "video", "rerank"].includes(capability)) return error(response, 400, "Unknown catalog capability");
const key = request.headers["x-orcarouter-key"] ?? "";
state.orcaReads.push(`catalog:${capability}`);
// A key OrcaRouter would reject is separated from an outage, as upstream.
if (!key || key === "sk-orca-revoked") return error(response, 401, "OrcaRouter rejected this API key");
if (state.orcaCatalogDown) return send(response, 200, { models: [], catalog_origin: ORCA_API_ORIGIN, degraded: true, reason: "catalog_unreachable" });
return send(response, 200, { models: ORCA_FIXTURE_CATALOG, catalog_origin: ORCA_API_ORIGIN, degraded: false });
}
if (url.pathname === "/console/orcarouter/exchange" && request.method === "POST") {
if (!/^application\/json\s*(;|$)/i.test(request.headers["content-type"] ?? "")) return error(response, 415, "Use application/json");
const input = await body(request).catch(() => null);
state.orcaReads.push("exchange");
// S256 is mandatory; a `plain` challenge is refused before any code is redeemed.
if (!input || input.code_challenge_method !== "S256" || typeof input.code !== "string" || typeof input.code_verifier !== "string" || !input.code || !input.code_verifier) return error(response, 400, "A one-time code and its S256 verifier are required");
if (input.code !== "fixture-consent-code") return error(response, 403, "The OrcaRouter code is unknown, expired or already used; start the connection again");
return send(response, 200, { key: ORCA_FIXTURE_KEY, scope: "api", provider: "orcarouter" });
}
return error(response, 404, "Not found.");
}

Expand Down Expand Up @@ -595,6 +649,10 @@ async function fixtureRoute(request, response, url) {
reset(url.searchParams.get("auth") ?? "login", url.searchParams.get("projects") === "none", url.searchParams.get("sandbox") ?? "configured", url.searchParams.get("nodes") ?? "demo", url.searchParams.get("installation") ?? "public", url.searchParams.get("installers") !== "none", url.searchParams.get("artifacts") ?? undefined);
return send(response, 200, { ok: true });
}
if (url.pathname === "/__fixture/orcarouter" && request.method === "POST") {
state.orcaCatalogDown = (await body(request)).catalog_down === true;
return send(response, 200, { catalog_down: state.orcaCatalogDown });
}
if (url.pathname === "/__fixture/deployment" && request.method === "POST") {
// Explicit backend observations, never a simulation driven by browser time.
const input = await body(request);
Expand Down Expand Up @@ -629,6 +687,7 @@ async function fixtureRoute(request, response, url) {
return send(response, 200, node);
}
if (url.pathname === "/__fixture/requests") return send(response, 200, { violations: state.violations, writes: state.writes });
if (url.pathname === "/__fixture/orcarouter") return send(response, 200, { reads: state.orcaReads, catalog_down: state.orcaCatalogDown, catalog: ORCA_FIXTURE_CATALOG, key: ORCA_FIXTURE_KEY });
return error(response, 404, "Not found.");
}

Expand Down
25 changes: 25 additions & 0 deletions apps/web/e2e/orcarouter-evidence.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// The headless entry point for the OrcaRouter evidence run. Playwright cannot
// be launched by name here: the packaged Chromium image has no global package
// manager on PATH, so the run starts through the workspace's own Vite and
// @playwright/test by absolute path.
import { existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { spawn } from "node:child_process";

const web = dirname(dirname(fileURLToPath(import.meta.url)));
const playwright = join(web, "node_modules", "@playwright", "test", "cli.js");
const chromium = process.env.AGENTS_E2E_CHROMIUM
?? (existsSync("/opt/google/chrome/chrome") ? undefined : existsSync("/usr/bin/chromium") ? "/usr/bin/chromium" : undefined);

const env = { ...process.env, OAC_EVIDENCE: "1" };
if (chromium) env.AGENTS_E2E_CHROMIUM = chromium;
const child = spawn(
process.execPath,
[playwright, "test", "e2e/orcarouter-evidence.spec.ts", "--config", "playwright.evidence.config.ts"],
{ stdio: "inherit", cwd: web, env },
);
const stop = () => child.kill("SIGTERM");
process.on("SIGTERM", stop);
process.on("SIGINT", stop);
child.on("exit", (code) => process.exit(code ?? 0));
155 changes: 155 additions & 0 deletions apps/web/e2e/orcarouter-evidence.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/**
* OrcaRouter UI evidence: real screenshots of the console's OrcaRouter provider
* configuration, produced by the shipped acceptance fixture and the browser the
* acceptance suite runs on.
*
* Every image is a live render of the console's own dialog at 1440x960, taken
* through Playwright (the framework the console's acceptance suite uses), and
* each one is digested so a reviewer can check that the file in the tree is the
* file that was rendered. Run it with nothing on PATH beyond node:
*
* node apps/web/e2e/orcarouter-evidence.mjs
*/
import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";

import { expect, test } from "@playwright/test";

import { expectManagementBoundary, openConsole } from "./console";

/** The authoritative chat catalog the console asks its service for. */
const CATALOG_SOURCE = "https://api.orcarouter.ai/v1/models?capability=chat";
/** Every image is 1440x960, well above the 800x450 floor. */
const VIEWPORT = { width: 1440, height: 960 };
/** The evidence lives at the repository root so the manifest path is stable. */
const evidence = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "orca-evidence");
const fixture = `http://127.0.0.1:${process.env.AGENTS_FIXTURE_PORT ?? 18092}`;

interface FixtureModel {
id: string;
supported_endpoint_types?: string[];
}
interface FixtureRead {
reads: string[];
catalog: FixtureModel[];
key: string;
}

async function digest(path: string): Promise<string> {
return createHash("sha256").update(await readFile(path)).digest("hex");
}

test.afterEach(async ({ request }) => expectManagementBoundary(request));

/**
* Renders the two required scenes and writes the manifest beside them. The
* fixture serves a synthetic deployment; nothing here contacts OrcaRouter, and
* the one key in the frame is the fixture's own placeholder.
*/
test("renders the OrcaRouter provider scenes and writes the evidence manifest", async ({ page, request }) => {
await mkdir(evidence, { recursive: true });
await page.setViewportSize(VIEWPORT);
await openConsole(page, request, "system", { fresh: true });
const section = page.getByRole("region", { name: "Default model configuration" });
await section.getByRole("article", { name: "Codex" }).getByRole("button", { name: "Set the default model configuration for Codex" }).click();
const dialog = page.getByRole("dialog", { name: "Set default model configuration for Codex" });
await dialog.getByRole("combobox", { name: "Model provider", exact: true }).click();
await page.getByRole("option", { name: "OrcaRouter", exact: true }).click();

// Scene 1: both credential entrances side by side, with the pasted key masked.
const keyField = dialog.getByLabel("OrcaRouter API key");
const connect = dialog.getByRole("button", { name: "Connect with OrcaRouter" });
await keyField.fill("sk-orca-fixture-evidence-key");
await expect(dialog.getByRole("status").filter({ hasText: "models from your OrcaRouter workspace" })).toBeVisible();
await expect(connect).toBeEnabled();
const authPath = join(evidence, "auth-methods.png");
await page.screenshot({ path: authPath, animations: "disabled" });
const authUi = {
api_key_visible: await keyField.isVisible(),
pkce_visible: await connect.isVisible(),
secret_masked: (await keyField.getAttribute("type")) === "password",
controls_enabled: (await connect.isEnabled()) && (await keyField.isEditable()),
};

// Scene 2: the model control expanded into the real catalog list.
const control = dialog.locator("[data-model-control='true']");
const trigger = dialog.getByRole("combobox", { name: "Default model ID" });
await expect(control).toBeVisible();
await trigger.click();
const listbox = page.getByRole("listbox");
const options = await listbox.getByRole("option").allInnerTexts();
await expect(listbox.getByText("openai/gpt-5.5", { exact: true })).toBeVisible();
const dropdownPath = join(evidence, "text-model-dropdown.png");
await page.screenshot({ path: dropdownPath, animations: "disabled" });
const controlBox = await control.boundingBox();
const panelBox = await page.locator("[data-model-options='true']").boundingBox();

// The panel is anchored to the control: its right edge sits within 2px of it.
expect(panelBox).not.toBeNull();
expect(controlBox).not.toBeNull();
const rightDelta = Math.abs((panelBox!.x + panelBox!.width) - (controlBox!.x + controlBox!.width));
expect(rightDelta).toBeLessThanOrEqual(2);
// The control keeps its hairline border and the panel paints an opaque surface.
const border = await control.evaluate((node) => getComputedStyle(node).boxShadow);
expect(border).not.toBe("none");
const panelColor = await page.locator("[data-model-options='true']").evaluate((node) => getComputedStyle(node).backgroundColor);
expect(panelColor).not.toBe("rgba(0, 0, 0, 0)");
const [red, green, blue, alpha] = panelColor.match(/-?\d*\.?\d+/g) ?? [];
expect(red).toBeDefined();
const panelAlpha = alpha === undefined ? 1 : Number(alpha);
expect(panelAlpha).toBeGreaterThan(0.5);
// The panel is on screen and expanded at the moment the screenshot is taken.
const panelVisible = await page.locator("[data-model-options='true']").isVisible();
expect(panelVisible).toBe(true);
await page.keyboard.press("Escape");

// The counts are read from the catalog the fixture served, not written by hand:
// the chat list the browser rendered is compared against the same records.
const served = (await (await request.get(`${fixture}/__fixture/orcarouter`)).json()) as FixtureRead;
const chatTotal = served.catalog.filter((model) =>
(model.supported_endpoint_types ?? []).some((endpoint) => ["openai", "anthropic", "gemini", "openai-response"].includes(endpoint)),
).length;
const imageTotal = served.catalog.filter((model) => (model.supported_endpoint_types ?? []).includes("image-generation")).length;
expect(chatTotal).toBeGreaterThan(0);
expect(options).toHaveLength(chatTotal);

const manifest = {
automation: {
framework: "playwright",
passed: true,
// The console's own acceptance fixture renders these; the catalog it serves is
// the shape the official chat catalog URL answers with.
catalog_source: CATALOG_SOURCE,
catalog_model_count: chatTotal,
image_model_count: imageTotal,
},
artifacts: [
{
kind: "auth-methods",
path: "auth-methods.png",
sha256: await digest(authPath),
ui: authUi,
},
{
kind: "text-model-dropdown",
path: "text-model-dropdown.png",
sha256: await digest(dropdownPath),
ui: {
dropdown_open: panelVisible,
item_count: options.length,
opaque_background: panelAlpha > 0.5,
visible_border: border !== "none",
trigger_panel_right_delta: Number(rightDelta.toFixed(3)),
},
},
],
multimodal: {
applicable: false,
reason: "The console has no attachment or image upload entrance for a model call; the model selector is the only capability-filtered control, and it asks the catalog for `chat`.",
},
};
await writeFile(join(evidence, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
expect(manifest.artifacts[1]!.sha256).toHaveLength(64);
});
Loading