Skip to content

Commit c23318b

Browse files
authored
Merge pull request #246 from pylon-code/upstream/2026-09-02-claude-manifest
feat(models): discover Claude models from a remote manifest
2 parents 233d655 + a9c86c4 commit c23318b

26 files changed

Lines changed: 1791 additions & 1035 deletions
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { assert, describe, it } from "@effect/vitest";
2+
import { ProviderInstanceId } from "@t3tools/contracts";
3+
4+
import { hasValidClaudeManifestAdapters } from "./ClaudeModelManifest.ts";
5+
import type { ModelManifestData } from "./ModelManifest.ts";
6+
import {
7+
formatClaudeVersionUpgradeMessage,
8+
normalizeClaudeCatalogEffort,
9+
resolveClaudeCatalogApiModelId,
10+
resolveClaudeModelCatalog,
11+
resolveClaudeModelsForVersion,
12+
resolveClaudeModelSlug,
13+
} from "./ClaudeModelCatalog.ts";
14+
15+
/**
16+
* Test policy: adding or changing a real Claude model in model-manifest.json
17+
* must not add or update tests here. These synthetic fixtures cover resolver
18+
* behavior once. Add a test only when Claude adapter semantics change, such
19+
* as introducing a new compatibility rule or dispatch mapping type.
20+
*/
21+
22+
const manifest = (): ModelManifestData => ({
23+
version: 1,
24+
currentModels: {},
25+
providers: {
26+
claudeAgent: {
27+
profiles: {
28+
synthetic: {
29+
capabilities: {
30+
optionDescriptors: [
31+
{
32+
id: "effort",
33+
label: "Reasoning",
34+
type: "select",
35+
options: [{ id: "extreme", label: "Extreme", isDefault: true }],
36+
},
37+
{
38+
id: "contextWindow",
39+
label: "Context Window",
40+
type: "select",
41+
options: [{ id: "large", label: "Large", isDefault: true }],
42+
},
43+
],
44+
},
45+
adapter: {
46+
claudeCode: {
47+
effortMap: { extreme: "high" },
48+
modelSuffixes: { contextWindow: { large: "[large]" } },
49+
},
50+
},
51+
},
52+
},
53+
models: [
54+
{
55+
slug: "claude-synthetic-next",
56+
name: "Claude Synthetic Next",
57+
aliases: ["synthetic"],
58+
status: "current",
59+
profile: "synthetic",
60+
adapter: { claudeCode: { minVersion: "3.2.0" } },
61+
},
62+
],
63+
},
64+
},
65+
});
66+
67+
describe("Claude model catalog", () => {
68+
it("filters models at runtime-version boundaries and derives the upgrade message", () => {
69+
const catalog = resolveClaudeModelCatalog(manifest());
70+
assert.deepStrictEqual(resolveClaudeModelsForVersion(catalog, "3.1.9"), []);
71+
assert.deepStrictEqual(
72+
resolveClaudeModelsForVersion(catalog, "3.2.0").map((model) => model.slug),
73+
["claude-synthetic-next"],
74+
);
75+
assert.strictEqual(
76+
formatClaudeVersionUpgradeMessage(catalog, "3.1.9"),
77+
"Claude Code v3.1.9 is too old for Claude Synthetic Next. Upgrade to v3.2.0 or newer to access it.",
78+
);
79+
});
80+
81+
it("resolves aliases and declarative adapter mappings", () => {
82+
const base = manifest();
83+
const input: ModelManifestData = {
84+
...base,
85+
providers: {
86+
...base.providers,
87+
claudeAgent: {
88+
...base.providers!.claudeAgent!,
89+
models: [
90+
{
91+
slug: "claude-synthetic-collision",
92+
name: "Claude Synthetic Collision",
93+
aliases: ["claude-synthetic-next"],
94+
status: "current",
95+
},
96+
...base.providers!.claudeAgent!.models,
97+
],
98+
},
99+
},
100+
};
101+
const catalog = resolveClaudeModelCatalog(input);
102+
assert.strictEqual(resolveClaudeModelSlug(catalog, "synthetic"), "claude-synthetic-next");
103+
assert.strictEqual(
104+
resolveClaudeModelSlug(catalog, "claude-synthetic-next"),
105+
"claude-synthetic-next",
106+
);
107+
assert.strictEqual(normalizeClaudeCatalogEffort(catalog, "extreme", "synthetic"), "high");
108+
assert.strictEqual(
109+
resolveClaudeCatalogApiModelId(catalog, {
110+
instanceId: ProviderInstanceId.make("claudeAgent"),
111+
model: "synthetic",
112+
}),
113+
"claude-synthetic-next[large]",
114+
);
115+
});
116+
117+
it("rejects malformed adapter mappings", () => {
118+
const base = manifest();
119+
const malformed: ModelManifestData = {
120+
...base,
121+
providers: {
122+
...base.providers,
123+
claudeAgent: {
124+
...base.providers!.claudeAgent!,
125+
profiles: {
126+
...base.providers!.claudeAgent!.profiles,
127+
synthetic: {
128+
...base.providers!.claudeAgent!.profiles.synthetic!,
129+
adapter: { claudeCode: { effortMap: { extreme: 123 } } },
130+
},
131+
},
132+
},
133+
},
134+
};
135+
assert.isFalse(hasValidClaudeManifestAdapters(malformed));
136+
});
137+
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { ClaudeModelCatalog } from "./ClaudeModelCatalog.ts";
2+
3+
// Transport tests must stay independent of bundled or remote manifest contents.
4+
// Keep every model, alias, capability, and runtime mapping in this fixture synthetic.
5+
export const SYNTHETIC_CLAUDE_CAPABLE_MODEL = "claude-synthetic-capable";
6+
export const SYNTHETIC_CLAUDE_COLLIDING_ALIAS = "synthetic-collision";
7+
export const SYNTHETIC_CLAUDE_STANDARD_MODEL = "claude-synthetic-standard";
8+
export const SYNTHETIC_CLAUDE_THINKING_MODEL = "claude-synthetic-thinking";
9+
10+
const effort = {
11+
id: "effort",
12+
label: "Reasoning",
13+
type: "select" as const,
14+
options: [
15+
{ id: "low", label: "Low" },
16+
{ id: "high", label: "High", isDefault: true },
17+
{ id: "max", label: "Max" },
18+
{ id: "ultrathink", label: "Ultrathink" },
19+
],
20+
promptInjectedValues: ["ultrathink"],
21+
};
22+
23+
const contextWindow = {
24+
id: "contextWindow",
25+
label: "Context Window",
26+
type: "select" as const,
27+
options: [
28+
{ id: "standard", label: "Standard" },
29+
{ id: "expanded", label: "Expanded", isDefault: true },
30+
],
31+
};
32+
33+
const runtime = {
34+
effortMap: { ultrathink: null },
35+
modelSuffixes: { contextWindow: { expanded: "[expanded]" } },
36+
contextWindowTokens: { standard: 200_000, expanded: 1_000_000 },
37+
};
38+
39+
export const SYNTHETIC_CLAUDE_MODEL_CATALOG: ClaudeModelCatalog = {
40+
models: [
41+
{
42+
model: {
43+
slug: SYNTHETIC_CLAUDE_CAPABLE_MODEL,
44+
name: "Claude Synthetic Capable",
45+
aliases: [SYNTHETIC_CLAUDE_COLLIDING_ALIAS],
46+
isCustom: false,
47+
capabilities: {
48+
optionDescriptors: [
49+
effort,
50+
{ id: "fastMode", label: "Fast Mode", type: "boolean" },
51+
contextWindow,
52+
],
53+
},
54+
},
55+
runtime,
56+
compatibility: {},
57+
},
58+
{
59+
model: {
60+
slug: SYNTHETIC_CLAUDE_STANDARD_MODEL,
61+
name: "Claude Synthetic Standard",
62+
isCustom: false,
63+
capabilities: {
64+
optionDescriptors: [effort, contextWindow],
65+
},
66+
},
67+
runtime,
68+
compatibility: {},
69+
},
70+
{
71+
model: {
72+
slug: SYNTHETIC_CLAUDE_THINKING_MODEL,
73+
name: "Claude Synthetic Thinking",
74+
isCustom: false,
75+
capabilities: {
76+
optionDescriptors: [{ id: "thinking", label: "Thinking", type: "boolean" }],
77+
},
78+
},
79+
runtime: {},
80+
compatibility: {},
81+
},
82+
],
83+
};

0 commit comments

Comments
 (0)