Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

456 lines · 23.4 KBRaw History
1/**
2 * Token usage from model responses, read by the model proxy as they stream
3 * through it, so a run's token budget is charged by Diffinery and not trusted
4 * to the agent. Understands Anthropic Messages, OpenAI Responses and Chat
5 * Completions, streamed (SSE) or not.
6 */
7
8export interface Usage {
9 /** Input tokens read without a cache. */
10 input: number;
11 output: number;
12 cacheRead: number;
13 cacheWrite: number;
14}
15
16export const NO_USAGE: Usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
17
18/**
19 * Tokens as a budget counts them: cache reads cost about a tenth of other
20 * input with every provider, so they count as a tenth.
21 */
22export const weighted = (u: Usage): number => u.input + u.cacheWrite + u.output + Math.ceil(u.cacheRead / 10);
23
24const n = (v: unknown): number => (typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.floor(v) : 0);
25
26/** The usage one response object (or stream event) reports, if any. */
27export function usageOf(o: unknown): Usage | null {
28 if (!o || typeof o !== "object") return null;
29 const x = o as Record<string, unknown>;
30 const u = (x.usage ?? (x.message as Record<string, unknown> | undefined)?.usage ?? (x.response as Record<string, unknown> | undefined)?.usage) as Record<string, unknown> | undefined;
31 if (!u || typeof u !== "object") return null;
32 if ("prompt_tokens" in u || "completion_tokens" in u) {
33 // Chat Completions: prompt_tokens includes cached ones.
34 const cached = n((u.prompt_tokens_details as Record<string, unknown> | undefined)?.cached_tokens);
35 return { input: Math.max(n(u.prompt_tokens) - cached, 0), output: n(u.completion_tokens), cacheRead: cached, cacheWrite: 0 };
36 }
37 const details = u.input_tokens_details as Record<string, unknown> | undefined;
38 if (details && "cached_tokens" in details) {
39 // OpenAI Responses: input_tokens includes cached ones.
40 const cached = n(details.cached_tokens);
41 return { input: Math.max(n(u.input_tokens) - cached, 0), output: n(u.output_tokens), cacheRead: cached, cacheWrite: 0 };
42 }
43 // Anthropic: input_tokens excludes cache reads and writes.
44 return { input: n(u.input_tokens), output: n(u.output_tokens), cacheRead: n(u.cache_read_input_tokens), cacheWrite: n(u.cache_creation_input_tokens) };
45}
46
47/** Events of one response report running totals (Anthropic's message_start, then message_delta): keep the largest of each. */
48const fold = (a: Usage | null, b: Usage | null): Usage | null => (!a ? b : b ? { input: Math.max(a.input, b.input), output: Math.max(a.output, b.output), cacheRead: Math.max(a.cacheRead, b.cacheRead), cacheWrite: Math.max(a.cacheWrite, b.cacheWrite) } : a);
49
50const parse = (s: string): unknown => {
51 try {
52 return JSON.parse(s);
53 } catch {
54 return null;
55 }
56};
57
58/** The most of a non-streamed body that is read for its usage. */
59const MAX_JSON = 16 * 1024 * 1024;
60
61/** An SSE line longer than this can't be a usage event: it is dropped rather than buffered. */
62const MAX_SSE_LINE = 1024 * 1024;
63
64/** Read a response body (a tee of what goes back to the agent) for the usage it reports; null when it reports none. */
65export async function readUsage(body: ReadableStream<Uint8Array>, contentType: string): Promise<Usage | null> {
66 return (await meterUsage(body, contentType)).usage;
67}
68
69/**
70 * The usage a response reports, and whether part of it couldn't be read: an SSE line too long to keep, or a
71 * JSON body too large to parse. Usage may have been in that part (a streamed Responses answer reports it only
72 * in `response.completed`, which carries the whole answer), so runs held to their bookings charge the most
73 * the request could have cost instead (`unreadCharge`).
74 */
75export async function meterUsage(body: ReadableStream<Uint8Array>, contentType: string): Promise<{ usage: Usage | null; unreadable: boolean }> {
76 const sse = contentType.includes("text/event-stream");
77 const reader = body.pipeThrough(new TextDecoderStream()).getReader();
78 let usage: Usage | null = null;
79 let buf = "";
80 let size = 0;
81 let dropping = false;
82 let unreadable = false;
83 try {
84 for (;;) {
85 const { done, value } = await reader.read();
86 if (done) break;
87 if (!sse) {
88 size += value.length;
89 if (size <= MAX_JSON) buf += value;
90 continue;
91 }
92 buf += value;
93 let nl: number;
94 while ((nl = buf.indexOf("\n")) >= 0) {
95 const line = buf.slice(0, nl).trim();
96 buf = buf.slice(nl + 1);
97 // The rest of a line already dropped.
98 if (dropping) {
99 dropping = false;
100 continue;
101 }
102 if (line.startsWith("data:")) usage = fold(usage, usageOf(parse(line.slice(5).trim())));
103 }
104 if (buf.length > MAX_SSE_LINE) {
105 buf = "";
106 dropping = true;
107 unreadable = true;
108 }
109 }
110 } finally {
111 reader.releaseLock();
112 }
113 if (sse) return { usage: !dropping && buf.trim().startsWith("data:") ? fold(usage, usageOf(parse(buf.trim().slice(5).trim()))) : usage, unreadable };
114 if (size > MAX_JSON) return { usage, unreadable: true };
115 return { usage: fold(usage, usageOf(parse(buf))), unreadable };
116}
117
118/**
119 * What a request whose answer's usage couldn't be read, or that reported none, is charged on a run held to its
120 * bookings: a token per byte it sent (three times its estimate's input; it can pull in nothing else, see
121 * prepareRequest), and the output cap it was sent with. Images and PDFs bill by pixels and pages instead, which
122 * the limit on requests at once (inflight.ts) bounds.
123 */
124export const unreadCharge = (estimate: Usage, size: number): Usage => ({ ...estimate, input: Math.max(estimate.input, size) });
125
126export const formatTokens = (t: number): string => (t >= 1_000_000 ? `${+(t / 1_000_000).toFixed(1)}M` : t >= 1000 ? `${Math.round(t / 1000)}k` : String(t));
127
128/**
129 * What a CLI reports about its own usage in one line of its output (phase 5a), for runs whose model
130 * requests don't pass Diffinery: a terminal's signed-in subscription. Claude Code's `result` event
131 * gives its run's totals and cost (`set`); each Codex `turn.completed` gives one turn's tokens (`add`).
132 */
133export function reportedUsage(runtime: string, line: string): { usage: Usage; costUsd: number | null; mode: "set" | "add" } | null {
134 const claude = runtime === "claude-code" && line.startsWith('{"type":"result"');
135 const codex = runtime === "codex" && line.startsWith('{"type":"turn.completed"');
136 if (!claude && !codex) return null;
137 const j = parse(line) as Record<string, unknown> | null;
138 if (!j || typeof j !== "object") return null;
139 const u = j.usage as Record<string, unknown> | undefined;
140 if (!u || typeof u !== "object") return null;
141 if (codex) {
142 // Codex: input_tokens includes the cached ones.
143 const cached = n(u.cached_input_tokens);
144 return { usage: { input: Math.max(n(u.input_tokens) - cached, 0), output: n(u.output_tokens), cacheRead: cached, cacheWrite: 0 }, costUsd: null, mode: "add" };
145 }
146 const cost = typeof j.total_cost_usd === "number" && Number.isFinite(j.total_cost_usd) && j.total_cost_usd >= 0 ? j.total_cost_usd : null;
147 return { usage: { input: n(u.input_tokens), output: n(u.output_tokens), cacheRead: n(u.cache_read_input_tokens), cacheWrite: n(u.cache_creation_input_tokens) }, costUsd: cost, mode: "set" };
148}
149
150/**
151 * A run's usage as people see it (phase 5a). Requests through the model proxy are counted by Diffinery
152 * (`proxy`); a run with none, in a terminal on a subscription, has only what its CLI reported (`runtime`).
153 * The cost is always the CLI's own figure (Claude Code's), never computed here.
154 */
155export interface RunUsage {
156 source: "proxy" | "runtime";
157 requests: number;
158 input: number;
159 output: number;
160 cacheRead: number;
161 cacheWrite: number;
162 costUsd: number | null;
163}
164
165export function runUsage(r: {
166 model_calls: number;
167 input_tokens: number;
168 output_tokens: number;
169 cache_read_tokens: number;
170 cache_write_tokens: number;
171 reported_input: number;
172 reported_output: number;
173 reported_cache_read: number;
174 reported_cache_write: number;
175 cost_usd: number | null;
176}): RunUsage | null {
177 const costUsd = r.cost_usd ?? null;
178 if (r.model_calls > 0) return { source: "proxy", requests: r.model_calls, input: r.input_tokens, output: r.output_tokens, cacheRead: r.cache_read_tokens, cacheWrite: r.cache_write_tokens, costUsd };
179 if (r.reported_input || r.reported_output || r.reported_cache_read || r.reported_cache_write || costUsd !== null)
180 return { source: "runtime", requests: 0, input: r.reported_input, output: r.reported_output, cacheRead: r.reported_cache_read, cacheWrite: r.reported_cache_write, costUsd };
181 return null;
182}
183
184/** Several runs' usage added up; the cost counts the runs that reported one, and is null when none did. */
185export function totalUsage(list: Array<RunUsage | null>): Omit<RunUsage, "source"> | null {
186 const xs = list.filter((u): u is RunUsage => !!u);
187 if (!xs.length) return null;
188 const costs = xs.map((u) => u.costUsd).filter((c): c is number => c !== null);
189 return {
190 requests: xs.reduce((a, u) => a + u.requests, 0),
191 input: xs.reduce((a, u) => a + u.input, 0),
192 output: xs.reduce((a, u) => a + u.output, 0),
193 cacheRead: xs.reduce((a, u) => a + u.cacheRead, 0),
194 cacheWrite: xs.reduce((a, u) => a + u.cacheWrite, 0),
195 costUsd: costs.length ? costs.reduce((a, c) => a + c, 0) : null,
196 };
197}
198
199/** Usage as the REST API and MCP give it, with GitHub-style snake_case names. */
200export const usageJson = (u: Omit<RunUsage, "source"> & { source?: RunUsage["source"] }) => ({
201 ...(u.source ? { source: u.source } : {}),
202 requests: u.requests,
203 input_tokens: u.input,
204 output_tokens: u.output,
205 cache_read_tokens: u.cacheRead,
206 cache_write_tokens: u.cacheWrite,
207 cost_usd: u.costUsd === null ? null : Math.round(u.costUsd * 10_000) / 10_000,
208});
209
210/** A cost in dollars as a person reads it: cents, or less than a cent. */
211export const formatCost = (usd: number): string => (usd > 0 && usd < 0.01 ? "<$0.01" : `$${usd.toFixed(2)}`);
212
213/** The output an estimate assumes when a request doesn't cap it. */
214export const DEFAULT_MAX_OUTPUT = 32_000;
215/** What a compaction may write back, which its request can't cap: the most any current OpenAI model emits. */
216export const COMPACT_MAX_OUTPUT = 128_000;
217/**
218 * The most output one request books, whatever cap it asks for: more than any model emits, and small enough
219 * that bookings and refunds add up exactly (a cap of 1e300 would swallow a month's count when refunded).
220 */
221export const MAX_OUTPUT_ESTIMATE = 1_000_000;
222
223/** Runs whose requests must stay within what was booked for them. */
224const LIMITED = "agent reviews, runs with a token budget and runs on an organization's key";
225
226/** Endpoints that generate tokens, so each request books an estimate, with the fields that cap their output. */
227const GENERATES: Record<string, string[]> = {
228 "/v1/messages": ["max_tokens"],
229 "/v1/responses": ["max_output_tokens"],
230 "/v1/responses/compact": [],
231 "/v1/chat/completions": ["max_completion_tokens", "max_tokens"],
232 // OpenRouter's legacy text completions.
233 "/v1/completions": ["max_tokens"],
234};
235
236export interface PreparedRequest {
237 /** The body to forward (changed only to ask for usage). */
238 body: string;
239 /** What the request may cost at most, booked before it goes upstream; null for requests that generate nothing. */
240 estimate: Usage | null;
241 /** Why a review may not send this request, if it may not. */
242 reviewRefuse?: string;
243 /** The body a run held to its bookings sends instead: every output cap set to the booked one and no paid provider-side tools, so the estimate holds. */
244 reviewBody?: string;
245 /**
246 * The body any other run sends when the request asks for provider-run tools (MCP servers, web fetch or search):
247 * the provider would reach hosts on the run's behalf, past the jail's allowlist, so they are dropped.
248 */
249 jailBody?: string;
250 /** The model the request asks for (null when it names none), for runs pinned to one. */
251 model?: string | null;
252 /** A routing field the request sets, which a pinned run may not. */
253 routing?: string;
254}
255
256/**
257 * Request fields that choose other models, providers or paid extras (OpenRouter's fallbacks and routing,
258 * plugins, web search, and more answers to pick from): a review or a run pinned to a model may not set them,
259 * or its code could switch to a far pricier model than the one its budget was set for.
260 */
261const ROUTING = ["models", "route", "provider", "plugins", "web_search_options", "speed", "best_of", "preset"] as const;
262
263/**
264 * Why a run may not send a prepared request: a review never sets routing fields, and a run pinned to a model
265 * (one that goes through OpenRouter with a model chosen in Diffinery) asks only for that model.
266 */
267export function pinRefusal(p: Pick<PreparedRequest, "estimate" | "model" | "routing">, run: { review: boolean; model: string | null; orgKey?: boolean }): string | undefined {
268 const who = run.review || run.orgKey ? LIMITED : "this task";
269 if ((run.review || run.orgKey || run.model) && p.routing) return `${p.routing} isn't available to ${who}: Diffinery pins the model and its routing`;
270 if (run.model && p.estimate && p.model !== run.model) return `this ${run.review ? "review" : "task"} runs on ${run.model}${p.model ? `, not ${p.model}` : ""}`;
271 return undefined;
272}
273
274/**
275 * The tools a review's requests may carry: tools the agent runs itself. Function and custom tools (and Anthropic's,
276 * which have no type), and the client-run built-ins, versioned or not (bash_20250124, text_editor_20250728, OpenAI's
277 * local_shell and apply_patch). Every other tool type runs on the provider and may cost extra beyond tokens (web
278 * search, web fetch, code execution, file search, MCP, and whatever providers add next), so it is dropped, with
279 * `mcp_servers`: a review's token budget then bounds what it spends.
280 */
281const CLIENT_TOOL = /^(function|custom|(bash|text_editor|computer|memory|local_shell|shell|apply_patch)(_\d+)?)$/;
282
283const typeOf = (t: unknown): unknown => (t && typeof t === "object" ? (t as { type?: unknown }).type : undefined);
284const clientTool = (t: unknown): boolean => {
285 const type = typeOf(t);
286 // A tool with an environment other than the agent's own (OpenAI's hosted shell, in a provider's container) runs there.
287 const env = t && typeof t === "object" ? (t as { environment?: unknown }).environment : undefined;
288 if (env !== undefined && typeOf(env) !== "local") return false;
289 return !!t && typeof t === "object" && (type === undefined || (typeof type === "string" && CLIENT_TOOL.test(type)));
290};
291/** The name a tool or a tool choice gives: Anthropic's and Responses' `name`, or Chat Completions' `function.name`. */
292const nameOf = (t: unknown): unknown => (t && typeof t === "object" ? ((t as { name?: unknown }).name ?? ((t as { function?: { name?: unknown } }).function?.name)) : undefined);
293
294/** A request's fields with only client-run tools; the same object when it has nothing else. */
295function withoutPaidTools(x: Record<string, unknown>): Record<string, unknown> {
296 const tools = Array.isArray(x.tools) ? (x.tools as unknown[]) : null;
297 const kept = tools?.filter(clientTool) ?? [];
298 const dropTools = !!tools && kept.length !== tools.length;
299 if (!dropTools && x.mcp_servers === undefined) return x;
300 const out: Record<string, unknown> = { ...x };
301 delete out.mcp_servers;
302 if (dropTools) {
303 if (kept.length) out.tools = kept;
304 else delete out.tools;
305 // A tool choice that names a tool, or a tool type, that isn't kept would be refused upstream, as would any choice once no tools are left.
306 const choice = out.tool_choice;
307 if (!kept.length) delete out.tool_choice;
308 else if (choice && typeof choice === "object") {
309 const type = typeOf(choice);
310 const name = nameOf(choice);
311 const general = type === "auto" || type === "any" || type === "none";
312 const keptName = name !== undefined && kept.some((t) => nameOf(t) === name);
313 const keptType = typeof type === "string" && type !== "function" && type !== "custom" && type !== "tool" && kept.some((t) => typeOf(t) === type);
314 if (!general && !keptName && !keptType) delete out.tool_choice;
315 }
316 }
317 return out;
318}
319
320/**
321 * Fields that pull in context stored at the provider, which the request's size doesn't show: the Responses API's
322 * stored responses, conversations and prompts, and Anthropic's containers (with their skills and files).
323 */
324const storedContext = (path: string): readonly string[] => (path.startsWith("/v1/responses") ? ["previous_response_id", "conversation", "prompt"] : path === "/v1/messages" ? ["container"] : []);
325
326/** The fields of a request that carry its input (and none of its tools or settings). */
327const INPUT_FIELDS = ["messages", "input", "system", "instructions"] as const;
328
329/** Keys whose value, a string or `{ url }`, is an image, file, audio or video given by URL unless it is a `data:` URL. */
330const MEDIA_URL = ["image_url", "file_url", "video_url", "audio_url"] as const;
331
332/**
333 * Input the provider fetches or looks up itself, so the request's size doesn't show what it costs: Responses
334 * `item_reference` items (stored context, like previous_response_id), anything given by file id, and images,
335 * documents and files given by URL (Anthropic's `url` and `file` sources, Responses' `input_file` and `input_image`,
336 * Chat Completions' `image_url` and `file` parts, OpenRouter's `file_data` URLs). Only what was sent counts:
337 * a tool call's own arguments (Claude Code's WebFetch takes a URL) are never read as input; every other field
338 * of the call is.
339 */
340export function remoteInput(x: Record<string, unknown>): string | undefined {
341 const stack: unknown[] = INPUT_FIELDS.map((k) => x[k]);
342 const isUrl = (v: unknown) => typeof v === "string" && !v.startsWith("data:");
343 while (stack.length) {
344 const v = stack.pop();
345 if (!v || typeof v !== "object") continue;
346 if (Array.isArray(v)) {
347 for (const e of v) stack.push(e);
348 continue;
349 }
350 const o = v as Record<string, unknown>;
351 const type = o.type;
352 if (type === "item_reference") return "item_reference inputs";
353 if (typeof o.file_id === "string") return "inputs given by file id";
354 const src = o.source as Record<string, unknown> | undefined;
355 if (src && typeof src === "object" && (src.type === "url" || src.type === "file")) return `inputs given by ${src.type === "url" ? "URL" : "file id"}`;
356 for (const k of MEDIA_URL) {
357 const m = o[k];
358 if (isUrl(m) || (m && typeof m === "object" && isUrl((m as { url?: unknown }).url))) return "inputs given by URL";
359 }
360 if (typeof o.file_data === "string" && /^[a-z][a-z0-9+.-]*:\/\//i.test(o.file_data)) return "inputs given by URL";
361 // What a model asked a tool to do (its input or arguments) isn't input.
362 const call = type === "tool_use" || type === "server_tool_use" || type === "function_call" || type === "custom_tool_call";
363 for (const [k, e] of Object.entries(o)) if (e && typeof e === "object" && !(call && (k === "input" || k === "arguments"))) stack.push(e);
364 }
365 return undefined;
366}
367
368/**
369 * Look at a model request before it goes upstream, so that every answer can
370 * be charged: its estimate (a token per 3 bytes of request, plus the output
371 * it allows) is booked up front and settled to the usage the answer reports,
372 * and streamed Chat Completions are asked to include usage. A run held to
373 * its bookings (LIMITED) may not start a background response, which it could
374 * leave unfetched and so uncharged, pull in stored context or remote inputs
375 * its estimate can't see, send a body that isn't a JSON object, or ask for
376 * more than one answer; its request that doesn't cap its output is capped at
377 * the estimate's DEFAULT_MAX_OUTPUT, and paid provider-side tools are
378 * stripped from it. A compaction can't be capped, so it books
379 * COMPACT_MAX_OUTPUT.
380 */
381export function prepareRequest(method: string, path: string, body: string, size: number): PreparedRequest {
382 const caps = method === "POST" && Object.hasOwn(GENERATES, path) ? GENERATES[path]! : null;
383 if (!caps) return { body, estimate: null };
384 const json = parse(body);
385 const x = json && typeof json === "object" && !Array.isArray(json) ? (json as Record<string, unknown>) : null;
386 // A request can set several caps (Chat Completions takes two): book the largest.
387 const capped = x ? caps.map((k) => n(x[k])).filter((v) => v > 0) : [];
388 const cap = capped.length ? Math.min(Math.max(...capped), MAX_OUTPUT_ESTIMATE) : undefined;
389 const estimate: Usage = { input: Math.ceil(size / 3), output: cap ?? (caps.length ? DEFAULT_MAX_OUTPUT : COMPACT_MAX_OUTPUT), cacheRead: 0, cacheWrite: 0 };
390 // A body that isn't a JSON object can't be capped or checked, and a lenient provider might still accept it.
391 if (!x) return { body, estimate, reviewRefuse: `a request whose body isn't a JSON object isn't available to ${LIMITED}` };
392 const stored = storedContext(path).find((k) => x[k] != null);
393 const remote = remoteInput(x);
394 const reviewRefuse =
395 x.background != null && x.background !== false
396 ? `background responses aren't available to ${LIMITED}`
397 : stored
398 ? `${stored} isn't available to ${LIMITED}: send the whole input instead`
399 : remote
400 ? `${remote} aren't available to ${LIMITED}: send the content itself instead`
401 : x.n != null && x.n !== 1
402 ? `more than one answer per request (n) isn't available to ${LIMITED}`
403 : typeof x.model === "string" && /:online\b/i.test(x.model)
404 ? `web search models (:online) aren't available to ${LIMITED}`
405 : typeof x.model === "string" && /@preset\//i.test(x.model)
406 ? `OpenRouter presets aren't available to ${LIMITED}`
407 : undefined;
408 let fields = x;
409 if (path === "/v1/chat/completions" && x.stream === true) {
410 const so = x.stream_options && typeof x.stream_options === "object" ? (x.stream_options as Record<string, unknown>) : {};
411 if (so.include_usage !== true) fields = { ...x, stream_options: { ...so, include_usage: true } };
412 }
413 const out: PreparedRequest = { body: fields === x ? body : JSON.stringify(fields), estimate, model: typeof x.model === "string" ? x.model : null };
414 if (reviewRefuse) out.reviewRefuse = reviewRefuse;
415 const routing = ROUTING.find((k) => x[k] != null);
416 if (routing) out.routing = routing;
417 // A run held to its bookings sends every cap as the one booked (the default when it sets none), and no paid
418 // provider-side tools: each of their steps reads more input than the request shows.
419 const set = caps.filter((k) => x[k] !== undefined);
420 const recap = caps[0] && (!set.length || set.some((k) => x[k] !== estimate.output));
421 const unpaid = withoutPaidTools(fields);
422 if (unpaid !== fields) out.jailBody = JSON.stringify(unpaid);
423 const capTo = (f: Record<string, unknown>) => ({ ...f, ...Object.fromEntries((set.length ? set : [caps[0]!]).map((k) => [k, estimate.output])) });
424 // Always written out again, so a key the request repeats reaches the provider once, as read here.
425 out.reviewBody = JSON.stringify(recap ? capTo(unpaid) : unpaid);
426 return out;
427}
428
429/** Read a request body up to `max` bytes, counting as it reads; null when it is larger. */
430export async function readBody(body: ReadableStream<Uint8Array> | null, max: number): Promise<Uint8Array | null> {
431 if (!body) return new Uint8Array();
432 const chunks: Uint8Array[] = [];
433 let size = 0;
434 const reader = body.getReader();
435 try {
436 for (;;) {
437 const { done, value } = await reader.read();
438 if (done) break;
439 size += value.byteLength;
440 if (size > max) {
441 await reader.cancel().catch(() => undefined);
442 return null;
443 }
444 chunks.push(value);
445 }
446 } finally {
447 reader.releaseLock();
448 }
449 const out = new Uint8Array(size);
450 let at = 0;
451 for (const c of chunks) {
452 out.set(c, at);
453 at += c.byteLength;
454 }
455 return out;
456}