@a-dray/aglib 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/agent.d.ts +158 -0
- package/dist/agent.js +2 -0
- package/dist/agent.js.map +1 -0
- package/dist/content.d.ts +45 -0
- package/dist/content.js +11 -0
- package/dist/content.js.map +1 -0
- package/dist/harness/adapters/acp/index.d.ts +92 -0
- package/dist/harness/adapters/acp/index.js +479 -0
- package/dist/harness/adapters/acp/index.js.map +1 -0
- package/dist/harness/adapters/acp/rpc.d.ts +23 -0
- package/dist/harness/adapters/acp/rpc.js +103 -0
- package/dist/harness/adapters/acp/rpc.js.map +1 -0
- package/dist/harness/adapters/native/compaction.d.ts +16 -0
- package/dist/harness/adapters/native/compaction.js +55 -0
- package/dist/harness/adapters/native/compaction.js.map +1 -0
- package/dist/harness/adapters/native/index.d.ts +2 -0
- package/dist/harness/adapters/native/index.js +2 -0
- package/dist/harness/adapters/native/index.js.map +1 -0
- package/dist/harness/adapters/native/loop.d.ts +25 -0
- package/dist/harness/adapters/native/loop.js +153 -0
- package/dist/harness/adapters/native/loop.js.map +1 -0
- package/dist/harness/harness.d.ts +129 -0
- package/dist/harness/harness.js +2 -0
- package/dist/harness/harness.js.map +1 -0
- package/dist/harness/index.d.ts +3 -0
- package/dist/harness/index.js +2 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +4 -0
- package/dist/json.js +2 -0
- package/dist/json.js.map +1 -0
- package/dist/model/adapters/anthropic/index.d.ts +34 -0
- package/dist/model/adapters/anthropic/index.js +271 -0
- package/dist/model/adapters/anthropic/index.js.map +1 -0
- package/dist/model/adapters/fake/index.d.ts +27 -0
- package/dist/model/adapters/fake/index.js +37 -0
- package/dist/model/adapters/fake/index.js.map +1 -0
- package/dist/model/adapters/openai-compatible/index.d.ts +29 -0
- package/dist/model/adapters/openai-compatible/index.js +226 -0
- package/dist/model/adapters/openai-compatible/index.js.map +1 -0
- package/dist/model/conformance.d.ts +210 -0
- package/dist/model/conformance.js +427 -0
- package/dist/model/conformance.js.map +1 -0
- package/dist/model/index.d.ts +2 -0
- package/dist/model/index.js +2 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/model.d.ts +85 -0
- package/dist/model/model.js +8 -0
- package/dist/model/model.js.map +1 -0
- package/dist/result.d.ts +23 -0
- package/dist/result.js +3 -0
- package/dist/result.js.map +1 -0
- package/dist/run.d.ts +10 -0
- package/dist/run.js +477 -0
- package/dist/run.js.map +1 -0
- package/dist/sandbox/adapters/docker.d.ts +22 -0
- package/dist/sandbox/adapters/docker.js +443 -0
- package/dist/sandbox/adapters/docker.js.map +1 -0
- package/dist/sandbox/adapters/local.d.ts +11 -0
- package/dist/sandbox/adapters/local.js +284 -0
- package/dist/sandbox/adapters/local.js.map +1 -0
- package/dist/sandbox/conformance.d.ts +73 -0
- package/dist/sandbox/conformance.js +309 -0
- package/dist/sandbox/conformance.js.map +1 -0
- package/dist/sandbox/index.d.ts +1 -0
- package/dist/sandbox/index.js +2 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/sandbox.d.ts +172 -0
- package/dist/sandbox/sandbox.js +2 -0
- package/dist/sandbox/sandbox.js.map +1 -0
- package/dist/session/entry.d.ts +196 -0
- package/dist/session/entry.js +2 -0
- package/dist/session/entry.js.map +1 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/log.d.ts +20 -0
- package/dist/session/log.js +24 -0
- package/dist/session/log.js.map +1 -0
- package/dist/session/messages.d.ts +42 -0
- package/dist/session/messages.js +99 -0
- package/dist/session/messages.js.map +1 -0
- package/dist/store/adapters/sqlite.d.ts +20 -0
- package/dist/store/adapters/sqlite.js +365 -0
- package/dist/store/adapters/sqlite.js.map +1 -0
- package/dist/store/conformance.d.ts +66 -0
- package/dist/store/conformance.js +580 -0
- package/dist/store/conformance.js.map +1 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/store.d.ts +244 -0
- package/dist/store/store.js +2 -0
- package/dist/store/store.js.map +1 -0
- package/dist/tools/execute.d.ts +24 -0
- package/dist/tools/execute.js +105 -0
- package/dist/tools/execute.js.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/tool.d.ts +106 -0
- package/dist/tools/tool.js +34 -0
- package/dist/tools/tool.js.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { textOf } from "../content.js";
|
|
2
|
+
function fail(what) {
|
|
3
|
+
throw new Error(`model conformance: ${what}`);
|
|
4
|
+
}
|
|
5
|
+
function holds(condition, what) {
|
|
6
|
+
if (!condition)
|
|
7
|
+
fail(what);
|
|
8
|
+
}
|
|
9
|
+
function same(actual, expected) {
|
|
10
|
+
if (actual === expected)
|
|
11
|
+
return true;
|
|
12
|
+
if (Array.isArray(actual) && Array.isArray(expected)) {
|
|
13
|
+
return actual.length === expected.length && actual.every((item, at) => same(item, expected[at]));
|
|
14
|
+
}
|
|
15
|
+
if (typeof actual !== "object" || typeof expected !== "object" || !actual || !expected)
|
|
16
|
+
return false;
|
|
17
|
+
const left = Object.keys(actual).sort();
|
|
18
|
+
const right = Object.keys(expected).sort();
|
|
19
|
+
return same(left, right)
|
|
20
|
+
&& left.every((key) => same(actual[key], expected[key]));
|
|
21
|
+
}
|
|
22
|
+
function equals(actual, expected, what) {
|
|
23
|
+
if (!same(actual, expected)) {
|
|
24
|
+
fail(`${what} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function absent(actual, what) {
|
|
28
|
+
if (actual !== undefined)
|
|
29
|
+
fail(`${what} — expected nothing, got ${JSON.stringify(actual)}`);
|
|
30
|
+
}
|
|
31
|
+
/** Unwraps a result the case expects to have succeeded. */
|
|
32
|
+
function got(result, what) {
|
|
33
|
+
if (!result.ok)
|
|
34
|
+
fail(`${what} — ${result.error.code}: ${result.error.message}`);
|
|
35
|
+
return result.value;
|
|
36
|
+
}
|
|
37
|
+
/** Unwraps a result the case expects to have failed, with this code. */
|
|
38
|
+
function failed(result, code, what) {
|
|
39
|
+
holds(!result.ok, `${what} — it succeeded instead`);
|
|
40
|
+
equals(result.error.code, code, `${what} — the code says ${code}`);
|
|
41
|
+
return result.error;
|
|
42
|
+
}
|
|
43
|
+
const joined = (deltas, type) => deltas.filter((delta) => delta.type === type).map((delta) => "text" in delta ? delta.text : delta.arguments).join("");
|
|
44
|
+
/** Drains a generation, keeping what it streamed on the way to its result. */
|
|
45
|
+
async function drive(generation) {
|
|
46
|
+
const deltas = [];
|
|
47
|
+
let step = await generation.next();
|
|
48
|
+
while (!step.done) {
|
|
49
|
+
deltas.push(step.value);
|
|
50
|
+
step = await generation.next();
|
|
51
|
+
}
|
|
52
|
+
return { deltas, result: step.value };
|
|
53
|
+
}
|
|
54
|
+
/** One opaque pixel: enough for an adapter to carry, too small to matter. */
|
|
55
|
+
const PIXEL = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
|
|
56
|
+
const user = (text) => ({ role: "user", content: text });
|
|
57
|
+
/** The arguments a call carries, compared as JSON rather than as a string a wire may re-spell. */
|
|
58
|
+
const parsed = (raw, what) => {
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return fail(`${what} — ${JSON.stringify(raw)} is not JSON`);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const asked = [
|
|
67
|
+
{ callId: "call-1", name: "balance", arguments: "{\"account\":\"main\",\"limit\":3}" },
|
|
68
|
+
// A call with nothing to pass. Both wires stream no fragments for it, and a
|
|
69
|
+
// caller still has to be handed arguments it can parse.
|
|
70
|
+
{ callId: "call-2", name: "clock", arguments: "{}" },
|
|
71
|
+
];
|
|
72
|
+
const tools = [
|
|
73
|
+
{
|
|
74
|
+
name: "balance", description: "Read an account balance",
|
|
75
|
+
parameters: { type: "object", properties: { account: { type: "string" } }, required: ["account"] },
|
|
76
|
+
},
|
|
77
|
+
{ name: "clock", description: "The time now", parameters: { type: "object", properties: {} } },
|
|
78
|
+
];
|
|
79
|
+
/** A turn of every shape the port carries: instruction, question, call, result, reply. */
|
|
80
|
+
const conversation = [
|
|
81
|
+
{ role: "system", content: "You are a bookkeeper." },
|
|
82
|
+
{ role: "user", content: "What is the balance?" },
|
|
83
|
+
{ role: "assistant", content: "Looking that up.", calls: [{ callId: "call-1", name: "balance", arguments: "{\"account\":\"main\"}" }] },
|
|
84
|
+
{ role: "tool", callId: "call-1", content: "1250" },
|
|
85
|
+
{ role: "user", content: "Thanks." },
|
|
86
|
+
];
|
|
87
|
+
export function defineModelConformance(subject) {
|
|
88
|
+
const cases = [];
|
|
89
|
+
const define = (name, run) => { cases.push({ name, run }); };
|
|
90
|
+
/** One scripted generation, driven to its end. */
|
|
91
|
+
const answering = async (script, request) => {
|
|
92
|
+
const primed = await subject.answering(script);
|
|
93
|
+
return await drive(primed.model.generate(request));
|
|
94
|
+
};
|
|
95
|
+
// ---- The shape of an answer ---------------------------------------------
|
|
96
|
+
define("a generation ends in one result, and yields nothing after it", async () => {
|
|
97
|
+
// A generator that falls off its end returns `undefined`, which typechecks
|
|
98
|
+
// as nothing and tells a caller nothing: the loop reads `step.value.ok` to
|
|
99
|
+
// decide whether the run failed, and would throw on the way to deciding.
|
|
100
|
+
const primed = await subject.answering({ kind: "text", deltas: ["one ", "two"] });
|
|
101
|
+
const generation = primed.model.generate({ messages: [user("say something")] });
|
|
102
|
+
let step = await generation.next();
|
|
103
|
+
while (!step.done) {
|
|
104
|
+
holds(["text.delta", "reasoning.delta", "tool-call.delta"].includes(step.value.type), `every delta is one of the port's three kinds — got ${JSON.stringify(step.value)}`);
|
|
105
|
+
step = await generation.next();
|
|
106
|
+
}
|
|
107
|
+
holds(typeof step.value?.ok === "boolean", "a generation returns a Result, never nothing");
|
|
108
|
+
const after = await generation.next();
|
|
109
|
+
holds(after.done === true && after.value === undefined, "and nothing follows the result");
|
|
110
|
+
});
|
|
111
|
+
define("the text a caller streamed is the text the message ends up holding", async () => {
|
|
112
|
+
const deltas = ["The ", "balance ", "is 1250."];
|
|
113
|
+
const streamed = await answering({ kind: "text", deltas }, { messages: [user("what is the balance?")] });
|
|
114
|
+
const response = got(streamed.result, "generate");
|
|
115
|
+
equals(joined(streamed.deltas, "text.delta"), deltas.join(""), "the caller saw the answer arrive");
|
|
116
|
+
equals(textOf(response.message.content), deltas.join(""), "and the message holds the same words");
|
|
117
|
+
equals(response.finishReason, "stop", "a turn that simply answered stopped");
|
|
118
|
+
equals(response.message.calls ?? [], [], "and asked for nothing");
|
|
119
|
+
});
|
|
120
|
+
define("a turn that asks for tools carries every call, with its id and arguments that parse", async () => {
|
|
121
|
+
const streamed = await answering({ kind: "tool-calls", calls: asked }, {
|
|
122
|
+
messages: [user("look it up")], tools,
|
|
123
|
+
});
|
|
124
|
+
const response = got(streamed.result, "generate");
|
|
125
|
+
const reported = response.message.calls ?? [];
|
|
126
|
+
equals(reported.map((call) => call.callId), asked.map((call) => call.callId), "every call comes back, under the id the provider gave it — the id is what a result is paired with");
|
|
127
|
+
equals(reported.map((call) => call.name), asked.map((call) => call.name), "and the tool it asked for");
|
|
128
|
+
for (const [at, call] of reported.entries()) {
|
|
129
|
+
// Complete JSON, not the fragments it arrived as. A caller parses this to
|
|
130
|
+
// decide whether a tool may run at all, so half of an object is worse than
|
|
131
|
+
// none — it parses on a good day.
|
|
132
|
+
equals(parsed(call.arguments, `call ${call.callId}`), parsed(asked[at].arguments, "the script"), "the arguments parse, whole");
|
|
133
|
+
}
|
|
134
|
+
equals(response.finishReason, "tool-calls", "a turn holding calls says that is why it stopped");
|
|
135
|
+
});
|
|
136
|
+
define("a turn cut off at the output ceiling says so", async () => {
|
|
137
|
+
// The reason the loop cannot read this from the absence of tool calls: a
|
|
138
|
+
// truncated turn looks exactly like a finished one, and reporting it as an
|
|
139
|
+
// answer hands the caller half a sentence dressed as a whole one.
|
|
140
|
+
const streamed = await answering({ kind: "truncated", text: "The balance is 12" }, { messages: [user("go")] });
|
|
141
|
+
const response = got(streamed.result, "generate");
|
|
142
|
+
equals(response.finishReason, "length", "the ceiling, not a full stop");
|
|
143
|
+
equals(textOf(response.message.content), "The balance is 12", "and what did arrive is kept");
|
|
144
|
+
});
|
|
145
|
+
define("a turn cut off while it was asking for tools still says it was cut off", async () => {
|
|
146
|
+
// The precedence the loop's truncation guard rests on. A response holding
|
|
147
|
+
// calls looks like a turn that asked for them, and half a request can parse
|
|
148
|
+
// as whole JSON — so an adapter that lets the calls decide the reason hands
|
|
149
|
+
// a batch built from a severed argument list to a tool that will run it.
|
|
150
|
+
const streamed = await answering({ kind: "truncated", text: "Looking", calls: [asked[0]] }, {
|
|
151
|
+
messages: [user("go")], tools,
|
|
152
|
+
});
|
|
153
|
+
const response = got(streamed.result, "generate");
|
|
154
|
+
equals(response.finishReason, "length", "the ceiling outranks the calls");
|
|
155
|
+
equals((response.message.calls ?? []).length, 1, "and the calls that did arrive are still reported, so the model can be told why they did not run");
|
|
156
|
+
});
|
|
157
|
+
define("a turn the provider declined says so", async () => {
|
|
158
|
+
const streamed = await answering({ kind: "refused" }, { messages: [user("go")] });
|
|
159
|
+
equals(got(streamed.result, "generate").finishReason, "refusal", "a refusal is not a completed answer");
|
|
160
|
+
});
|
|
161
|
+
define("the counts the provider reported reach the caller, and so does which model answered", async () => {
|
|
162
|
+
const counted = { inputTokens: 120, outputTokens: 6, cacheReadTokens: 80 };
|
|
163
|
+
const streamed = await answering({ kind: "usage", usage: counted, model: "acme/small" }, {
|
|
164
|
+
messages: [user("say ok")],
|
|
165
|
+
});
|
|
166
|
+
const response = got(streamed.result, "generate");
|
|
167
|
+
equals(response.usage, counted, "the counts come back as they were reported");
|
|
168
|
+
// Not the id the model was built with: a provider may answer with a dated
|
|
169
|
+
// variant, and an application pricing a run has to price what served it.
|
|
170
|
+
equals(response.model, "acme/small", "and the model that actually served the request");
|
|
171
|
+
});
|
|
172
|
+
define("the input counts do not overlap, whichever wire answered", async () => {
|
|
173
|
+
// The wires disagree: Anthropic's `input_tokens` already excludes what was
|
|
174
|
+
// cached, OpenAI's `prompt_tokens` includes it. `Usage` picks one meaning —
|
|
175
|
+
// disjoint — so an application never has to ask which one it is holding.
|
|
176
|
+
//
|
|
177
|
+
// A caller sums. This is the case that lets it: the total below is only
|
|
178
|
+
// right if no field is counted twice, and the one that used to subtract
|
|
179
|
+
// instead priced a cached Anthropic turn's fresh tokens at zero.
|
|
180
|
+
const split = { inputTokens: 40, outputTokens: 6, cacheReadTokens: 80 };
|
|
181
|
+
const streamed = await answering({ kind: "usage", usage: split, model: "acme/small" }, {
|
|
182
|
+
messages: [user("say ok")],
|
|
183
|
+
});
|
|
184
|
+
const { usage } = got(streamed.result, "generate");
|
|
185
|
+
equals(usage.inputTokens, 40, "input excludes what was served from cache");
|
|
186
|
+
equals(usage.cacheReadTokens, 80, "and the cached tokens are reported beside it, not inside it");
|
|
187
|
+
equals((usage.inputTokens ?? 0) + (usage.cacheReadTokens ?? 0), 120, "so the prompt's size is the sum");
|
|
188
|
+
});
|
|
189
|
+
if (subject.cost === "reported") {
|
|
190
|
+
define("a cost the provider states reaches the caller", async () => {
|
|
191
|
+
// Carried, never computed. An application that had to price this itself
|
|
192
|
+
// was reconstructing, from a table it maintains by hand, a number the
|
|
193
|
+
// wire had already given it — and for a router it could not: the upstream
|
|
194
|
+
// provider is chosen per request.
|
|
195
|
+
const charged = { inputTokens: 120, outputTokens: 6, costUsd: 0.0042 };
|
|
196
|
+
const streamed = await answering({ kind: "usage", usage: charged, model: "acme/small" }, {
|
|
197
|
+
messages: [user("say ok")],
|
|
198
|
+
});
|
|
199
|
+
equals(got(streamed.result, "generate").usage.costUsd, 0.0042, "the charge comes back as it was reported");
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
define("a wire that states no cost does not invent one", async () => {
|
|
204
|
+
const streamed = await answering({ kind: "usage", usage: { inputTokens: 120, outputTokens: 6 }, model: "acme/small" }, { messages: [user("say ok")] });
|
|
205
|
+
absent(got(streamed.result, "generate").usage.costUsd, "usage.costUsd is absent on a wire that does not carry one");
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
define("what the provider did not report is absent, not zero", async () => {
|
|
209
|
+
const streamed = await answering({ kind: "silent" }, { messages: [user("say ok")] });
|
|
210
|
+
const response = got(streamed.result, "generate");
|
|
211
|
+
for (const field of ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens", "costUsd"]) {
|
|
212
|
+
// A zero is a count. An application summing them for a ceiling would be
|
|
213
|
+
// told this generation was free, which is a different claim from silence.
|
|
214
|
+
absent(response.usage[field], `usage.${field} is absent when the provider reported none`);
|
|
215
|
+
}
|
|
216
|
+
absent(response.model, "and the model that answered is not invented");
|
|
217
|
+
});
|
|
218
|
+
define("a model names itself", async () => {
|
|
219
|
+
const { model } = await subject.answering({ kind: "text", deltas: ["hello"] });
|
|
220
|
+
holds(typeof model.id === "string" && model.id.length > 0, "a model has an id, so a log can say which one ran");
|
|
221
|
+
});
|
|
222
|
+
// ---- Giving up ----------------------------------------------------------
|
|
223
|
+
define("a generation cancelled before it starts fails cancelled, and does not throw", async () => {
|
|
224
|
+
const streamed = await answering({ kind: "text", deltas: ["never ", "delivered"] }, {
|
|
225
|
+
messages: [user("go")], signal: AbortSignal.abort(),
|
|
226
|
+
});
|
|
227
|
+
const error = failed(streamed.result, "cancelled", "an already-cancelled generation reports cancelled");
|
|
228
|
+
equals(error.retryable, false, "a caller that gave up is not told to try again");
|
|
229
|
+
equals(streamed.deltas, [], "and nothing was streamed to it");
|
|
230
|
+
});
|
|
231
|
+
// ---- What differs, as the subject declares it ---------------------------
|
|
232
|
+
if (subject.reasoning === "streamed") {
|
|
233
|
+
define("reasoning reaches the caller as deltas, and never as something the model said", async () => {
|
|
234
|
+
const streamed = await answering({ kind: "reasoning", thoughts: ["Check ", "the ledger."], text: "1250" }, {
|
|
235
|
+
messages: [user("what is the balance?")], effort: "high",
|
|
236
|
+
});
|
|
237
|
+
const response = got(streamed.result, "generate");
|
|
238
|
+
equals(joined(streamed.deltas, "reasoning.delta"), "Check the ledger.", "the caller saw it being thought");
|
|
239
|
+
// Reasoning is not speech. Folded into the message it would be committed
|
|
240
|
+
// as the assistant's words and replayed to the provider next turn, as if
|
|
241
|
+
// the model had said aloud what it only thought.
|
|
242
|
+
equals(textOf(response.message.content), "1250", "and the message holds only what was said");
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
define("an implementation that carries no reasoning emits none", async () => {
|
|
247
|
+
const streamed = await answering({ kind: "text", deltas: ["hello"] }, { messages: [user("go")] });
|
|
248
|
+
holds(!streamed.deltas.some((delta) => delta.type === "reasoning.delta"), "a subject declaring no reasoning must not emit a delta the suite would then not check");
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (subject.toolArguments === "streamed") {
|
|
252
|
+
define("a tool call's arguments reach the caller as they are produced", async () => {
|
|
253
|
+
const call = asked[0];
|
|
254
|
+
const streamed = await answering({ kind: "tool-calls", calls: [call] }, {
|
|
255
|
+
messages: [user("look it up")], tools,
|
|
256
|
+
});
|
|
257
|
+
const [reported] = got(streamed.result, "generate").message.calls ?? [];
|
|
258
|
+
holds(reported !== undefined, "the call came back");
|
|
259
|
+
const fragments = streamed.deltas
|
|
260
|
+
.filter((delta) => delta.type === "tool-call.delta");
|
|
261
|
+
holds(fragments.length > 0, "the arguments were streamed at all");
|
|
262
|
+
equals([...new Set(fragments.map((delta) => delta.callId))], [reported.callId], "every fragment names the call it belongs to — an application renders them per call");
|
|
263
|
+
equals(fragments.map((delta) => delta.arguments).join(""), reported.arguments, "and they reassemble into exactly what the call ended up holding");
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
define("an implementation that does not stream arguments emits no tool-call deltas", async () => {
|
|
268
|
+
const streamed = await answering({ kind: "tool-calls", calls: asked }, {
|
|
269
|
+
messages: [user("look it up")], tools,
|
|
270
|
+
});
|
|
271
|
+
holds(!streamed.deltas.some((delta) => delta.type === "tool-call.delta"), "a subject declaring `whole` must not emit a delta the suite would then not check");
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
if (subject.wire === "none") {
|
|
275
|
+
define("an implementation with no wire under it says so by not offering one", async () => {
|
|
276
|
+
const primed = await subject.answering({ kind: "text", deltas: ["hello"] });
|
|
277
|
+
holds(primed.sent === undefined, "a subject that can report what it sent declares a wire, rather than offering a report nothing reads");
|
|
278
|
+
});
|
|
279
|
+
return cases;
|
|
280
|
+
}
|
|
281
|
+
const wire = subject.wire;
|
|
282
|
+
// ---- What the provider refused ------------------------------------------
|
|
283
|
+
const refusals = [
|
|
284
|
+
{ status: 401, body: "{\"error\":{\"message\":\"invalid api key\"}}", code: "auth", retryable: false },
|
|
285
|
+
{ status: 429, body: "{\"error\":{\"message\":\"rate limit exceeded\"}}", code: "rate-limit", retryable: true },
|
|
286
|
+
{
|
|
287
|
+
status: 400,
|
|
288
|
+
body: "{\"error\":{\"message\":\"prompt is too long: 300000 tokens > 200000 maximum context length\"}}",
|
|
289
|
+
code: "context-length", retryable: false,
|
|
290
|
+
},
|
|
291
|
+
{ status: 500, body: "upstream is unwell", code: "provider", retryable: true },
|
|
292
|
+
{ status: 404, body: "{\"error\":{\"message\":\"no such model\"}}", code: "failed", retryable: false },
|
|
293
|
+
];
|
|
294
|
+
for (const refusal of refusals) {
|
|
295
|
+
define(`a ${refusal.status} from the provider is a ${refusal.code} failure a caller can act on`, async () => {
|
|
296
|
+
const streamed = await answering({ kind: "status", status: refusal.status, body: refusal.body }, {
|
|
297
|
+
messages: [user("go")],
|
|
298
|
+
});
|
|
299
|
+
const error = failed(streamed.result, refusal.code, `${refusal.status} is ${refusal.code}`);
|
|
300
|
+
// The whole of what a backoff loop reads. Getting it wrong costs either a
|
|
301
|
+
// run that gives up on a rate limit or one that hammers a bad credential.
|
|
302
|
+
equals(error.retryable, refusal.retryable, `a ${refusal.code} failure says whether trying again is worth it`);
|
|
303
|
+
holds(error.message.length > 0, "and carries something a human can read");
|
|
304
|
+
equals(streamed.deltas, [], "a refused request streamed nothing");
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
define("a body that dies part way through is a failure, not a thrown generator", async () => {
|
|
308
|
+
// Without this the generator throws, `run.result` rejects instead of
|
|
309
|
+
// resolving, no `run.finished` is ever committed, and the activation stays
|
|
310
|
+
// open until its claim expires.
|
|
311
|
+
const streamed = await answering({ kind: "cut", text: "The bal" }, { messages: [user("go")] });
|
|
312
|
+
const error = failed(streamed.result, "provider", "a dropped body is a provider failure");
|
|
313
|
+
equals(error.retryable, true, "a connection that dropped is worth trying again");
|
|
314
|
+
holds(streamed.deltas.length > 0, "and what did arrive had already reached the caller");
|
|
315
|
+
});
|
|
316
|
+
define("a generation cancelled while it streams ends cancelled, not as a provider fault", async () => {
|
|
317
|
+
const primed = await subject.answering({ kind: "text", deltas: ["one ", "two ", "three"] });
|
|
318
|
+
const controller = new AbortController();
|
|
319
|
+
const generation = primed.model.generate({ messages: [user("count")], signal: controller.signal });
|
|
320
|
+
const first = await generation.next();
|
|
321
|
+
holds(first.done === false, "the first delta arrived");
|
|
322
|
+
// The moment that matters: a caller that has read something and then gives
|
|
323
|
+
// up. A stream torn down under an adapter looks exactly like one the network
|
|
324
|
+
// dropped, and only the signal tells them apart.
|
|
325
|
+
controller.abort();
|
|
326
|
+
let step = await generation.next();
|
|
327
|
+
while (!step.done)
|
|
328
|
+
step = await generation.next();
|
|
329
|
+
equals(failed(step.value, "cancelled", "a cancelled stream reports cancelled").retryable, false, "a caller that gave up is not told to try again");
|
|
330
|
+
});
|
|
331
|
+
// ---- What went out ------------------------------------------------------
|
|
332
|
+
/** One scripted generation, reported back as the request it put on the wire. */
|
|
333
|
+
const sending = async (request) => {
|
|
334
|
+
const primed = await subject.answering({ kind: "text", deltas: ["ok"] });
|
|
335
|
+
holds(primed.sent !== undefined, "a subject declaring a wire owes a report of what it sent");
|
|
336
|
+
got((await drive(primed.model.generate(request))).result, "generate");
|
|
337
|
+
return primed.sent();
|
|
338
|
+
};
|
|
339
|
+
define("everything the caller said reaches the provider, in the order it was said", async () => {
|
|
340
|
+
// Every role the port carries, in one turn. The wires disagree about where
|
|
341
|
+
// each one goes — an instruction is hoisted out of the list on one and left
|
|
342
|
+
// in it on the other, a tool result is a message here and a block inside the
|
|
343
|
+
// previous user turn there — and none of that may lose a word or reorder one.
|
|
344
|
+
const sent = await sending({ messages: conversation });
|
|
345
|
+
equals(sent.text, ["You are a bookkeeper.", "What is the balance?", "Looking that up.", "1250", "Thanks."], "the instruction, the question, the reply, the result and the follow-up all arrive, in that order");
|
|
346
|
+
});
|
|
347
|
+
define("a tool result reaches the provider paired with the call it answers", async () => {
|
|
348
|
+
// Unpaired, a provider rejects the turn outright: it has an answer and no
|
|
349
|
+
// question. This is the one encoding mistake that makes a session unusable
|
|
350
|
+
// rather than merely worse.
|
|
351
|
+
const sent = await sending({ messages: conversation, tools });
|
|
352
|
+
equals(sent.toolResults, [{ callId: "call-1", content: "1250" }], "the result carries the id it answers");
|
|
353
|
+
equals(sent.toolCalls.map((call) => call.callId), ["call-1"], "and the call it answers went back with it");
|
|
354
|
+
equals(parsed(sent.toolCalls[0].arguments, "the call sent back"), { account: "main" }, "still holding the arguments the model asked with");
|
|
355
|
+
});
|
|
356
|
+
define("the tools the caller advertised reach the provider with their schemas", async () => {
|
|
357
|
+
const sent = await sending({ messages: [user("look it up")], tools });
|
|
358
|
+
equals([...sent.toolNames].sort(), tools.map((tool) => tool.name).sort(), "every tool is advertised");
|
|
359
|
+
for (const tool of tools) {
|
|
360
|
+
equals(sent.toolSchemas[tool.name], tool.parameters, `${tool.name} is advertised with the schema it declared — the model calls what it was shown`);
|
|
361
|
+
}
|
|
362
|
+
equals((await sending({ messages: [user("look it up")] })).toolNames, [], "and a request with no tools advertises none");
|
|
363
|
+
});
|
|
364
|
+
define("an image reaches the provider as an image", async () => {
|
|
365
|
+
const sent = await sending({
|
|
366
|
+
messages: [{
|
|
367
|
+
role: "user",
|
|
368
|
+
content: [
|
|
369
|
+
{ type: "text", text: "what is this?" },
|
|
370
|
+
{ type: "image", mediaType: "image/png", source: { kind: "inline", data: PIXEL } },
|
|
371
|
+
],
|
|
372
|
+
}],
|
|
373
|
+
});
|
|
374
|
+
equals(sent.mediaTypes, ["image/png"], "the picture is transmitted as a picture, not as prose about one");
|
|
375
|
+
holds(sent.text.includes("what is this?"), "and the text beside it is not lost carrying it");
|
|
376
|
+
});
|
|
377
|
+
define("a block this wire cannot carry is dropped, not stringified into something the model reads", async () => {
|
|
378
|
+
// An opaque block belongs to the provider that made it and is valid on no
|
|
379
|
+
// other wire. The tempting fix is to serialize what cannot be sent, which
|
|
380
|
+
// puts another provider's internals — signatures and all — in front of the
|
|
381
|
+
// model as if a person had typed them.
|
|
382
|
+
const sent = await sending({
|
|
383
|
+
messages: [{
|
|
384
|
+
role: "user",
|
|
385
|
+
content: [
|
|
386
|
+
{ type: "text", text: "what is this?" },
|
|
387
|
+
{ type: "opaque", provider: "another-provider", data: { signature: "sig-9" } },
|
|
388
|
+
],
|
|
389
|
+
}],
|
|
390
|
+
});
|
|
391
|
+
equals(sent.text, ["what is this?"], "the words go, the block this wire has no place for does not");
|
|
392
|
+
});
|
|
393
|
+
define("the output ceiling the caller set reaches the provider", async () => {
|
|
394
|
+
equals((await sending({ messages: [user("go")], maxOutputTokens: 64 })).maxOutputTokens, 64, "a ceiling nothing transmits is a ceiling nothing enforces");
|
|
395
|
+
});
|
|
396
|
+
define(`effort is ${wire.effort === "sent" ? "sent as this provider's own depth control" : "dropped rather than sent"}`, async () => {
|
|
397
|
+
const withEffort = await sending({ messages: [user("think first")], effort: "high" });
|
|
398
|
+
absent((await sending({ messages: [user("think first")] })).effort, "a request that asked for no effort carries no depth control");
|
|
399
|
+
if (wire.effort === "ignored") {
|
|
400
|
+
absent(withEffort.effort, "a provider with no such control is given none, rather than a budget invented for it");
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
holds(withEffort.effort !== undefined, "the level the caller asked for reaches the provider");
|
|
404
|
+
});
|
|
405
|
+
define(`temperature is ${wire.temperature === "sent" ? "sent" : "dropped rather than sent to a provider that rejects it"}`, async () => {
|
|
406
|
+
const withTemperature = await sending({ messages: [user("go")], temperature: 0.2 });
|
|
407
|
+
absent((await sending({ messages: [user("go")] })).temperature, "a request that set none carries none");
|
|
408
|
+
if (wire.temperature === "ignored") {
|
|
409
|
+
// Not an oversight: the current Anthropic models reject sampling
|
|
410
|
+
// controls, so forwarding one turns every request into a 400.
|
|
411
|
+
absent(withTemperature.temperature, "a provider that rejects sampling controls is sent none");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
equals(withTemperature.temperature, 0.2, "the value the caller set reaches the provider");
|
|
415
|
+
});
|
|
416
|
+
define(`cacheAfter is ${wire.cache === "sent" ? "marked on the wire" : "dropped on a wire with no breakpoint"}`, async () => {
|
|
417
|
+
const marked = await sending({ messages: conversation, cacheAfter: 1 });
|
|
418
|
+
equals((await sending({ messages: conversation })).cacheMarks, 0, "a request that asked for no breakpoint marks none");
|
|
419
|
+
if (wire.cache === "ignored") {
|
|
420
|
+
equals(marked.cacheMarks, 0, "a wire that caches by itself is not sent a mark it has no field for");
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
holds(marked.cacheMarks > 0, "the prefix the caller ended is marked, or nothing is cached and every turn pays");
|
|
424
|
+
});
|
|
425
|
+
return cases;
|
|
426
|
+
}
|
|
427
|
+
//# sourceMappingURL=conformance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.js","sourceRoot":"","sources":["../../src/model/conformance.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What any implementation of the model port must do.\n *\n * The port is one method, and almost everything it promises is about the shape\n * of an answer rather than its content: exactly one result and nothing after it,\n * a cancellation that arrives as a value, deltas that add up to the message,\n * tool arguments that parse, counts that stay absent when nobody reported them.\n * Those are the promises a run recovers on — and every one of them is invisible\n * to a test that only asks a model a question and reads the reply.\n *\n * So the suite cannot supply requests and expect responses: what a provider says\n * is the provider's, and two of the three implementations here reach one. What\n * a subject is asked for instead is a model **primed to say a scripted thing** —\n * the script in the port's own vocabulary, rendered by the subject onto whatever\n * it actually speaks, which for a wire is a stream of frames and for a fake is a\n * scripted response. A case then drives that model and holds the answer to the\n * port, without ever naming a provider's fields.\n *\n * The same trick runs the other way. An adapter that decodes a response\n * perfectly and never transmits the caller's tools is useless, and that half of\n * an adapter is pure encoding — which is why a wire also hands back a\n * `SentRequest`: its own translation of what went out, back into the port's\n * vocabulary. The alternative was to assert on the raw body, which would make\n * every case provider-specific and so not a shared contract at all.\n *\n * One request per model. Nothing in this port is a conversation — a `Model` is a\n * value you call — so a case wanting a second answer asks for a second model,\n * and no subject has to decide what a spent script means.\n *\n * Inert on purpose. Each case is a name and a function that throws, so the suite\n * drags no test framework into the package:\n *\n * ```ts\n * for (const item of defineModelConformance(subject)) test(item.name, item.run);\n * ```\n */\nimport type {\n Message, Model, ModelDelta, ModelError, ModelRequest, ModelResponse, ToolCall, ToolSpec, Usage,\n} from \"./model.js\";\nimport type { Result } from \"../result.js\";\nimport type { JsonValue } from \"../json.js\";\nimport { textOf } from \"../content.js\";\n\n/** One case: a name, and a function that throws when the contract is broken. */\nexport interface ConformanceCase {\n name: string;\n run(): Promise<void>;\n}\n\n/**\n * What the provider should say, in the port's vocabulary.\n *\n * A subject renders each of these onto whatever it speaks. Two rendering rules\n * carry cases of their own, so they are stated here rather than assumed:\n *\n * - **Deltas arrive one at a time.** A subject that hands the whole body over at\n * once cannot be cancelled part way through, and the case that pulls one delta\n * and then gives up would prove nothing.\n * - **A wire behaves as `fetch` does about the signal.** An already-aborted\n * request never reaches the provider, and a body dies when the caller gives\n * up. A transport that ignores the signal lets an adapter that never forwarded\n * it pass both cancellation cases.\n */\nexport type ModelScript =\n /** An answer that arrives in pieces. */\n | { kind: \"text\"; deltas: readonly string[] }\n /** An answer the model thought about first, aloud. */\n | { kind: \"reasoning\"; thoughts: readonly string[]; text: string }\n /** A turn that asks for tools, with each call's arguments fragmented on the way. */\n | { kind: \"tool-calls\"; calls: readonly ToolCall[] }\n /** An answer the provider cut off at the output ceiling, mid-sentence or mid-call. */\n | { kind: \"truncated\"; text: string; calls?: readonly ToolCall[] }\n /** An answer the provider declined to give. */\n | { kind: \"refused\" }\n /** An answer reporting exactly these counts, from exactly this model. */\n | { kind: \"usage\"; usage: Usage; model: string }\n /** An answer that says nothing about counts or about which model served it. */\n | { kind: \"silent\" }\n /** The provider refusing the request outright. Wires only. */\n | { kind: \"status\"; status: number; body: string }\n /** A body that delivers this much and then dies. Wires only. */\n | { kind: \"cut\"; text: string };\n\n/**\n * What one outgoing request carried, translated back out of the wire's own\n * vocabulary by the subject. Absence is meaningful: an empty `toolNames`\n * asserts the request advertised no tools, and a missing `effort` asserts it\n * carried no reasoning control at all.\n */\nexport interface SentRequest {\n /**\n * Every text the request transmitted for the model to read, in order —\n * instructions first, then the conversation, wherever this wire puts them.\n * One list rather than a field per role, because the roles are exactly what\n * the wires disagree about: a tool result is a message on one and a block\n * inside the previous user turn on another, and what an adapter owes is that\n * the words arrive in the order they were said.\n */\n text: readonly string[];\n /** The names of the tools the request advertised. */\n toolNames: readonly string[];\n /** The JSON Schema the request advertised for each advertised tool. */\n toolSchemas: Readonly<Record<string, JsonValue>>;\n /** Each tool result the request carried, paired with the call it answers. */\n toolResults: readonly { callId: string; content: string }[];\n /** Each tool call the request carried back, so the provider can pair the result with it. */\n toolCalls: readonly ToolCall[];\n /** Media types of the image and file content the request transmitted, in order. */\n mediaTypes: readonly string[];\n /** The output ceiling the request carried, or absent where it carried none. */\n maxOutputTokens?: number;\n /** The provider-native reasoning control the request carried, verbatim, or absent. */\n effort?: JsonValue;\n /** The sampling control the request carried, or absent. */\n temperature?: number;\n /** How many cache breakpoints the request marked. */\n cacheMarks: number;\n}\n\n/** A model primed to answer one request, and — where there is a wire — that request. */\nexport interface Answering {\n model: Model;\n /** What the one request this model was given actually carried. Wires only. */\n sent?(): SentRequest;\n}\n\n/**\n * What this implementation does with the three request options a provider may\n * refuse, each of which the port says is honoured or honestly ignored.\n *\n * Declared rather than inferred from the request, because both answers look\n * identical from outside and only one of them is a defect: dropping `temperature`\n * is what the current Anthropic models require, and dropping it silently on a\n * wire that would have accepted it is a caller's control going nowhere.\n */\nexport interface Wire {\n /** Whether a level reaches the provider as its own depth control. */\n effort: \"sent\" | \"ignored\";\n /** Whether a sampling control reaches a provider that accepts one. */\n temperature: \"sent\" | \"ignored\";\n /** Whether `cacheAfter` reaches the provider as a breakpoint. */\n cache: \"sent\" | \"ignored\";\n}\n\nexport interface ModelUnderTest {\n /**\n * A model primed to answer one request with `script`.\n *\n * Called once per request, so nothing leaks between cases, and never with a\n * script this subject's declarations say it cannot play.\n */\n answering(script: ModelScript): Answering | Promise<Answering>;\n /**\n * The transport under this implementation, where there is one the subject can\n * script and read back.\n *\n * `\"none\"` is a claim about the implementation, not a way out of the cases: a\n * model answering from something it already holds has no status to return, no\n * body to cut in half and no request to report — and those are the cases a\n * provider adapter most needs, so one that declares `\"none\"` is untested where\n * it matters.\n */\n wire: Wire | \"none\";\n /**\n * Whether a tool call's arguments reach the caller as they arrive.\n *\n * Declared rather than inferred from an empty stream, because \"streamed\n * nothing\" and \"asked for no tools this turn\" look the same from outside, and\n * an application rendering a call as it is typed would find out in production.\n */\n toolArguments: \"streamed\" | \"whole\";\n /** Whether this implementation can carry a model's reasoning back as deltas. */\n reasoning: \"streamed\" | \"none\";\n /**\n * Whether this wire states what a generation cost.\n *\n * A router does — it picks an upstream provider per request and adds its own\n * margin, so what it charged is a thing only it can say, and no rate table an\n * application keeps can reconstruct it. Most wires do not, and there the\n * field stays absent rather than being estimated from one.\n *\n * Declared both ways, because both are a promise: one that reports a cost has\n * to carry it, and one that does not has to leave it alone rather than\n * inventing a zero.\n */\n cost: \"reported\" | \"none\";\n}\n\n/** One generation, driven to its end. */\ninterface Streamed {\n deltas: readonly ModelDelta[];\n result: Result<ModelResponse, ModelError>;\n}\n\nfunction fail(what: string): never {\n throw new Error(`model conformance: ${what}`);\n}\n\nfunction holds(condition: boolean, what: string): asserts condition {\n if (!condition) fail(what);\n}\n\nfunction same(actual: unknown, expected: unknown): boolean {\n if (actual === expected) return true;\n if (Array.isArray(actual) && Array.isArray(expected)) {\n return actual.length === expected.length && actual.every((item, at) => same(item, expected[at]));\n }\n if (typeof actual !== \"object\" || typeof expected !== \"object\" || !actual || !expected) return false;\n const left = Object.keys(actual as object).sort();\n const right = Object.keys(expected as object).sort();\n return same(left, right)\n && left.every((key) => same((actual as Record<string, unknown>)[key], (expected as Record<string, unknown>)[key]));\n}\n\nfunction equals(actual: unknown, expected: unknown, what: string): void {\n if (!same(actual, expected)) {\n fail(`${what} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);\n }\n}\n\nfunction absent(actual: unknown, what: string): void {\n if (actual !== undefined) fail(`${what} — expected nothing, got ${JSON.stringify(actual)}`);\n}\n\n/** Unwraps a result the case expects to have succeeded. */\nfunction got(result: Result<ModelResponse, ModelError>, what: string): ModelResponse {\n if (!result.ok) fail(`${what} — ${result.error.code}: ${result.error.message}`);\n return result.value;\n}\n\n/** Unwraps a result the case expects to have failed, with this code. */\nfunction failed(result: Result<ModelResponse, ModelError>, code: ModelError[\"code\"], what: string): ModelError {\n holds(!result.ok, `${what} — it succeeded instead`);\n equals(result.error.code, code, `${what} — the code says ${code}`);\n return result.error;\n}\n\nconst joined = (deltas: readonly ModelDelta[], type: ModelDelta[\"type\"]): string =>\n deltas.filter((delta) => delta.type === type).map((delta) => \"text\" in delta ? delta.text : delta.arguments).join(\"\");\n\n/** Drains a generation, keeping what it streamed on the way to its result. */\nasync function drive(\n generation: AsyncGenerator<ModelDelta, Result<ModelResponse, ModelError>>,\n): Promise<Streamed> {\n const deltas: ModelDelta[] = [];\n let step = await generation.next();\n while (!step.done) {\n deltas.push(step.value);\n step = await generation.next();\n }\n return { deltas, result: step.value };\n}\n\n/** One opaque pixel: enough for an adapter to carry, too small to matter. */\nconst PIXEL = \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==\";\n\nconst user = (text: string): Message => ({ role: \"user\", content: text });\n\n/** The arguments a call carries, compared as JSON rather than as a string a wire may re-spell. */\nconst parsed = (raw: string, what: string): unknown => {\n try { return JSON.parse(raw) as unknown; } catch { return fail(`${what} — ${JSON.stringify(raw)} is not JSON`); }\n};\n\nconst asked: readonly ToolCall[] = [\n { callId: \"call-1\", name: \"balance\", arguments: \"{\\\"account\\\":\\\"main\\\",\\\"limit\\\":3}\" },\n // A call with nothing to pass. Both wires stream no fragments for it, and a\n // caller still has to be handed arguments it can parse.\n { callId: \"call-2\", name: \"clock\", arguments: \"{}\" },\n];\n\nconst tools: readonly ToolSpec[] = [\n {\n name: \"balance\", description: \"Read an account balance\",\n parameters: { type: \"object\", properties: { account: { type: \"string\" } }, required: [\"account\"] },\n },\n { name: \"clock\", description: \"The time now\", parameters: { type: \"object\", properties: {} } },\n];\n\n/** A turn of every shape the port carries: instruction, question, call, result, reply. */\nconst conversation: readonly Message[] = [\n { role: \"system\", content: \"You are a bookkeeper.\" },\n { role: \"user\", content: \"What is the balance?\" },\n { role: \"assistant\", content: \"Looking that up.\", calls: [{ callId: \"call-1\", name: \"balance\", arguments: \"{\\\"account\\\":\\\"main\\\"}\" }] },\n { role: \"tool\", callId: \"call-1\", content: \"1250\" },\n { role: \"user\", content: \"Thanks.\" },\n];\n\nexport function defineModelConformance(subject: ModelUnderTest): readonly ConformanceCase[] {\n const cases: ConformanceCase[] = [];\n const define = (name: string, run: () => Promise<void>): void => { cases.push({ name, run }); };\n\n /** One scripted generation, driven to its end. */\n const answering = async (script: ModelScript, request: ModelRequest): Promise<Streamed> => {\n const primed = await subject.answering(script);\n return await drive(primed.model.generate(request));\n };\n\n // ---- The shape of an answer ---------------------------------------------\n\n define(\"a generation ends in one result, and yields nothing after it\", async () => {\n // A generator that falls off its end returns `undefined`, which typechecks\n // as nothing and tells a caller nothing: the loop reads `step.value.ok` to\n // decide whether the run failed, and would throw on the way to deciding.\n const primed = await subject.answering({ kind: \"text\", deltas: [\"one \", \"two\"] });\n const generation = primed.model.generate({ messages: [user(\"say something\")] });\n\n let step = await generation.next();\n while (!step.done) {\n holds([\"text.delta\", \"reasoning.delta\", \"tool-call.delta\"].includes(step.value.type),\n `every delta is one of the port's three kinds — got ${JSON.stringify(step.value)}`);\n step = await generation.next();\n }\n holds(typeof (step.value as { ok?: unknown } | undefined)?.ok === \"boolean\",\n \"a generation returns a Result, never nothing\");\n\n const after = await generation.next();\n holds(after.done === true && after.value === undefined, \"and nothing follows the result\");\n });\n\n define(\"the text a caller streamed is the text the message ends up holding\", async () => {\n const deltas = [\"The \", \"balance \", \"is 1250.\"];\n const streamed = await answering({ kind: \"text\", deltas }, { messages: [user(\"what is the balance?\")] });\n const response = got(streamed.result, \"generate\");\n\n equals(joined(streamed.deltas, \"text.delta\"), deltas.join(\"\"), \"the caller saw the answer arrive\");\n equals(textOf(response.message.content), deltas.join(\"\"), \"and the message holds the same words\");\n equals(response.finishReason, \"stop\", \"a turn that simply answered stopped\");\n equals(response.message.calls ?? [], [], \"and asked for nothing\");\n });\n\n define(\"a turn that asks for tools carries every call, with its id and arguments that parse\", async () => {\n const streamed = await answering({ kind: \"tool-calls\", calls: asked }, {\n messages: [user(\"look it up\")], tools,\n });\n const response = got(streamed.result, \"generate\");\n const reported = response.message.calls ?? [];\n\n equals(reported.map((call) => call.callId), asked.map((call) => call.callId),\n \"every call comes back, under the id the provider gave it — the id is what a result is paired with\");\n equals(reported.map((call) => call.name), asked.map((call) => call.name), \"and the tool it asked for\");\n for (const [at, call] of reported.entries()) {\n // Complete JSON, not the fragments it arrived as. A caller parses this to\n // decide whether a tool may run at all, so half of an object is worse than\n // none — it parses on a good day.\n equals(parsed(call.arguments, `call ${call.callId}`), parsed(asked[at]!.arguments, \"the script\"),\n \"the arguments parse, whole\");\n }\n equals(response.finishReason, \"tool-calls\", \"a turn holding calls says that is why it stopped\");\n });\n\n define(\"a turn cut off at the output ceiling says so\", async () => {\n // The reason the loop cannot read this from the absence of tool calls: a\n // truncated turn looks exactly like a finished one, and reporting it as an\n // answer hands the caller half a sentence dressed as a whole one.\n const streamed = await answering({ kind: \"truncated\", text: \"The balance is 12\" }, { messages: [user(\"go\")] });\n const response = got(streamed.result, \"generate\");\n equals(response.finishReason, \"length\", \"the ceiling, not a full stop\");\n equals(textOf(response.message.content), \"The balance is 12\", \"and what did arrive is kept\");\n });\n\n define(\"a turn cut off while it was asking for tools still says it was cut off\", async () => {\n // The precedence the loop's truncation guard rests on. A response holding\n // calls looks like a turn that asked for them, and half a request can parse\n // as whole JSON — so an adapter that lets the calls decide the reason hands\n // a batch built from a severed argument list to a tool that will run it.\n const streamed = await answering({ kind: \"truncated\", text: \"Looking\", calls: [asked[0]!] }, {\n messages: [user(\"go\")], tools,\n });\n const response = got(streamed.result, \"generate\");\n equals(response.finishReason, \"length\", \"the ceiling outranks the calls\");\n equals((response.message.calls ?? []).length, 1,\n \"and the calls that did arrive are still reported, so the model can be told why they did not run\");\n });\n\n define(\"a turn the provider declined says so\", async () => {\n const streamed = await answering({ kind: \"refused\" }, { messages: [user(\"go\")] });\n equals(got(streamed.result, \"generate\").finishReason, \"refusal\", \"a refusal is not a completed answer\");\n });\n\n define(\"the counts the provider reported reach the caller, and so does which model answered\", async () => {\n const counted: Usage = { inputTokens: 120, outputTokens: 6, cacheReadTokens: 80 };\n const streamed = await answering({ kind: \"usage\", usage: counted, model: \"acme/small\" }, {\n messages: [user(\"say ok\")],\n });\n const response = got(streamed.result, \"generate\");\n equals(response.usage, counted, \"the counts come back as they were reported\");\n // Not the id the model was built with: a provider may answer with a dated\n // variant, and an application pricing a run has to price what served it.\n equals(response.model, \"acme/small\", \"and the model that actually served the request\");\n });\n\n define(\"the input counts do not overlap, whichever wire answered\", async () => {\n // The wires disagree: Anthropic's `input_tokens` already excludes what was\n // cached, OpenAI's `prompt_tokens` includes it. `Usage` picks one meaning —\n // disjoint — so an application never has to ask which one it is holding.\n //\n // A caller sums. This is the case that lets it: the total below is only\n // right if no field is counted twice, and the one that used to subtract\n // instead priced a cached Anthropic turn's fresh tokens at zero.\n const split: Usage = { inputTokens: 40, outputTokens: 6, cacheReadTokens: 80 };\n const streamed = await answering({ kind: \"usage\", usage: split, model: \"acme/small\" }, {\n messages: [user(\"say ok\")],\n });\n const { usage } = got(streamed.result, \"generate\");\n equals(usage.inputTokens, 40, \"input excludes what was served from cache\");\n equals(usage.cacheReadTokens, 80, \"and the cached tokens are reported beside it, not inside it\");\n equals((usage.inputTokens ?? 0) + (usage.cacheReadTokens ?? 0), 120, \"so the prompt's size is the sum\");\n });\n\n if (subject.cost === \"reported\") {\n define(\"a cost the provider states reaches the caller\", async () => {\n // Carried, never computed. An application that had to price this itself\n // was reconstructing, from a table it maintains by hand, a number the\n // wire had already given it — and for a router it could not: the upstream\n // provider is chosen per request.\n const charged: Usage = { inputTokens: 120, outputTokens: 6, costUsd: 0.0042 };\n const streamed = await answering({ kind: \"usage\", usage: charged, model: \"acme/small\" }, {\n messages: [user(\"say ok\")],\n });\n equals(got(streamed.result, \"generate\").usage.costUsd, 0.0042, \"the charge comes back as it was reported\");\n });\n } else {\n define(\"a wire that states no cost does not invent one\", async () => {\n const streamed = await answering(\n { kind: \"usage\", usage: { inputTokens: 120, outputTokens: 6 }, model: \"acme/small\" },\n { messages: [user(\"say ok\")] },\n );\n absent(got(streamed.result, \"generate\").usage.costUsd,\n \"usage.costUsd is absent on a wire that does not carry one\");\n });\n }\n\n define(\"what the provider did not report is absent, not zero\", async () => {\n const streamed = await answering({ kind: \"silent\" }, { messages: [user(\"say ok\")] });\n const response = got(streamed.result, \"generate\");\n for (const field of [\"inputTokens\", \"outputTokens\", \"cacheReadTokens\", \"cacheWriteTokens\", \"costUsd\"] as const) {\n // A zero is a count. An application summing them for a ceiling would be\n // told this generation was free, which is a different claim from silence.\n absent(response.usage[field], `usage.${field} is absent when the provider reported none`);\n }\n absent(response.model, \"and the model that answered is not invented\");\n });\n\n define(\"a model names itself\", async () => {\n const { model } = await subject.answering({ kind: \"text\", deltas: [\"hello\"] });\n holds(typeof model.id === \"string\" && model.id.length > 0, \"a model has an id, so a log can say which one ran\");\n });\n\n // ---- Giving up ----------------------------------------------------------\n\n define(\"a generation cancelled before it starts fails cancelled, and does not throw\", async () => {\n const streamed = await answering({ kind: \"text\", deltas: [\"never \", \"delivered\"] }, {\n messages: [user(\"go\")], signal: AbortSignal.abort(),\n });\n const error = failed(streamed.result, \"cancelled\", \"an already-cancelled generation reports cancelled\");\n equals(error.retryable, false, \"a caller that gave up is not told to try again\");\n equals(streamed.deltas, [], \"and nothing was streamed to it\");\n });\n\n // ---- What differs, as the subject declares it ---------------------------\n\n if (subject.reasoning === \"streamed\") {\n define(\"reasoning reaches the caller as deltas, and never as something the model said\", async () => {\n const streamed = await answering({ kind: \"reasoning\", thoughts: [\"Check \", \"the ledger.\"], text: \"1250\" }, {\n messages: [user(\"what is the balance?\")], effort: \"high\",\n });\n const response = got(streamed.result, \"generate\");\n equals(joined(streamed.deltas, \"reasoning.delta\"), \"Check the ledger.\", \"the caller saw it being thought\");\n // Reasoning is not speech. Folded into the message it would be committed\n // as the assistant's words and replayed to the provider next turn, as if\n // the model had said aloud what it only thought.\n equals(textOf(response.message.content), \"1250\", \"and the message holds only what was said\");\n });\n } else {\n define(\"an implementation that carries no reasoning emits none\", async () => {\n const streamed = await answering({ kind: \"text\", deltas: [\"hello\"] }, { messages: [user(\"go\")] });\n holds(!streamed.deltas.some((delta) => delta.type === \"reasoning.delta\"),\n \"a subject declaring no reasoning must not emit a delta the suite would then not check\");\n });\n }\n\n if (subject.toolArguments === \"streamed\") {\n define(\"a tool call's arguments reach the caller as they are produced\", async () => {\n const call = asked[0]!;\n const streamed = await answering({ kind: \"tool-calls\", calls: [call] }, {\n messages: [user(\"look it up\")], tools,\n });\n const [reported] = got(streamed.result, \"generate\").message.calls ?? [];\n holds(reported !== undefined, \"the call came back\");\n\n const fragments = streamed.deltas\n .filter((delta): delta is Extract<ModelDelta, { type: \"tool-call.delta\" }> => delta.type === \"tool-call.delta\");\n holds(fragments.length > 0, \"the arguments were streamed at all\");\n equals([...new Set(fragments.map((delta) => delta.callId))], [reported.callId],\n \"every fragment names the call it belongs to — an application renders them per call\");\n equals(fragments.map((delta) => delta.arguments).join(\"\"), reported.arguments,\n \"and they reassemble into exactly what the call ended up holding\");\n });\n } else {\n define(\"an implementation that does not stream arguments emits no tool-call deltas\", async () => {\n const streamed = await answering({ kind: \"tool-calls\", calls: asked }, {\n messages: [user(\"look it up\")], tools,\n });\n holds(!streamed.deltas.some((delta) => delta.type === \"tool-call.delta\"),\n \"a subject declaring `whole` must not emit a delta the suite would then not check\");\n });\n }\n\n if (subject.wire === \"none\") {\n define(\"an implementation with no wire under it says so by not offering one\", async () => {\n const primed = await subject.answering({ kind: \"text\", deltas: [\"hello\"] });\n holds(primed.sent === undefined,\n \"a subject that can report what it sent declares a wire, rather than offering a report nothing reads\");\n });\n return cases;\n }\n\n const wire = subject.wire;\n\n // ---- What the provider refused ------------------------------------------\n\n const refusals = [\n { status: 401, body: \"{\\\"error\\\":{\\\"message\\\":\\\"invalid api key\\\"}}\", code: \"auth\", retryable: false },\n { status: 429, body: \"{\\\"error\\\":{\\\"message\\\":\\\"rate limit exceeded\\\"}}\", code: \"rate-limit\", retryable: true },\n {\n status: 400,\n body: \"{\\\"error\\\":{\\\"message\\\":\\\"prompt is too long: 300000 tokens > 200000 maximum context length\\\"}}\",\n code: \"context-length\", retryable: false,\n },\n { status: 500, body: \"upstream is unwell\", code: \"provider\", retryable: true },\n { status: 404, body: \"{\\\"error\\\":{\\\"message\\\":\\\"no such model\\\"}}\", code: \"failed\", retryable: false },\n ] as const;\n\n for (const refusal of refusals) {\n define(`a ${refusal.status} from the provider is a ${refusal.code} failure a caller can act on`, async () => {\n const streamed = await answering({ kind: \"status\", status: refusal.status, body: refusal.body }, {\n messages: [user(\"go\")],\n });\n const error = failed(streamed.result, refusal.code, `${refusal.status} is ${refusal.code}`);\n // The whole of what a backoff loop reads. Getting it wrong costs either a\n // run that gives up on a rate limit or one that hammers a bad credential.\n equals(error.retryable, refusal.retryable, `a ${refusal.code} failure says whether trying again is worth it`);\n holds(error.message.length > 0, \"and carries something a human can read\");\n equals(streamed.deltas, [], \"a refused request streamed nothing\");\n });\n }\n\n define(\"a body that dies part way through is a failure, not a thrown generator\", async () => {\n // Without this the generator throws, `run.result` rejects instead of\n // resolving, no `run.finished` is ever committed, and the activation stays\n // open until its claim expires.\n const streamed = await answering({ kind: \"cut\", text: \"The bal\" }, { messages: [user(\"go\")] });\n const error = failed(streamed.result, \"provider\", \"a dropped body is a provider failure\");\n equals(error.retryable, true, \"a connection that dropped is worth trying again\");\n holds(streamed.deltas.length > 0, \"and what did arrive had already reached the caller\");\n });\n\n define(\"a generation cancelled while it streams ends cancelled, not as a provider fault\", async () => {\n const primed = await subject.answering({ kind: \"text\", deltas: [\"one \", \"two \", \"three\"] });\n const controller = new AbortController();\n const generation = primed.model.generate({ messages: [user(\"count\")], signal: controller.signal });\n\n const first = await generation.next();\n holds(first.done === false, \"the first delta arrived\");\n // The moment that matters: a caller that has read something and then gives\n // up. A stream torn down under an adapter looks exactly like one the network\n // dropped, and only the signal tells them apart.\n controller.abort();\n\n let step = await generation.next();\n while (!step.done) step = await generation.next();\n equals(failed(step.value, \"cancelled\", \"a cancelled stream reports cancelled\").retryable, false,\n \"a caller that gave up is not told to try again\");\n });\n\n // ---- What went out ------------------------------------------------------\n\n /** One scripted generation, reported back as the request it put on the wire. */\n const sending = async (request: ModelRequest): Promise<SentRequest> => {\n const primed = await subject.answering({ kind: \"text\", deltas: [\"ok\"] });\n holds(primed.sent !== undefined, \"a subject declaring a wire owes a report of what it sent\");\n got((await drive(primed.model.generate(request))).result, \"generate\");\n return primed.sent();\n };\n\n define(\"everything the caller said reaches the provider, in the order it was said\", async () => {\n // Every role the port carries, in one turn. The wires disagree about where\n // each one goes — an instruction is hoisted out of the list on one and left\n // in it on the other, a tool result is a message here and a block inside the\n // previous user turn there — and none of that may lose a word or reorder one.\n const sent = await sending({ messages: conversation });\n equals(sent.text, [\"You are a bookkeeper.\", \"What is the balance?\", \"Looking that up.\", \"1250\", \"Thanks.\"],\n \"the instruction, the question, the reply, the result and the follow-up all arrive, in that order\");\n });\n\n define(\"a tool result reaches the provider paired with the call it answers\", async () => {\n // Unpaired, a provider rejects the turn outright: it has an answer and no\n // question. This is the one encoding mistake that makes a session unusable\n // rather than merely worse.\n const sent = await sending({ messages: conversation, tools });\n equals(sent.toolResults, [{ callId: \"call-1\", content: \"1250\" }], \"the result carries the id it answers\");\n equals(sent.toolCalls.map((call) => call.callId), [\"call-1\"], \"and the call it answers went back with it\");\n equals(parsed(sent.toolCalls[0]!.arguments, \"the call sent back\"), { account: \"main\" },\n \"still holding the arguments the model asked with\");\n });\n\n define(\"the tools the caller advertised reach the provider with their schemas\", async () => {\n const sent = await sending({ messages: [user(\"look it up\")], tools });\n equals([...sent.toolNames].sort(), tools.map((tool) => tool.name).sort(), \"every tool is advertised\");\n for (const tool of tools) {\n equals(sent.toolSchemas[tool.name], tool.parameters,\n `${tool.name} is advertised with the schema it declared — the model calls what it was shown`);\n }\n equals((await sending({ messages: [user(\"look it up\")] })).toolNames, [],\n \"and a request with no tools advertises none\");\n });\n\n define(\"an image reaches the provider as an image\", async () => {\n const sent = await sending({\n messages: [{\n role: \"user\",\n content: [\n { type: \"text\", text: \"what is this?\" },\n { type: \"image\", mediaType: \"image/png\", source: { kind: \"inline\", data: PIXEL } },\n ],\n }],\n });\n equals(sent.mediaTypes, [\"image/png\"], \"the picture is transmitted as a picture, not as prose about one\");\n holds(sent.text.includes(\"what is this?\"), \"and the text beside it is not lost carrying it\");\n });\n\n define(\"a block this wire cannot carry is dropped, not stringified into something the model reads\", async () => {\n // An opaque block belongs to the provider that made it and is valid on no\n // other wire. The tempting fix is to serialize what cannot be sent, which\n // puts another provider's internals — signatures and all — in front of the\n // model as if a person had typed them.\n const sent = await sending({\n messages: [{\n role: \"user\",\n content: [\n { type: \"text\", text: \"what is this?\" },\n { type: \"opaque\", provider: \"another-provider\", data: { signature: \"sig-9\" } },\n ],\n }],\n });\n equals(sent.text, [\"what is this?\"], \"the words go, the block this wire has no place for does not\");\n });\n\n define(\"the output ceiling the caller set reaches the provider\", async () => {\n equals((await sending({ messages: [user(\"go\")], maxOutputTokens: 64 })).maxOutputTokens, 64,\n \"a ceiling nothing transmits is a ceiling nothing enforces\");\n });\n\n define(`effort is ${wire.effort === \"sent\" ? \"sent as this provider's own depth control\" : \"dropped rather than sent\"}`,\n async () => {\n const withEffort = await sending({ messages: [user(\"think first\")], effort: \"high\" });\n absent((await sending({ messages: [user(\"think first\")] })).effort,\n \"a request that asked for no effort carries no depth control\");\n if (wire.effort === \"ignored\") {\n absent(withEffort.effort, \"a provider with no such control is given none, rather than a budget invented for it\");\n return;\n }\n holds(withEffort.effort !== undefined, \"the level the caller asked for reaches the provider\");\n });\n\n define(`temperature is ${wire.temperature === \"sent\" ? \"sent\" : \"dropped rather than sent to a provider that rejects it\"}`,\n async () => {\n const withTemperature = await sending({ messages: [user(\"go\")], temperature: 0.2 });\n absent((await sending({ messages: [user(\"go\")] })).temperature, \"a request that set none carries none\");\n if (wire.temperature === \"ignored\") {\n // Not an oversight: the current Anthropic models reject sampling\n // controls, so forwarding one turns every request into a 400.\n absent(withTemperature.temperature, \"a provider that rejects sampling controls is sent none\");\n return;\n }\n equals(withTemperature.temperature, 0.2, \"the value the caller set reaches the provider\");\n });\n\n define(`cacheAfter is ${wire.cache === \"sent\" ? \"marked on the wire\" : \"dropped on a wire with no breakpoint\"}`,\n async () => {\n const marked = await sending({ messages: conversation, cacheAfter: 1 });\n equals((await sending({ messages: conversation })).cacheMarks, 0, \"a request that asked for no breakpoint marks none\");\n if (wire.cache === \"ignored\") {\n equals(marked.cacheMarks, 0, \"a wire that caches by itself is not sent a mark it has no field for\");\n return;\n }\n holds(marked.cacheMarks > 0, \"the prefix the caller ended is marked, or nothing is cached and every turn pays\");\n });\n\n return cases;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { collect } from \"./model.js\";\nexport type {\n Message, Model, ModelDelta, ModelError, ModelRequest, ModelResponse, ToolSpec,\n} from \"./model.js\";\n"]}
|