Skip to content

g1t/services/models/src/gateway.test.ts

181 lines7,853 bytesCodeBlame
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { GatewayModel, User } from "@g1t/contracts";
5
6import {
7 anthropicError,
8 callerOf,
9 errorMessage,
10 gatewayRecord,
11 gatewayRoute,
12 hostedRequest,
13 requestId,
14 sessionOf,
15 unoffered,
16 unpriced,
17} from "./gateway.ts";
18
19const workspaceToken = (scopes: string[] | null, name = "ci"): User => ({
20 id: "wsp_1",
21 username: "Acme",
22 kind: "workspace",
23 workspaces: [{ slug: "acme", role: "member" }],
24 token: { token_id: "tok_1", scopes, name },
25});
26
27const sonnet: GatewayModel = {
28 model: "claude-sonnet-5-5",
29 name: "Claude Sonnet 5.5",
30 provider: "anthropic",
31 inputMicros: 2_000_000,
32 outputMicros: 10_000_000,
33 cacheReadMicros: 200_000,
34 cacheWriteMicros: 2_500_000,
35};
36
37test("a workspace's token with models:write is let through, as that workspace", () => {
38 const who = callerOf(workspaceToken(["repo:read", "models:write"]));
39 assert.ok("caller" in who);
40 assert.deepEqual(who.caller, { workspace: "acme", tokenId: "tok_1", tokenName: "ci" });
41 // Full access holds every scope.
42 assert.ok("caller" in callerOf(workspaceToken(null)));
43});
44
45test("without models:write, or not a workspace's token, it is refused as Anthropic would", () => {
46 const reader = callerOf(workspaceToken(["models:read", "billing:write"]));
47 assert.ok(!("caller" in reader));
48 assert.equal(reader.status, 403);
49 assert.equal(reader.type, "permission_error");
50 assert.match(reader.message, /models:write/);
51
52 const person: User = { id: "usr_1", username: "ada", token: { token_id: "tok_2", scopes: null } };
53 const personal = callerOf(person);
54 assert.ok(!("caller" in personal));
55 assert.equal(personal.status, 403);
56 assert.match(personal.message, /workspace's access token/);
57
58 const unknown = callerOf(null);
59 assert.ok(!("caller" in unknown));
60 assert.equal(unknown.status, 401);
61 assert.equal(unknown.type, "authentication_error");
62
63 // A workspace acting through a signed-in session, not a token, is not a caller either.
64 const session: User = { id: "wsp_1", username: "acme", kind: "workspace" };
65 assert.ok(!("caller" in callerOf(session)));
66});
67
68test("errors are in Anthropic's shape", async () => {
69 const response = anthropicError(402, "billing_error", "Out of AI credit.");
70 assert.equal(response.status, 402);
71 assert.deepEqual(await response.json(), { type: "error", error: { type: "billing_error", message: "Out of AI credit." } });
72});
73
74test("the gateway answers messages and counting tokens only", () => {
75 assert.equal(gatewayRoute("/v1/messages"), "messages");
76 assert.equal(gatewayRoute("/v1/messages?beta=true"), "messages");
77 assert.equal(gatewayRoute("/v1/messages/count_tokens"), "count_tokens");
78 assert.equal(gatewayRoute("/v1/messages/batches"), null);
79 assert.equal(gatewayRoute("/v1/models"), null);
80});
81
82test("only the models g1t prices are offered on its key", () => {
83 assert.equal(unoffered("claude-sonnet-5-5", [sonnet]), null);
84 const why = unoffered("gpt-5", [sonnet, sonnet]);
85 assert.match(why ?? "", /gpt-5 is not offered/);
86 assert.match(why ?? "", /It offers claude-sonnet-5-5\. /);
87 assert.match(unoffered(undefined, [sonnet]) ?? "", /model/);
88});
89
90test("requests to g1t's models go through its gateway, tagged, without the caller's token", () => {
91 const hosted = { AI_GATEWAY_ID: "g1t", CLOUDFLARE_ACCOUNT_ID: "acct", AI_GATEWAY_TOKEN: "gw-token" };
92 const incoming = new Headers({
93 "x-api-key": "g1t_secret",
94 authorization: "Bearer g1t_secret",
95 "anthropic-version": "2023-06-01",
96 "cf-aig-metadata": "{\"workspace\":\"someone-else\"}",
97 });
98 const caller = { workspace: "acme", tokenId: "tok_1", tokenName: "ci" };
99 const { url, headers } = hostedRequest(hosted, "/v1/messages", incoming, caller, "gw_tok_1_2026100712");
100 assert.equal(url, "https://gateway.ai.cloudflare.com/v1/acct/g1t/anthropic/v1/messages");
101 assert.equal(headers.get("x-api-key"), null);
102 assert.equal(headers.get("authorization"), null);
103 assert.equal(headers.get("cf-aig-authorization"), "Bearer gw-token");
104 assert.equal(headers.get("anthropic-version"), "2023-06-01");
105 assert.deepEqual(JSON.parse(headers.get("cf-aig-metadata") ?? "{}"), {
106 task: "gateway",
107 workspace: "acme",
108 token: "tok_1",
109 session: "gw_tok_1_2026100712",
110 });
111 // With no gateway, straight to Anthropic with g1t's key.
112 const direct = hostedRequest({ AI_GATEWAY_ID: "", CLOUDFLARE_ACCOUNT_ID: "", ANTHROPIC_API_KEY: "sk-g1t" }, "/v1/messages", incoming, caller, "s");
113 assert.equal(direct.url, "https://api.anthropic.com/v1/messages");
114 assert.equal(direct.headers.get("x-api-key"), "sk-g1t");
115});
116
117test("on g1t's models, only what is charged by its tokens is let through", () => {
118 const plain = { model: "claude-sonnet-5-5", max_tokens: 10, messages: [] };
119 assert.equal(unpriced(plain), null);
120 assert.equal(unpriced({ ...plain, speed: "standard", inference_geo: "global" }), null);
121 // The caller's own tools, and the client tools Anthropic defines, cost only their tokens.
122 const clientTools = [{ name: "lookup", input_schema: {} }, { type: "custom", name: "x" }, { type: "bash_20250124", name: "bash" }, { type: "text_editor_20250728", name: "e" }, { type: "computer_toolset_20260801", name: "c" }, { type: "memory_20250818", name: "memory" }];
123 assert.equal(unpriced({ ...plain, tools: clientTools }), null);
124 // Billed otherwise by the provider: refused, pointing at the workspace's own key.
125 assert.match(unpriced({ ...plain, speed: "fast" }) ?? "", /Fast mode/);
126 assert.match(unpriced({ ...plain, inference_geo: "us" }) ?? "", /inference_geo/);
127 assert.match(unpriced({ ...plain, fallbacks: "default" }) ?? "", /fallbacks/);
128 assert.match(unpriced({ ...plain, container: { skills: [] } }) ?? "", /Containers/);
129 const search = unpriced({ ...plain, tools: [{ type: "web_search_20260209", name: "web_search" }] }) ?? "";
130 assert.match(search, /web_search_20260209/);
131 assert.match(search, /own Anthropic key/);
132});
133
134test("a caller cannot set the gateway's own headers", () => {
135 const caller = { workspace: "acme", tokenId: "tok_1", tokenName: null };
136 const incoming = new Headers({ "cf-aig-custom-cost": "{\"total_cost\":0}", "cf-aig-cache-ttl": "3600", "cf-aig-skip-cache": "true" });
137 const { headers } = hostedRequest({ AI_GATEWAY_ID: "g1t", CLOUDFLARE_ACCOUNT_ID: "acct" }, "/v1/messages", incoming, caller, "s");
138 assert.equal(headers.get("cf-aig-custom-cost"), null);
139 assert.equal(headers.get("cf-aig-cache-ttl"), null);
140 assert.equal(headers.get("cf-aig-skip-cache"), null);
141});
142
143test("a request has its own id and is logged under its token's hour", () => {
144 assert.match(requestId(), /^gw_[0-9a-f]{24}$/);
145 assert.notEqual(requestId(), requestId());
146 assert.equal(sessionOf("tok_1", new Date("2026-10-07T12:34:56Z")), "gw_tok_1_2026100712");
147});
148
149test("what billing is told: tokens by kind, whose key, and how it went", () => {
150 const record = gatewayRecord({
151 id: "gw_1",
152 caller: { workspace: "acme", tokenId: "tok_1", tokenName: "ci" },
153 model: "claude-sonnet-5-5",
154 tokens: { input: 10, output: 5, cacheRead: 100, cacheWrite: 0 },
155 status: 200,
156 ownKey: true,
157 streamed: true,
158 durationMs: 812.4,
159 });
160 assert.deepEqual(record, {
161 id: "gw_1",
162 workspace: "acme",
163 tokenId: "tok_1",
164 tokenName: "ci",
165 model: "claude-sonnet-5-5",
166 input: 10,
167 output: 5,
168 cacheRead: 100,
169 cacheWrite: 0,
170 status: 200,
171 ownKey: true,
172 streamed: true,
173 durationMs: 812,
174 error: null,
175 });
176});
177
178test("a provider's error is logged by its message", () => {
179 assert.equal(errorMessage(429, JSON.stringify({ type: "error", error: { type: "rate_limit_error", message: "Slow down." } })), "Slow down.");
180 assert.equal(errorMessage(502, "<html>bad gateway</html>"), "The model provider answered 502.");
181});