feat(agent): unify models and validate OpenCode providers
Generate chat, task and learning models from agent-models.json. Use WaterNetwork fast/expert models with 272k context and validate upstream names before readiness. Add an isolated model check command with signal-safe cleanup and plugin, Skill and MCP management docs. Verified in isolation: type checks, 26 focused tests and both model catalog checks; no model inference requests.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { checkOpencodeModels } from "../../.opencode/model-check.js";
|
||||
import { assertModelCheckPassed, type ModelCheckInput } from "../../src/runtime/model-check.js";
|
||||
|
||||
const targets: ModelCheckInput = { models: [
|
||||
{ id: "custom/alias", provider_id: "custom", model_id: "alias", wire_id: "org/model", requires_tools: true },
|
||||
{ id: "custom/typo", provider_id: "custom", model_id: "typo", wire_id: "does-not-exist", requires_tools: true },
|
||||
] };
|
||||
|
||||
describe("OpenCode model validation", () => {
|
||||
it("rejects a manually registered invalid name and validates the actual upstream alias", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let queries = 0;
|
||||
globalThis.fetch = (async (input: unknown, init: RequestInit) => {
|
||||
queries++;
|
||||
expect(String(input)).toBe("https://example.com/v1/models");
|
||||
expect(new Headers(init.headers).get("Authorization")).toBe("Bearer TEST_ONLY_SECRET");
|
||||
expect(init.redirect).toBe("error");
|
||||
return Response.json({ data: [{ id: "org/model" }] });
|
||||
}) as typeof fetch;
|
||||
try {
|
||||
const report = await checkOpencodeModels({
|
||||
model: { list: async () => ({ data: targets.models.map((model) => ({ id: model.model_id, modelID: model.wire_id, providerID: "custom", enabled: true, capabilities: { tools: true } })) }) },
|
||||
provider: { get: async () => ({ data: { package: "@opencode/ai/providers/openai-compatible", settings: { baseURL: "https://example.com/v1", apiKey: "TEST_ONLY_SECRET" } } }) },
|
||||
} as never, targets, new AbortController().signal);
|
||||
expect(report.models.map((model) => model.status)).toEqual(["valid", "not_advertised"]);
|
||||
expect(queries).toBe(1);
|
||||
expect(() => assertModelCheckPassed(report)).toThrow("custom/typo: not_advertised");
|
||||
expect(JSON.stringify(report)).not.toContain("TEST_ONLY_SECRET");
|
||||
expect(JSON.stringify(report)).not.toContain("example.com");
|
||||
} finally { globalThis.fetch = originalFetch; }
|
||||
});
|
||||
|
||||
it("reports unavailable, non-tool and mismatched native models", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = (async () => Response.json({ data: [{ id: "org/model" }, { id: "does-not-exist" }] })) as unknown as typeof fetch;
|
||||
try {
|
||||
for (const [native, expected] of [
|
||||
[undefined, "not_registered"],
|
||||
[{ enabled: false, modelID: "org/model", capabilities: { tools: true } }, "not_enabled"],
|
||||
[{ enabled: true, modelID: "org/model", capabilities: { tools: false } }, "no_tools"],
|
||||
[{ enabled: true, modelID: "different", capabilities: { tools: true } }, "wire_id_mismatch"],
|
||||
] as const) {
|
||||
const report = await checkOpencodeModels({
|
||||
model: { list: async () => ({ data: native ? [{ id: "alias", providerID: "custom", ...native }] : [] }) },
|
||||
provider: { get: async () => ({ data: { package: "@opencode/ai/providers/openai-compatible", settings: { baseURL: "https://example.com/v1", apiKey: "TEST_ONLY_SECRET" } } }) },
|
||||
} as never, { models: [targets.models[0]!] }, new AbortController().signal);
|
||||
expect(report.models[0]!.status).toBe(expected);
|
||||
expect(() => assertModelCheckPassed(report)).toThrow();
|
||||
}
|
||||
} finally { globalThis.fetch = originalFetch; }
|
||||
});
|
||||
|
||||
it("fails closed on auth, empty/invalid inventory and cancellation without exposing upstream errors", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const context = {
|
||||
model: { list: async () => ({ data: [{ id: "alias", modelID: "org/model", providerID: "custom", enabled: true, capabilities: { tools: true } }] }) },
|
||||
provider: { get: async () => ({ data: { package: "@opencode/ai/providers/openai-compatible", settings: { baseURL: "https://example.com/v1", apiKey: "TEST_ONLY_SECRET" } } }) },
|
||||
} as never;
|
||||
try {
|
||||
for (const response of [Response.json({ secret: "TEST_ONLY_SECRET" }, { status: 401 }), Response.json({ data: [] }), Response.json({ data: [{ wrong: true }] })]) {
|
||||
globalThis.fetch = (async () => response) as unknown as typeof fetch;
|
||||
const report = await checkOpencodeModels(context, { models: [targets.models[0]!] }, new AbortController().signal);
|
||||
expect(() => assertModelCheckPassed(report)).toThrow();
|
||||
expect(JSON.stringify(report)).not.toContain("TEST_ONLY_SECRET");
|
||||
}
|
||||
globalThis.fetch = (async (_input: unknown, init: RequestInit) => { init.signal!.throwIfAborted(); throw new Error("should not query"); }) as unknown as typeof fetch;
|
||||
const controller = new AbortController(); controller.abort();
|
||||
const report = await checkOpencodeModels(context, { models: [targets.models[0]!] }, controller.signal);
|
||||
expect(report.providers[0]!.status).toBe("catalog_unavailable");
|
||||
} finally { globalThis.fetch = originalFetch; }
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user