|
1 | 1 | import { describe, expect, test } from "bun:test"; |
2 | | -import { createBuiltinRegistry } from "@intx/inference/providers"; |
3 | | -import type { |
4 | | - ConversationTurn, |
5 | | - InferenceOptions, |
6 | | - LastCycleSource, |
7 | | -} from "@intx/types/runtime"; |
8 | 2 | import { |
9 | | - adapterPreservesMidSystem, |
10 | 3 | ephemeralInjectRole, |
11 | 4 | midConversationSystemSupported, |
12 | 5 | } from "./ephemeral-inject-role.js"; |
13 | 6 |
|
14 | | -function sourceFor(provider: string): LastCycleSource { |
15 | | - return { sourceId: `test-${provider}`, provider, model: "test-model" }; |
16 | | -} |
17 | | - |
18 | | -function userTurn(text: string): ConversationTurn { |
19 | | - return { |
20 | | - role: "user", |
21 | | - timestamp: 0, |
22 | | - content: [{ type: "text", text }], |
23 | | - }; |
24 | | -} |
25 | | - |
26 | | -function assistantTurn(text: string): ConversationTurn { |
27 | | - return { |
28 | | - role: "assistant", |
29 | | - timestamp: 0, |
30 | | - content: [{ type: "text", text }], |
31 | | - }; |
32 | | -} |
33 | | - |
34 | | -function systemTurn(text: string): ConversationTurn { |
35 | | - return { |
36 | | - role: "system", |
37 | | - timestamp: 0, |
38 | | - content: [{ type: "text", text }], |
39 | | - }; |
40 | | -} |
41 | | - |
42 | | -type WireBody = { |
43 | | - messages: { role: string; content: { text?: string }[] }[]; |
44 | | - system?: { text?: string }[]; |
45 | | -}; |
46 | | - |
47 | | -function wireBody(body: string): WireBody { |
48 | | - return JSON.parse(body) as WireBody; |
49 | | -} |
50 | | - |
51 | | -function systemText(body: WireBody): string { |
52 | | - return (body.system ?? []).map((block) => block.text ?? "").join("\n\n"); |
53 | | -} |
| 7 | +const SUPPORT_TABLE = [ |
| 8 | + ["anthropic", "claude-sonnet-4-6", false], |
| 9 | + ["zen-messages", "claude-sonnet-4-6", false], |
| 10 | + ["anthropic", "claude-fable-5-1", true], |
| 11 | + ["zen-messages", "claude-fable-5", true], |
| 12 | + ["opencode-go-messages", "mythos", true], |
| 13 | + ["anthropic", "claude-opus-4-8", true], |
| 14 | + ["anthropic", "claude-opus-4.8", true], |
| 15 | + ["anthropic", "claude-opus-5", true], |
| 16 | + ["anthropic", "claude-opus-4-6", false], |
| 17 | + ["openai", "claude-fable-5-1", false], |
| 18 | + ["grok-responses", "grok-4", false], |
| 19 | + ["openai", "gpt-5", false], |
| 20 | +] as const; |
54 | 21 |
|
55 | 22 | describe("midConversationSystemSupported", () => { |
56 | | - const providers = ["anthropic", "zen-messages", "opencode-go-messages"]; |
57 | | - const models = [ |
58 | | - "fable", |
59 | | - "mythos", |
60 | | - "claude-opus-4-8", |
61 | | - "claude-opus-4.8", |
62 | | - "claude-opus-5", |
63 | | - ]; |
64 | | - for (const provider of providers) { |
65 | | - for (const model of models) { |
66 | | - test(`supports ${provider} / ${model}`, () => { |
67 | | - expect(midConversationSystemSupported(provider, model)).toBe(true); |
68 | | - }); |
69 | | - } |
70 | | - } |
71 | | - |
72 | | - test("rejects non-anthropic providers even on new models", () => { |
73 | | - expect(midConversationSystemSupported("openai", "fable")).toBe(false); |
74 | | - expect(midConversationSystemSupported("openai", "claude-opus-5")).toBe( |
75 | | - false, |
76 | | - ); |
77 | | - }); |
78 | | - |
79 | | - test("rejects older models on anthropic providers", () => { |
80 | | - expect( |
81 | | - midConversationSystemSupported("anthropic", "claude-sonnet-4-5"), |
82 | | - ).toBe(false); |
83 | | - expect( |
84 | | - midConversationSystemSupported("zen-messages", "claude-opus-4-6"), |
85 | | - ).toBe(false); |
| 23 | + test.each(SUPPORT_TABLE)("%s / %s → %s", (provider, model, supported) => { |
| 24 | + expect(midConversationSystemSupported(provider, model)).toBe(supported); |
86 | 25 | }); |
87 | 26 | }); |
88 | 27 |
|
89 | 28 | describe("ephemeralInjectRole", () => { |
90 | | - test("adapter does not preserve mid-conversation system turns", () => { |
91 | | - expect(adapterPreservesMidSystem()).toBe(false); |
92 | | - }); |
93 | | - |
94 | | - test("never returns system, even where mid-conversation system is supported", () => { |
95 | | - for (const provider of [ |
96 | | - "anthropic", |
97 | | - "zen-messages", |
98 | | - "opencode-go-messages", |
99 | | - "openai", |
100 | | - ]) { |
101 | | - for (const model of ["fable", "mythos", "claude-opus-4-8", "other"]) { |
102 | | - expect(ephemeralInjectRole(provider, model)).toBe("user"); |
103 | | - } |
104 | | - } |
105 | | - }); |
106 | | -}); |
107 | | - |
108 | | -describe("builtin anthropic adapter vs system-role inject (real registry, no stubs)", () => { |
109 | | - const persisted = [userTurn("q1"), assistantTurn("a1")]; |
110 | | - const injectText = "wrap up soon"; |
111 | | - |
112 | | - test("mid-conversation system turn is hoisted to the head system block", () => { |
113 | | - const request = createBuiltinRegistry() |
114 | | - .resolve(sourceFor("anthropic")) |
115 | | - .buildRequest( |
116 | | - [...persisted, systemTurn(injectText)], |
117 | | - "claude-opus-4-8", |
118 | | - {}, |
119 | | - ); |
120 | | - const body = wireBody(request.body); |
121 | | - expect(body.messages.some((m) => m.role === "system")).toBe(false); |
122 | | - expect(systemText(body)).toContain(injectText); |
123 | | - }); |
124 | | - |
125 | | - test("with systemPrompt set, a system-role inject is dropped", () => { |
126 | | - const options = { systemPrompt: "STABLE PROMPT" } as InferenceOptions; |
127 | | - const request = createBuiltinRegistry() |
128 | | - .resolve(sourceFor("anthropic")) |
129 | | - .buildRequest( |
130 | | - [...persisted, systemTurn(injectText)], |
131 | | - "claude-opus-4-8", |
132 | | - options, |
133 | | - ); |
134 | | - const body = wireBody(request.body); |
135 | | - expect(systemText(body)).toBe("STABLE PROMPT"); |
136 | | - expect(JSON.stringify(body.messages)).not.toContain(injectText); |
137 | | - }); |
| 29 | + test.each(SUPPORT_TABLE.map(([provider, model]) => [provider, model]))( |
| 30 | + "%s / %s → user until the adapter preserves mid-conversation system", |
| 31 | + (provider, model) => { |
| 32 | + expect(ephemeralInjectRole(provider, model)).toBe("user"); |
| 33 | + }, |
| 34 | + ); |
138 | 35 | }); |
0 commit comments