diff --git a/bun.lock b/bun.lock index 3dd9e10..77aeaad 100644 --- a/bun.lock +++ b/bun.lock @@ -56,7 +56,7 @@ }, "packages/plugins/nexus-service": { "name": "@microvoid/convax-plugin-nexus-service", - "version": "0.3.8", + "version": "0.3.9", "dependencies": { "@microvoid/convax-nexus-mcp": "workspace:*", }, @@ -198,7 +198,7 @@ }, "packages/tools/nexus-mcp": { "name": "@microvoid/convax-nexus-mcp", - "version": "0.3.7", + "version": "0.3.8", "bin": { "convax-nexus-mcp": "dist/convax-nexus-mcp", }, diff --git a/packages/plugins/nexus-service/convax-package.json b/packages/plugins/nexus-service/convax-package.json index 43c2e0a..c65fbcb 100644 --- a/packages/plugins/nexus-service/convax-package.json +++ b/packages/plugins/nexus-service/convax-package.json @@ -4,7 +4,7 @@ "id": "nexus-service", "name": "Nexus · OpenRouter", "description": "Connects Convax to the Convax Workspace in Nexus for OpenRouter chat and live image-model generation through short-lived Data Tokens.", - "version": "0.3.8", + "version": "0.3.9", "license": "MIT", "compatibility": { "pluginSchema": "convax.plugin/7", @@ -13,7 +13,7 @@ "companions": [ { "command": "convax-nexus-mcp", - "version": "0.3.7", + "version": "0.3.8", "source": "packages/tools/nexus-mcp", "targets": [ { diff --git a/packages/plugins/nexus-service/package.json b/packages/plugins/nexus-service/package.json index 20c509b..e8a2920 100644 --- a/packages/plugins/nexus-service/package.json +++ b/packages/plugins/nexus-service/package.json @@ -1,6 +1,6 @@ { "name": "@microvoid/convax-plugin-nexus-service", - "version": "0.3.8", + "version": "0.3.9", "private": true, "type": "module", "dependencies": { diff --git a/packages/plugins/nexus-service/package/manifest.json b/packages/plugins/nexus-service/package/manifest.json index 09afe45..6d4107d 100644 --- a/packages/plugins/nexus-service/package/manifest.json +++ b/packages/plugins/nexus-service/package/manifest.json @@ -3,7 +3,7 @@ "id": "nexus-service", "name": "Nexus · OpenRouter", "description": "Connects Convax to the Convax Workspace in Nexus for OpenRouter chat and live image-model generation through short-lived Data Tokens.", - "version": "0.3.8", + "version": "0.3.9", "contributes": { "generation": { "models": [ diff --git a/packages/tools/nexus-mcp/README.md b/packages/tools/nexus-mcp/README.md index 1374d8a..8136d61 100644 --- a/packages/tools/nexus-mcp/README.md +++ b/packages/tools/nexus-mcp/README.md @@ -4,7 +4,8 @@ First-party Convax companion for Nexus Hosted Auth and its OpenAI-compatible Gat The companion requests the complete OpenRouter model catalog through Nexus and keeps output modalities bounded locally. Text-output models feed the LLM provider; -image-output models populate the host-rendered Nexus image-model control. Image +concrete image-output models populate the host-rendered Nexus image-model control, +while OpenRouter's automatic routers remain available only to the LLM provider. Image generation uses the already-metered Chat Completions path and returns only validated embedded image artifacts to the host. Nexus video endpoints remain unavailable until the service adds a dedicated video Usage Inspector and quota settlement model. diff --git a/packages/tools/nexus-mcp/package.json b/packages/tools/nexus-mcp/package.json index 5f6e543..169b7cc 100644 --- a/packages/tools/nexus-mcp/package.json +++ b/packages/tools/nexus-mcp/package.json @@ -1,6 +1,6 @@ { "name": "@microvoid/convax-nexus-mcp", - "version": "0.3.7", + "version": "0.3.8", "private": true, "license": "MIT", "type": "module", diff --git a/packages/tools/nexus-mcp/src/mcp-server.ts b/packages/tools/nexus-mcp/src/mcp-server.ts index 0e438fe..1fc039d 100644 --- a/packages/tools/nexus-mcp/src/mcp-server.ts +++ b/packages/tools/nexus-mcp/src/mcp-server.ts @@ -310,7 +310,7 @@ export class NexusMcpServer { this.#sendResult(value.id, { capabilities: { tools: {} }, protocolVersion, - serverInfo: { name: "convax-nexus-mcp", version: "0.3.7" }, + serverInfo: { name: "convax-nexus-mcp", version: "0.3.8" }, }); return; } diff --git a/packages/tools/nexus-mcp/src/nexus-client.ts b/packages/tools/nexus-mcp/src/nexus-client.ts index 71f11cf..9bdf695 100644 --- a/packages/tools/nexus-mcp/src/nexus-client.ts +++ b/packages/tools/nexus-mcp/src/nexus-client.ts @@ -23,6 +23,10 @@ const maximumImageCompletionBytes = 16 * 1024 * 1024; const maximumImageErrorBytes = 64 * 1024; const openRouterModelIdPattern = /^~?[A-Za-z0-9]+(?:[._/:-][A-Za-z0-9]+)*$/; const nexusRequestIdPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const automaticOpenRouterModelIds: ReadonlySet = new Set([ + "openrouter/auto", + "openrouter/auto-beta", +]); const nexusGatewayErrorCodes: ReadonlySet = new Set([ "access_unavailable", "invalid_gateway_request", @@ -221,8 +225,10 @@ export class NexusClient { } async imageModels(signal?: AbortSignal): Promise { - return (await this.providerModels(signal)).filter(({ outputModalities }) => - outputModalities.includes("image"), + return (await this.providerModels(signal)).filter( + ({ id, outputModalities }) => + outputModalities.includes("image") && + !automaticOpenRouterModelIds.has(id), ); } diff --git a/packages/tools/nexus-mcp/test/mcp-server.test.ts b/packages/tools/nexus-mcp/test/mcp-server.test.ts index d4fa76c..1a24d68 100644 --- a/packages/tools/nexus-mcp/test/mcp-server.test.ts +++ b/packages/tools/nexus-mcp/test/mcp-server.test.ts @@ -75,7 +75,7 @@ test("Nexus image diagnostics expose only typed bounded HTTP fields", () => { expect(generic).not.toContain("/private/output/path"); }); -test("the first tools/list response includes live image model choices", async () => { +test("MCP excludes automatic routers only from live image model choices", async () => { const responses: unknown[] = []; const sessions = { async read() { @@ -118,6 +118,16 @@ test("the first tools/list response includes live image model choices", async () if (url.pathname.endsWith("/models")) { return Response.json({ data: [ + { + architecture: { output_modalities: ["image", "text"] }, + id: "openrouter/auto", + name: "Auto Router", + }, + { + architecture: { output_modalities: ["image", "text"] }, + id: "openrouter/auto-beta", + name: "Auto Router Beta", + }, { architecture: { output_modalities: ["image", "text"] }, id: "openai/gpt-image-1", @@ -170,6 +180,34 @@ test("the first tools/list response includes live image model choices", async () "x-convax-role": "generation-model-id", }); + controller.enqueue( + new TextEncoder().encode( + `${JSON.stringify({ + id: 2, + jsonrpc: "2.0", + method: "tools/call", + params: { arguments: {}, name: "llm.models.list" }, + })}\n`, + ), + ); + for (let attempt = 0; attempt < 100 && responses.length < 2; attempt += 1) { + await Bun.sleep(10); + } + expect(responses).toHaveLength(2); + expect(responses[1]).toMatchObject({ + id: 2, + result: { + structuredContent: { + models: [ + { id: "openrouter/auto", name: "Auto Router" }, + { id: "openrouter/auto-beta", name: "Auto Router Beta" }, + { id: "openai/gpt-image-1", name: "GPT Image 1" }, + ], + schema: "convax.llm-model-catalog/1", + }, + }, + }); + await server.shutdown(1_000); await running; }); diff --git a/packages/tools/nexus-mcp/test/nexus-client.test.ts b/packages/tools/nexus-mcp/test/nexus-client.test.ts index 66222b4..3cdefd4 100644 --- a/packages/tools/nexus-mcp/test/nexus-client.test.ts +++ b/packages/tools/nexus-mcp/test/nexus-client.test.ts @@ -317,7 +317,7 @@ describe("NexusClient", () => { }); }); - test("loads and bounds the opaque OpenRouter catalog through the Nexus Gateway", async () => { + test("keeps automatic routers in the LLM catalog but excludes them from image models", async () => { const root = await fs.mkdtemp( path.join(os.tmpdir(), "convax-nexus-client-"), ); @@ -383,6 +383,16 @@ describe("NexusClient", () => { id: "deepseek/deepseek-v4-flash:free", name: "DeepSeek V4 Flash Free", }, + { + architecture: { output_modalities: ["image", "text"] }, + id: "openrouter/auto", + name: "Auto Router", + }, + { + architecture: { output_modalities: ["image", "text"] }, + id: "openrouter/auto-beta", + name: "Auto Router Beta", + }, { architecture: { output_modalities: ["image", "text"] }, id: "openai/gpt-image-1", @@ -403,6 +413,8 @@ describe("NexusClient", () => { id: "deepseek/deepseek-v4-flash:free", name: "DeepSeek V4 Flash Free", }, + { id: "openrouter/auto", name: "Auto Router" }, + { id: "openrouter/auto-beta", name: "Auto Router Beta" }, { id: "openai/gpt-image-1", name: "GPT Image 1" }, ], schema: "convax.llm-model-catalog/1",