From 320f514f77c21d024ec813a68bff7aabb79a0648 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sun, 21 Jun 2026 02:45:30 +0200 Subject: [PATCH] fix(ai): adjusted llama-cpp base url and refactor token validation - Update `llama.cpp` base URL to remove the `/v1` suffix. - Consolidate local provider token validation in `coding-agent` using a `Set`. - Add test coverage to ensure catalog model IDs are preserved verbatim on the wire. --- packages/ai/src/registry/llama-cpp.ts | 2 +- packages/catalog/test/wafer.test.ts | 42 ++++++++++++++++++- .../coding-agent/src/config/model-registry.ts | 19 +++------ 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/packages/ai/src/registry/llama-cpp.ts b/packages/ai/src/registry/llama-cpp.ts index 85e3f6ec1..008bc156f 100644 --- a/packages/ai/src/registry/llama-cpp.ts +++ b/packages/ai/src/registry/llama-cpp.ts @@ -3,7 +3,7 @@ import type { ProviderDefinition } from "./types"; const PROVIDER_ID = "llama.cpp"; const AUTH_URL = "https://github.com/ggml-org/llama.cpp#quick-start"; -const DEFAULT_LOCAL_BASE_URL = "http://127.0.0.1:8080/v1"; +const DEFAULT_LOCAL_BASE_URL = "http://127.0.0.1:8080"; const DEFAULT_LOCAL_TOKEN = "llama-cpp-local"; export async function loginLlamaCpp(options: OAuthController): Promise { diff --git a/packages/catalog/test/wafer.test.ts b/packages/catalog/test/wafer.test.ts index 12a2fc8b2..a67a42826 100644 --- a/packages/catalog/test/wafer.test.ts +++ b/packages/catalog/test/wafer.test.ts @@ -7,10 +7,19 @@ * catalog contract and the case-sensitive id pass-through against the wire. */ import { describe, expect, it } from "bun:test"; +import { streamOpenAICompletions } from "@oh-my-pi/pi-ai/providers/openai-completions"; +import type { Context, FetchImpl, Model } from "@oh-my-pi/pi-ai/types"; import { createModelManager } from "@oh-my-pi/pi-catalog/model-manager"; import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; import { waferServerlessModelManagerOptions } from "@oh-my-pi/pi-catalog/provider-models/openai-compat"; -import type { FetchImpl, Model } from "@oh-my-pi/pi-catalog/types"; + +function sseResponse(events: unknown[]): Response { + const payload = `${events.map(e => `data: ${typeof e === "string" ? e : JSON.stringify(e)}`).join("\n\n")}\n\n`; + return new Response(payload, { + status: 200, + headers: { "content-type": "text/event-stream" }, + }); +} describe("Wafer Serverless provider", () => { it("ships the documented Serverless catalog (GLM-5.1, Qwen3.5, Qwen3.6, Qwen3.7-Max, Kimi-K2.6, DeepSeek V4 Flash/Pro)", () => { @@ -70,6 +79,37 @@ describe("Wafer Serverless provider", () => { expect(dsPro.reasoning).toBe(true); expect(dsPro.compatConfig?.thinkingFormat).toBeUndefined(); }); + + it("preserves the catalog id verbatim on the wire (no rewrite, case-sensitive)", async () => { + const model = getBundledModel<"openai-completions">("wafer-serverless", "GLM-5.1"); + const captured: { url: string | null; body: string | null } = { url: null, body: null }; + const fetchMock: FetchImpl = async (input: string | URL | Request, init?: RequestInit) => { + captured.url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + captured.body = typeof init?.body === "string" ? init.body : null; + return sseResponse(["[DONE]"]); + }; + + const context: Context = { + systemPrompt: ["t"], + messages: [{ role: "user", content: "hi", timestamp: Date.now() }], + }; + const stream = streamOpenAICompletions(model as Model<"openai-completions">, context, { + apiKey: "wfr_test", + fetch: fetchMock, + }); + for await (const _event of stream) { + /* drain */ + } + + expect(captured.url).toBe("https://pass.wafer.ai/v1/chat/completions"); + expect(captured.body).not.toBeNull(); + const parsed = JSON.parse(captured.body ?? "{}") as { model?: unknown }; + // Wafer's docs note model names are case-insensitive on input, but the + // canonical id has mixed case; we must round-trip it unchanged so users + // who pin `GLM-5.1` don't end up with usage rows under `glm-5.1` or + // hitting the upstream 404 path. + expect(parsed.model).toBe("GLM-5.1"); + }); }); describe("Wafer dynamic discovery mapper", () => { diff --git a/packages/coding-agent/src/config/model-registry.ts b/packages/coding-agent/src/config/model-registry.ts index da80b07a6..7aa949994 100644 --- a/packages/coding-agent/src/config/model-registry.ts +++ b/packages/coding-agent/src/config/model-registry.ts @@ -24,13 +24,6 @@ import { resolveVariantAlias, } from "@oh-my-pi/pi-catalog/variant-collapse"; -// Sentinels for local-only OAuth tokens — declared inline to avoid loading -// provider modules at startup. Must match packages/ai/src/registry/llama-cpp.ts, -// packages/ai/src/registry/lm-studio.ts, and packages/ai/src/registry/vllm.ts. -const DEFAULT_LLAMA_CPP_LOCAL_TOKEN = "llama-cpp-local"; -const DEFAULT_LOCAL_TOKEN = "lm-studio-local"; -const DEFAULT_VLLM_LOCAL_TOKEN = "vllm-local"; - const SPECIAL_MODEL_MANAGER_PROVIDER_IDS: readonly string[] = [ "google-antigravity", "google-gemini-cli", @@ -42,6 +35,11 @@ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [ ...SPECIAL_MODEL_MANAGER_PROVIDER_IDS, ]; +// Sentinels for local-only OAuth tokens — declared inline to avoid loading +// provider modules at startup. Must match packages/ai/src/registry/llama-cpp.ts, +// packages/ai/src/registry/lm-studio.ts, and packages/ai/src/registry/vllm.ts. +const LOCAL_PROVIDER_PLACEHOLDERS = new Set(["llama-cpp-local", "lm-studio-local", "vllm-local"]); + import type { ApiKeyResolver, FetchImpl } from "@oh-my-pi/pi-ai"; import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/oauth"; import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types"; @@ -86,12 +84,7 @@ export function isAuthenticated(apiKey: string | undefined | null): apiKey is st } function isDiscoveryBearerApiKey(apiKey: string | undefined | null): apiKey is string { - return ( - isAuthenticated(apiKey) && - apiKey !== DEFAULT_LLAMA_CPP_LOCAL_TOKEN && - apiKey !== DEFAULT_LOCAL_TOKEN && - apiKey !== DEFAULT_VLLM_LOCAL_TOKEN - ); + return isAuthenticated(apiKey) && !LOCAL_PROVIDER_PLACEHOLDERS.has(apiKey); } /** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */