mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 14:52:09 +03:00
fix(auggie): update model registry to match v0.32.0 CLI model IDs (#7032)
* fix(auggie): update model registry to match v0.32.0 CLI model IDs All previous model IDs (claude-sonnet-4.6, claude-opus-4.6, gpt-5.5-high, etc.) were synthetic — the actual IDs use a different naming scheme (sonnet4.6, opus4.6, gpt5.5, etc.). Replaced the static best-guess registry with the 31 real model IDs from on v0.32.0, including: - All Claude variants (fable-5, haiku4.5, sonnet4.x/5, opus4.x/5) - Gemini 3.1 Pro Preview - Full GPT-5.x family (gpt5 ~ gpt5.6-terra) - GLM 5.2, Kimi K2.6/K2.7 - Prism composite routers (prism-a, prism-b) Removed unused entries that don't exist in v0.32.0 (gemini-3.0-flash, thinking variants, high/medium split IDs). Updated unit tests to reference valid model IDs (haiku4.5, sonnet4.6, opus4.6). * feat(auggie): auto-fetch model IDs on first execute() * fix(auggie): move sonnet4.6 first in model list, remove duplicate * fix(tests): update old claude-sonnet-4.6 model ID to sonnet4.6 in auggie test The registry was updated to use sonnet4.6 but the test at line 352 still referenced the old model ID claude-sonnet-4.6, causing resolveAuggieModel to reject it. * test(autoCombo): account for auggie's new glm-5.2 model in auto/glm family test The v0.32.0 auggie registry update in this PR adds a literal "glm-5.2" model id. auggie is a no-auth candidate (always in the auto/<family> pool per open-sse/services/autoCombo/virtualFactory.ts), and the family filter matches by model-id pattern (open-sse/services/autoCombo/modelFamily.ts), so it now legitimately joins auto/glm alongside the glm/zai connections — same documented behavior the "degrades gracefully" test below already covers for opencode/minimax. Updates the strict-equality assertion to include it instead of narrowing the pool in production code. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(auggie): add backward-compat alias map for v0.32.0 model IDs Saved combos may reference old model IDs (claude-sonnet-4.6 → sonnet4.6, gemini-3.1-pro → gemini-3.1-pro-preview, gpt-5.5-high → gpt5.5, etc). The alias map in resolveAuggieModel() resolves these before the allowlist check so existing combos continue working after the registry rename. Refs: #7032 * fix(auggie): use Map.get() for the pre-v0.32.0 alias lookup + changelog resolveAuggieModel() indexed AUGGIE_MODEL_ALIASES (a Map) with bracket notation (AUGGIE_MODEL_ALIASES[requested]), which always returns undefined for a Map instance — the alias branch never actually fired, so every pre-v0.32.0 saved model id still hit "Unknown Auggie model" after the v0.32.0 registry rename. Switch to .get(requested), the Map accessor. Adds a red-first regression test (fails on the old bracket access, passes with .get()) covering every old->new id pair in the alias map, and a changelog.d fragment documenting the breaking model-id rename + the alias fallback that keeps existing combos working. Refs: #7032 Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
1
changelog.d/fixes/7032-auggie-model-ids-v032.md
Normal file
1
changelog.d/fixes/7032-auggie-model-ids-v032.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(providers):** Auggie (Augment CLI) model registry updated to the real v0.32.0 CLI model IDs, with a live `auggie model list` auto-discovery fallback for future renames; the old pre-v0.32.0 IDs (`claude-sonnet-4.6`, `claude-opus-4.6`, `claude-haiku-4.5`, `gemini-3.1-pro`, `gemini-3.0-flash`, the `gpt-5.4`/`gpt-5.5` high/medium variants) are a **breaking rename**, but a backward-compat alias map in `resolveAuggieModel()` transparently remaps them to their v0.32.0 equivalents so existing saved combos keep working without any manual update (#7032 — thanks @oyi77).
|
||||
@@ -3,6 +3,8 @@ import type { RegistryEntry } from "../../shared.ts";
|
||||
// Augment / Auggie CLI — local no-auth provider. The executor spawns the
|
||||
// user's local `auggie` binary (auth handled entirely by `auggie login`);
|
||||
// OmniRoute never stores credentials for this connection.
|
||||
//
|
||||
// Model IDs sourced from `auggie model list` on auggie v0.32.0.
|
||||
export const auggieProvider: RegistryEntry = {
|
||||
id: "auggie",
|
||||
alias: "aug",
|
||||
@@ -13,26 +15,38 @@ export const auggieProvider: RegistryEntry = {
|
||||
authHeader: "none",
|
||||
defaultContextLength: 200000,
|
||||
models: [
|
||||
// Claude
|
||||
{ id: "claude-sonnet-4.6", name: "Claude Sonnet 4.6", contextLength: 200000 },
|
||||
{
|
||||
id: "claude-sonnet-4.6-thinking",
|
||||
name: "Claude Sonnet 4.6 Thinking",
|
||||
contextLength: 200000,
|
||||
},
|
||||
{ id: "claude-opus-4.6", name: "Claude Opus 4.6", contextLength: 200000 },
|
||||
{ id: "claude-haiku-4.5", name: "Claude Haiku 4.5", contextLength: 200000 },
|
||||
// Gemini
|
||||
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro", contextLength: 1000000 },
|
||||
{ id: "gemini-3.0-flash", name: "Gemini 3 Flash", contextLength: 1000000 },
|
||||
// GPT-5.x
|
||||
{ id: "gpt-5.5-high", name: "GPT-5.5 High", contextLength: 200000 },
|
||||
{ id: "gpt-5.5-medium", name: "GPT-5.5 Medium", contextLength: 200000 },
|
||||
{ id: "gpt-5.4-high", name: "GPT-5.4 High", contextLength: 200000 },
|
||||
{ id: "gpt-5.4-medium", name: "GPT-5.4 Medium", contextLength: 200000 },
|
||||
// Kimi
|
||||
// ── Anthropic Claude ────────────────────────────────────────────────
|
||||
{ id: "sonnet4.6", name: "Sonnet 4.6", contextLength: 200000 },
|
||||
{ id: "fable-5", name: "Claude Fable 5", contextLength: 200000 },
|
||||
{ id: "haiku4.5", name: "Haiku 4.5", contextLength: 200000 },
|
||||
{ id: "sonnet4.5", name: "Sonnet 4.5", contextLength: 200000 },
|
||||
{ id: "sonnet4.6-500k", name: "Sonnet 4.6 (500K)", contextLength: 500000 },
|
||||
{ id: "sonnet5-high", name: "Claude Sonnet 5", contextLength: 200000 },
|
||||
{ id: "sonnet5-500k", name: "Claude Sonnet 5 (500K)", contextLength: 500000 },
|
||||
{ id: "opus4.5", name: "Opus 4.5", contextLength: 200000 },
|
||||
{ id: "opus4.6", name: "Opus 4.6", contextLength: 200000 },
|
||||
{ id: "opus4.6-500k", name: "Opus 4.6 (500K)", contextLength: 500000 },
|
||||
{ id: "opus4.7", name: "Opus 4.7", contextLength: 200000 },
|
||||
{ id: "opus4.7-500k", name: "Opus 4.7 (500K)", contextLength: 500000 },
|
||||
{ id: "opus4.8", name: "Opus 4.8", contextLength: 200000 },
|
||||
// ── Gemini ──────────────────────────────────────────────────────────
|
||||
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro", contextLength: 1000000 },
|
||||
// ── OpenAI GPT ──────────────────────────────────────────────────────
|
||||
{ id: "gpt5", name: "GPT-5", contextLength: 200000 },
|
||||
{ id: "gpt5.1", name: "GPT-5.1", contextLength: 200000 },
|
||||
{ id: "gpt5.2", name: "GPT-5.2", contextLength: 200000 },
|
||||
{ id: "gpt5.4", name: "GPT-5.4", contextLength: 200000 },
|
||||
{ id: "gpt5.4-mini", name: "GPT-5.4 Mini", contextLength: 200000 },
|
||||
{ id: "gpt5.5", name: "GPT-5.5", contextLength: 200000 },
|
||||
{ id: "gpt5.6-luna", name: "GPT-5.6 Luna", contextLength: 200000 },
|
||||
{ id: "gpt5.6-sol", name: "GPT-5.6 Sol", contextLength: 200000 },
|
||||
{ id: "gpt5.6-terra", name: "GPT-5.6 Terra", contextLength: 200000 },
|
||||
// ── Others ──────────────────────────────────────────────────────────
|
||||
{ id: "glm-5.2", name: "GLM 5.2", contextLength: 200000 },
|
||||
{ id: "kimi-k2.6", name: "Kimi K2.6", contextLength: 131000 },
|
||||
// Prism (Augment's in-house model)
|
||||
{ id: "prism", name: "Augment Prism", contextLength: 200000 },
|
||||
{ id: "kimi-k2.7", name: "Kimi K2.7 Code", contextLength: 131000 },
|
||||
// ── Augment Prism (composite routers) ───────────────────────────────
|
||||
{ id: "prism-a", name: "Prism (Claude + Gemini)", contextLength: 200000 },
|
||||
{ id: "prism-b", name: "Prism (GPT + Kimi)", contextLength: 200000 },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -38,8 +38,114 @@ const AUGGIE_URL = "auggie://cli/stdio";
|
||||
// untrusted-input sink. We only ever pass a model that is declared in the
|
||||
// registry entry — this closes flag-smuggling (a `model` starting with "-" would
|
||||
// otherwise be parsed by auggie as an option) and unknown-model passthrough.
|
||||
//
|
||||
// The static registry (shipped with the code) is checked first. On first use
|
||||
// the executor also spawns `auggie model list` at runtime and merges any IDs it
|
||||
// finds — this lets the allowlist stay current when auggie adds or renames
|
||||
// models without a code update.
|
||||
const AUGGIE_MODEL_ALLOWLIST: ReadonlySet<string> = new Set(auggieProvider.models.map((m) => m.id));
|
||||
const DEFAULT_AUGGIE_MODEL = auggieProvider.models[0]?.id ?? "claude-sonnet-4.6";
|
||||
// ─── Model alias map (backward compat for saved combos) ─────────────────────
|
||||
// Old model IDs from before the v0.32.0 registry update; each maps to the
|
||||
// equivalent v0.32.0 ID so existing combos continue to work after the rename.
|
||||
const AUGGIE_MODEL_ALIASES: ReadonlyMap<string, string> = new Map([
|
||||
// Claude
|
||||
["claude-sonnet-4.6", "sonnet4.6"],
|
||||
["claude-sonnet-4.6-thinking", "sonnet4.6"],
|
||||
["claude-opus-4.6", "opus4.6"],
|
||||
["claude-haiku-4.5", "haiku4.5"],
|
||||
// Gemini
|
||||
["gemini-3.1-pro", "gemini-3.1-pro-preview"],
|
||||
["gemini-3.0-flash", "gemini-3.1-pro-preview"],
|
||||
// GPT-5.x (high/medium split was synthetic — v0.32.0 has a single ID per version)
|
||||
["gpt-5.5-high", "gpt5.5"],
|
||||
["gpt-5.5-medium", "gpt5.5"],
|
||||
["gpt-5.4-high", "gpt5.4"],
|
||||
["gpt-5.4-medium", "gpt5.4"],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Live model cache populated by `initAuggieModels()`.
|
||||
* - `null` = not yet attempted
|
||||
* - `Set` = successfully fetched IDs (possibly empty)
|
||||
*/
|
||||
let liveModelSet: Set<string> | null = null;
|
||||
|
||||
/**
|
||||
* Spawn `auggie model list`, parse `[model-id]` entries, and merge them into
|
||||
* the live allowlist so the executor accepts models auggie recognises even
|
||||
* when the static registry has not been updated yet.
|
||||
*
|
||||
* Safe to call repeatedly: only the first call spawns the process; subsequent
|
||||
* calls are a no-op (including after a failed fetch — `liveModelSet` is set to
|
||||
* an empty set so we don't retry every request).
|
||||
*/
|
||||
export async function initAuggieModels(
|
||||
signal?: AbortSignal | null,
|
||||
timeoutMs = 8000
|
||||
): Promise<void> {
|
||||
if (liveModelSet !== null) return;
|
||||
let bin: string;
|
||||
try {
|
||||
bin = resolveAuggieBin();
|
||||
} catch {
|
||||
liveModelSet = new Set();
|
||||
return;
|
||||
}
|
||||
const child = spawn(bin, ["model", "list"], {
|
||||
env: process.env,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
shell: false,
|
||||
windowsHide: true,
|
||||
});
|
||||
const fragments: string[] = [];
|
||||
child.stdout.on("data", (d: Buffer) => fragments.push(d.toString("utf8")));
|
||||
let settled = false;
|
||||
const settle = (result: Set<string>) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
liveModelSet = result;
|
||||
};
|
||||
const timer = setTimeout(() => {
|
||||
if (!child.killed) child.kill("SIGKILL");
|
||||
settle(new Set());
|
||||
}, timeoutMs);
|
||||
const onAbort = () => {
|
||||
if (!child.killed) child.kill("SIGKILL");
|
||||
clearTimeout(timer);
|
||||
settle(new Set());
|
||||
};
|
||||
if (signal) {
|
||||
if (signal.aborted) {
|
||||
clearTimeout(timer);
|
||||
settle(new Set());
|
||||
return;
|
||||
}
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
}
|
||||
try {
|
||||
const code = await new Promise<number | null>((resolve, reject) => {
|
||||
child.on("close", resolve);
|
||||
child.on("error", (e: Error) => reject(e));
|
||||
});
|
||||
clearTimeout(timer);
|
||||
signal?.removeEventListener("abort", onAbort);
|
||||
if (code !== 0) {
|
||||
settle(new Set());
|
||||
return;
|
||||
}
|
||||
const ids = new Set<string>();
|
||||
for (const line of fragments.join("").split("\n")) {
|
||||
const m = line.match(/\[([^\]]+)\]/);
|
||||
if (m) ids.add(m[1]);
|
||||
}
|
||||
settle(ids.size > 0 ? ids : new Set());
|
||||
} catch {
|
||||
clearTimeout(timer);
|
||||
settle(new Set());
|
||||
signal?.removeEventListener("abort", onAbort);
|
||||
}
|
||||
}
|
||||
|
||||
type AuggieModelResolution = { ok: true; model: string } | { ok: false; error: string };
|
||||
|
||||
@@ -47,6 +153,9 @@ type AuggieModelResolution = { ok: true; model: string } | { ok: false; error: s
|
||||
* Validate + resolve the requested model against the registry allowlist.
|
||||
* Rejects flag-smuggling (leading "-") and any id not declared in the registry.
|
||||
* An empty/absent model resolves to the registry's first (default) model.
|
||||
*
|
||||
* Note: `initAuggieModels()` must be called at least once before this function
|
||||
* sees live-discovered models (the executor's `execute()` does this).
|
||||
*/
|
||||
export function resolveAuggieModel(model: unknown): AuggieModelResolution {
|
||||
const requested = typeof model === "string" ? model.trim() : "";
|
||||
@@ -57,15 +166,20 @@ export function resolveAuggieModel(model: unknown): AuggieModelResolution {
|
||||
error: `Invalid Auggie model "${requested}": model must not start with "-".`,
|
||||
};
|
||||
}
|
||||
if (!AUGGIE_MODEL_ALLOWLIST.has(requested)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: `Unknown Auggie model "${requested}". Supported models: ${[
|
||||
...AUGGIE_MODEL_ALLOWLIST,
|
||||
].join(", ")}.`,
|
||||
};
|
||||
}
|
||||
return { ok: true, model: requested };
|
||||
// Backward-compat alias: resolve old model IDs → v0.32.0 equivalents.
|
||||
// This lets saved combos referencing the old names keep working.
|
||||
const requestedAlias = AUGGIE_MODEL_ALIASES.get(requested);
|
||||
if (requestedAlias) return { ok: true, model: requestedAlias };
|
||||
// Static registry — always authoritative for the shipped set.
|
||||
if (AUGGIE_MODEL_ALLOWLIST.has(requested)) return { ok: true, model: requested };
|
||||
// Live-discovered models (if loaded) extend the static list.
|
||||
if (liveModelSet?.has(requested)) return { ok: true, model: requested };
|
||||
const known = [...AUGGIE_MODEL_ALLOWLIST];
|
||||
if (liveModelSet) known.push(...liveModelSet);
|
||||
return {
|
||||
ok: false,
|
||||
error: `Unknown Auggie model "${requested}". Supported models: ${known.join(", ")}.`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,6 +265,7 @@ export function buildAuggiePrompt(messages: OpenAIMsg[]): string {
|
||||
}
|
||||
}
|
||||
if (!text.trim()) continue;
|
||||
|
||||
if (role === "system") {
|
||||
lines.push(`[System]\n${text}`);
|
||||
} else if (role === "assistant") {
|
||||
@@ -252,7 +367,6 @@ export class AuggieExecutor extends BaseExecutor {
|
||||
): Promise<Partial<ProviderCredentials> | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async execute({ model, body, stream, signal, log }: ExecuteInput): Promise<{
|
||||
response: Response;
|
||||
url: string;
|
||||
@@ -265,6 +379,9 @@ export class AuggieExecutor extends BaseExecutor {
|
||||
const auggieBin = resolveAuggieBin();
|
||||
const wantsStream = stream !== false;
|
||||
|
||||
// On first execution, try to discover model IDs the local auggie recognises.
|
||||
// Best-effort: missing/inactive CLI falls through to the static list.
|
||||
await initAuggieModels(signal);
|
||||
// Argument-injection defense: never forward an unvalidated model into the argv.
|
||||
const modelResolution = resolveAuggieModel(model);
|
||||
if (!modelResolution.ok) {
|
||||
@@ -601,3 +718,13 @@ function buildAuggieSseError(message: string): Response {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Test helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Reset the live model cache for testing.
|
||||
* Not exported from the package index.
|
||||
*/
|
||||
export function __resetAuggieModels(): void {
|
||||
liveModelSet = null;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,8 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const { AuggieExecutor, buildAuggiePrompt, resolveAuggieBin, resolveAuggieModel } = await import(
|
||||
"@omniroute/open-sse/executors/auggie"
|
||||
);
|
||||
const { AuggieExecutor, buildAuggiePrompt, resolveAuggieBin, resolveAuggieModel } =
|
||||
await import("@omniroute/open-sse/executors/auggie");
|
||||
|
||||
const TMP_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-auggie-test-"));
|
||||
|
||||
@@ -27,7 +26,6 @@ function writeFakeBin(name: string, script: string): string {
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
async function readSseEvents(response: Response): Promise<Record<string, unknown>[]> {
|
||||
const text = await response.text();
|
||||
const events: Record<string, unknown>[] = [];
|
||||
@@ -61,7 +59,13 @@ test("buildAuggiePrompt flattens system/user/assistant turns with role tags", ()
|
||||
|
||||
test("buildAuggiePrompt flattens array-shaped content blocks and skips empty turns", () => {
|
||||
const prompt = buildAuggiePrompt([
|
||||
{ role: "user", content: [{ type: "text", text: "part1 " }, { type: "text", text: "part2" }] },
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "part1 " },
|
||||
{ type: "text", text: "part2" },
|
||||
],
|
||||
},
|
||||
{ role: "user", content: "" },
|
||||
{ role: "user", content: [] },
|
||||
]);
|
||||
@@ -93,7 +97,7 @@ test("execute() surfaces a sanitized 'CLI not found' error on ENOENT (streaming)
|
||||
try {
|
||||
const executor = new AuggieExecutor();
|
||||
const { response } = await executor.execute({
|
||||
model: "claude-sonnet-4.6",
|
||||
model: "sonnet4.6",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: {} as never,
|
||||
@@ -120,7 +124,7 @@ test("execute() surfaces a sanitized 'CLI not found' error on ENOENT (non-stream
|
||||
try {
|
||||
const executor = new AuggieExecutor();
|
||||
const { response } = await executor.execute({
|
||||
model: "claude-sonnet-4.6",
|
||||
model: "sonnet4.6",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: false,
|
||||
credentials: {} as never,
|
||||
@@ -138,13 +142,16 @@ test("execute() surfaces a sanitized 'CLI not found' error on ENOENT (non-stream
|
||||
// ─── execute(): non-zero exit → sanitized error ────────────────────────────
|
||||
|
||||
test("execute() surfaces a sanitized error when the CLI exits non-zero (streaming)", async () => {
|
||||
const bin = writeFakeBin("fake-auggie-fail.sh", 'echo "boom at /home/attacker/secret.ts:42" 1>&2\nexit 3');
|
||||
const bin = writeFakeBin(
|
||||
"fake-auggie-fail.sh",
|
||||
'echo "boom at /home/attacker/secret.ts:42" 1>&2\nexit 3'
|
||||
);
|
||||
const prevBin = process.env.AUGGIE_BIN;
|
||||
process.env.AUGGIE_BIN = bin;
|
||||
try {
|
||||
const executor = new AuggieExecutor();
|
||||
const { response } = await executor.execute({
|
||||
model: "claude-sonnet-4.6",
|
||||
model: "sonnet4.6",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: {} as never,
|
||||
@@ -168,7 +175,7 @@ test("execute() surfaces a sanitized error when the CLI exits non-zero (non-stre
|
||||
try {
|
||||
const executor = new AuggieExecutor();
|
||||
const { response } = await executor.execute({
|
||||
model: "claude-sonnet-4.6",
|
||||
model: "sonnet4.6",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: false,
|
||||
credentials: {} as never,
|
||||
@@ -191,7 +198,7 @@ test("execute() with stream=true returns SSE deltas + [DONE]", async () => {
|
||||
try {
|
||||
const executor = new AuggieExecutor();
|
||||
const { response } = await executor.execute({
|
||||
model: "claude-sonnet-4.6",
|
||||
model: "sonnet4.6",
|
||||
body: { messages: [{ role: "user", content: "say hi" }] },
|
||||
stream: true,
|
||||
credentials: {} as never,
|
||||
@@ -221,7 +228,7 @@ test("execute() with stream=false returns a single chat.completion JSON body", a
|
||||
try {
|
||||
const executor = new AuggieExecutor();
|
||||
const { response } = await executor.execute({
|
||||
model: "claude-sonnet-4.6",
|
||||
model: "sonnet4.6",
|
||||
body: { messages: [{ role: "user", content: "say hi" }] },
|
||||
stream: false,
|
||||
credentials: {} as never,
|
||||
@@ -249,8 +256,41 @@ test("resolveAuggieModel defaults to a real allowlisted model when unset", () =>
|
||||
});
|
||||
|
||||
test("resolveAuggieModel accepts a known registry model id verbatim", () => {
|
||||
const result = resolveAuggieModel("claude-haiku-4.5");
|
||||
assert.deepEqual(result, { ok: true, model: "claude-haiku-4.5" });
|
||||
const result = resolveAuggieModel("haiku4.5");
|
||||
assert.deepEqual(result, { ok: true, model: "haiku4.5" });
|
||||
});
|
||||
|
||||
// ─── resolveAuggieModel: pre-v0.32.0 alias back-compat (#7032) ────────────
|
||||
// The v0.32.0 registry rename dropped ~10 old model IDs outright. Saved combos
|
||||
// created before the rename still reference those old IDs, so resolveAuggieModel
|
||||
// must transparently map each one to its v0.32.0 replacement instead of erroring
|
||||
// with "Unknown Auggie model".
|
||||
|
||||
test("resolveAuggieModel resolves a pre-v0.32.0 saved model id via the alias map (#7032)", () => {
|
||||
const result = resolveAuggieModel("claude-sonnet-4.6");
|
||||
assert.deepEqual(result, { ok: true, model: "sonnet4.6" });
|
||||
});
|
||||
|
||||
test("resolveAuggieModel resolves every pre-v0.32.0 alias to an allowlisted v0.32.0 id (#7032)", () => {
|
||||
const OLD_TO_NEW: Record<string, string> = {
|
||||
"claude-sonnet-4.6": "sonnet4.6",
|
||||
"claude-sonnet-4.6-thinking": "sonnet4.6",
|
||||
"claude-opus-4.6": "opus4.6",
|
||||
"claude-haiku-4.5": "haiku4.5",
|
||||
"gemini-3.1-pro": "gemini-3.1-pro-preview",
|
||||
"gemini-3.0-flash": "gemini-3.1-pro-preview",
|
||||
"gpt-5.5-high": "gpt5.5",
|
||||
"gpt-5.5-medium": "gpt5.5",
|
||||
"gpt-5.4-high": "gpt5.4",
|
||||
"gpt-5.4-medium": "gpt5.4",
|
||||
};
|
||||
for (const [oldId, newId] of Object.entries(OLD_TO_NEW)) {
|
||||
assert.deepEqual(
|
||||
resolveAuggieModel(oldId),
|
||||
{ ok: true, model: newId },
|
||||
`${oldId} should resolve to ${newId}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── execute(): model allowlist (argument-injection defense) ──────────────
|
||||
@@ -314,7 +354,7 @@ test("execute() spawns with a valid allowlisted model, no shell, and a '--' argv
|
||||
try {
|
||||
const executor = new AuggieExecutor();
|
||||
const { response } = await executor.execute({
|
||||
model: "claude-opus-4.6",
|
||||
model: "opus4.6",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: false,
|
||||
credentials: {} as never,
|
||||
@@ -323,7 +363,7 @@ test("execute() spawns with a valid allowlisted model, no shell, and a '--' argv
|
||||
const body = await response.json();
|
||||
assert.equal(body.choices[0].message.content, "ok");
|
||||
const argv = fs.readFileSync(argvFile, "utf8").trim().split("\n");
|
||||
assert.deepEqual(argv, ["--print", "--quiet", "--model", "claude-opus-4.6", "--"]);
|
||||
assert.deepEqual(argv, ["--print", "--quiet", "--model", "opus4.6", "--"]);
|
||||
} finally {
|
||||
if (prevBin === undefined) delete process.env.AUGGIE_BIN;
|
||||
else process.env.AUGGIE_BIN = prevBin;
|
||||
@@ -342,7 +382,7 @@ test("execute() aborts a long-running CLI process instead of hanging (streaming)
|
||||
const executor = new AuggieExecutor();
|
||||
const controller = new AbortController();
|
||||
const { response } = await executor.execute({
|
||||
model: "claude-sonnet-4.6",
|
||||
model: "sonnet4.6",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: {} as never,
|
||||
@@ -378,3 +418,99 @@ test("buildUrl/buildHeaders/transformRequest match the CLI-passthrough shape", (
|
||||
assert.deepEqual(executor.buildHeaders(), {});
|
||||
assert.equal(executor.transformRequest(), null);
|
||||
});
|
||||
|
||||
// ─── initAuggieModels / resolveAuggieModel auto-fetch ────────────────────
|
||||
|
||||
test("initAuggieModels discovers models from `auggie model list` output", async () => {
|
||||
const { initAuggieModels, resolveAuggieModel, __resetAuggieModels } =
|
||||
await import("@omniroute/open-sse/executors/auggie");
|
||||
__resetAuggieModels(); // wipe stale cache from previous tests
|
||||
const bin = writeFakeBin(
|
||||
"fake-auggie-list.sh",
|
||||
`printf '[new-model-alpha]\\nignored\\n[new-model-beta]'`
|
||||
);
|
||||
const prevBin = process.env.AUGGIE_BIN;
|
||||
process.env.AUGGIE_BIN = bin;
|
||||
try {
|
||||
await initAuggieModels();
|
||||
const r = resolveAuggieModel("new-model-alpha");
|
||||
assert.deepEqual(r, { ok: true, model: "new-model-alpha" });
|
||||
} finally {
|
||||
if (prevBin === undefined) delete process.env.AUGGIE_BIN;
|
||||
else process.env.AUGGIE_BIN = prevBin;
|
||||
__resetAuggieModels(); // restore for subsequent tests
|
||||
}
|
||||
});
|
||||
|
||||
test("execute() spawns a model discovered via auto-fetch", async () => {
|
||||
const { __resetAuggieModels } = await import("@omniroute/open-sse/executors/auggie");
|
||||
__resetAuggieModels();
|
||||
const bin = writeFakeBin(
|
||||
"fake-auggie-autofetch.sh",
|
||||
[
|
||||
`case "$*" in`,
|
||||
` *model\\ list*)`,
|
||||
` printf '[auto-discovered-model]'`,
|
||||
` exit 0`,
|
||||
` ;;`,
|
||||
` *)`,
|
||||
` printf 'auto-ok'`,
|
||||
` ;;`,
|
||||
`esac`,
|
||||
].join("\n")
|
||||
);
|
||||
const prevBin = process.env.AUGGIE_BIN;
|
||||
process.env.AUGGIE_BIN = bin;
|
||||
try {
|
||||
const executor = new AuggieExecutor();
|
||||
const { response } = await executor.execute({
|
||||
model: "auto-discovered-model",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: false,
|
||||
credentials: {} as never,
|
||||
});
|
||||
assert.equal(response.status, 200);
|
||||
const body = await response.json();
|
||||
assert.equal(body.choices[0].message.content, "auto-ok");
|
||||
} finally {
|
||||
if (prevBin === undefined) delete process.env.AUGGIE_BIN;
|
||||
else process.env.AUGGIE_BIN = prevBin;
|
||||
__resetAuggieModels();
|
||||
}
|
||||
});
|
||||
|
||||
test("initAuggieModels failure does not block execute() for a known model", async () => {
|
||||
const { __resetAuggieModels } = await import("@omniroute/open-sse/executors/auggie");
|
||||
__resetAuggieModels();
|
||||
const bin = writeFakeBin(
|
||||
"fake-auggie-crash.sh",
|
||||
[
|
||||
`case "$*" in`,
|
||||
` *model\\ list*)`,
|
||||
` exit 1 # crash on model list so init fails`,
|
||||
` ;;`,
|
||||
` *)`,
|
||||
` printf 'known-model-ok'`,
|
||||
` ;;`,
|
||||
`esac`,
|
||||
].join("\n")
|
||||
);
|
||||
const prevBin = process.env.AUGGIE_BIN;
|
||||
process.env.AUGGIE_BIN = bin;
|
||||
try {
|
||||
const executor = new AuggieExecutor();
|
||||
const { response } = await executor.execute({
|
||||
model: "haiku4.5", // static-registry model, should work regardless
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: false,
|
||||
credentials: {} as never,
|
||||
});
|
||||
assert.equal(response.status, 200);
|
||||
const body = await response.json();
|
||||
assert.equal(body.choices[0].message.content, "known-model-ok");
|
||||
} finally {
|
||||
if (prevBin === undefined) delete process.env.AUGGIE_BIN;
|
||||
else process.env.AUGGIE_BIN = prevBin;
|
||||
__resetAuggieModels();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -123,7 +123,12 @@ describe("auto/<family> materialization (#6453)", () => {
|
||||
assert.equal(combo.id, "auto/glm");
|
||||
assert.equal(combo.strategy, "auto");
|
||||
const providerIds = combo.models.map((m) => m.providerId).sort();
|
||||
assert.deepEqual(providerIds, ["glm", "zai"]);
|
||||
// Includes the always-on `auggie` no-auth candidate: its registry (v0.32.0 CLI
|
||||
// model ids) advertises a literal "glm-5.2" model, and — same as the
|
||||
// "degrades gracefully" test below documents for opencode/minimax — a
|
||||
// no-auth backend that genuinely serves a family model IS a legitimate
|
||||
// member of the family pool, not just credentialed provider_connections rows.
|
||||
assert.deepEqual(providerIds, ["auggie", "glm", "zai"]);
|
||||
assert.ok(combo.models.every((m) => m.model.endsWith("glm-5.2")));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user