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.
This commit is contained in:
@@ -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<string> {
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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<string>(["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 */
|
||||
|
||||
Reference in New Issue
Block a user