pr_01m47d15m3e54sn21z27rpy5n9/services/models/src/index.ts

64 lines2,829 bytesCodeBlame
1/**
2 * The model proxy: every model request a g1t sandbox makes comes through
3 * here, at `https://models.g1t.sh/anthropic`.
4 *
5 * A sandbox holds a token for its one run, never a key. The proxy looks the
6 * token up and forwards the request with the credentials for that run:
7 * g1t's AI Gateway when g1t pays, or the workspace's own Anthropic key or
8 * endpoint when the workspace does. So a sandbox that is tricked into
9 * printing its environment gives away a token that stops working when the
10 * run ends, and nothing of the workspace's.
11 *
12 * Responses stream through untouched.
13 */
14import { type ModelUpstream, type ServiceBinding, integrationsClient } from "@g1t/contracts";
15
16import { type HostedRouting, presentedToken, upstreamRequest } from "./route";
17
18interface Env extends HostedRouting {
19 INTEGRATIONS: ServiceBinding;
20}
21
22/** How long a looked-up token is trusted before it is looked up again. */
23const REMEMBER_MS = 60_000;
24const remembered = new Map<string, { upstream: ModelUpstream | null; until: number }>();
25
26async function lookUp(env: Env, token: string): Promise<ModelUpstream | null> {
27 const now = Date.now();
28 const hit = remembered.get(token);
29 if (hit && hit.until > now) return hit.upstream;
30 const upstream = await integrationsClient(env.INTEGRATIONS).modelUpstream(token);
31 if (remembered.size > 5_000) remembered.clear();
32 remembered.set(token, { upstream, until: now + REMEMBER_MS });
33 return upstream;
34}
35
36/** An error in the shape Anthropic's API uses, which the harness understands. */
37function refuse(status: number, message: string): Response {
38 return Response.json(
39 { type: "error", error: { type: status === 401 ? "authentication_error" : "not_found_error", message } },
40 { status },
41 );
42}
43
44export default {
45 async fetch(request: Request, env: Env): Promise<Response> {
46 const url = new URL(request.url);
47 if (url.pathname === "/" || url.pathname === "") {
48 return new Response("g1t's model proxy, for g1t's sandboxes. See https://docs.g1t.sh/guides/models/\n");
49 }
50 if (!url.pathname.startsWith("/anthropic/")) return refuse(404, "Requests go to /anthropic/v1/….");
51 const token = presentedToken(request.headers);
52 if (!token?.startsWith("g1tm_")) return refuse(401, "This needs a g1t run's model token.");
53 const upstream = await lookUp(env, token);
54 if (!upstream) return refuse(401, "This run's model token has expired, or its model connection was removed.");
55
56 const path = url.pathname.slice("/anthropic".length) + url.search;
57 const { url: target, headers } = upstreamRequest(upstream, env, path, request.headers);
58 return fetch(target, {
59 method: request.method,
60 headers,
61 body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
62 });
63 },
64} satisfies ExportedHandler<Env>;