mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
8028a3a5b2
* feat(anthropic): gate live model discovery on contract coverage Live catalog discovery cloned a template for any newly discovered Claude id, so a future model generation would be selectable while request shaping treated it as pre-4.6 and 400d. Add an opt-in acceptUnknownModel gate to the shared live catalog seam and have the Anthropic plugin accept a discovered model only when Anthropic's advertised capabilities agree with the contracts we would apply. Manifest-published ids bypass the gate and keep their metadata. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(plugins): document the live-discovery admission hook Record acceptUnknownModel in the provider-plugin SDK reference: when to use it, that manifest-published ids bypass it, and the fail-closed guidance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
// Plugin SDK tests cover the live-discovery unknown-model gate.
|
|
import { describe, expect, it } from "vitest";
|
|
import { buildOpenAICompatibleLiveModels } from "./provider-catalog-live-normalize.internal.js";
|
|
import type { ModelProviderConfig } from "./provider-model-shared.js";
|
|
|
|
const fallback = {
|
|
baseUrl: "https://api.example.com",
|
|
api: "anthropic-messages",
|
|
models: [
|
|
{
|
|
id: "known-model",
|
|
name: "Known",
|
|
reasoning: true,
|
|
input: ["text"],
|
|
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 128_000,
|
|
},
|
|
],
|
|
} as unknown as ModelProviderConfig;
|
|
|
|
const rows = [
|
|
{ id: "known-model", object: "model" },
|
|
{ id: "brand-new-model", object: "model" },
|
|
];
|
|
|
|
describe("live discovery unknown-model gate", () => {
|
|
it("publishes discovered models unchanged when no gate is supplied", () => {
|
|
const ids = buildOpenAICompatibleLiveModels(rows, fallback).map((m) => m.id);
|
|
expect(ids).toContain("known-model");
|
|
expect(ids).toContain("brand-new-model");
|
|
});
|
|
|
|
it("drops only the ids the manifest does not publish when the gate rejects", () => {
|
|
const seen: string[] = [];
|
|
const models = buildOpenAICompatibleLiveModels(rows, fallback, ({ id }) => {
|
|
seen.push(id);
|
|
return false;
|
|
});
|
|
// The manifest-published row bypasses the gate entirely and survives.
|
|
expect(models.map((m) => m.id)).toEqual(["known-model"]);
|
|
expect(seen).toEqual(["brand-new-model"]);
|
|
});
|
|
|
|
it("keeps the manifest entry verbatim for published ids", () => {
|
|
const [model] = buildOpenAICompatibleLiveModels(rows, fallback, () => false);
|
|
expect(model).toEqual(fallback.models[0]);
|
|
});
|
|
|
|
it("admits unknown ids the gate accepts", () => {
|
|
const ids = buildOpenAICompatibleLiveModels(rows, fallback, () => true).map((m) => m.id);
|
|
expect(ids).toEqual(["brand-new-model", "known-model"]);
|
|
});
|
|
});
|