@tokensize/agent-client 0.3.0-beta.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/dist/index.js ADDED
@@ -0,0 +1,1131 @@
1
+ // src/index.ts
2
+ import { createHash, createHmac, randomBytes, randomUUID as randomUUID2, timingSafeEqual } from "crypto";
3
+ import { access as access3, mkdir as mkdir4, readFile as readFile4, rename as rename4, writeFile as writeFile4 } from "fs/promises";
4
+ import { createServer } from "http";
5
+ import path4 from "path";
6
+ import { execFile } from "child_process";
7
+ import {
8
+ AGENT_ROUTER_SCHEMA_VERSION,
9
+ agentRouteResponseSchema,
10
+ featurizeAgentTask,
11
+ runEventSchema
12
+ } from "@tokensize/agent-router";
13
+
14
+ // src/local/discovery.ts
15
+ import { access, mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
16
+ import os2 from "os";
17
+ import path2 from "path";
18
+
19
+ // src/local/config.ts
20
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
21
+ import os from "os";
22
+ import path from "path";
23
+ import { randomUUID } from "crypto";
24
+ function home() {
25
+ return process.env.TOKENSIZE_HOME ?? path.join(os.homedir(), ".tokensize");
26
+ }
27
+ async function saveApiKey(apiKey) {
28
+ const directory = home();
29
+ const file = path.join(directory, "credentials.json");
30
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
31
+ await mkdir(directory, { recursive: true, mode: 448 });
32
+ await writeFile(temporary, `${JSON.stringify({ apiKey }, null, 2)}
33
+ `, { mode: 384 });
34
+ await rename(temporary, file);
35
+ }
36
+ async function readApiKey() {
37
+ try {
38
+ const value = JSON.parse(await readFile(path.join(home(), "credentials.json"), "utf8"));
39
+ return typeof value.apiKey === "string" && value.apiKey.length > 0 ? value.apiKey : null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ async function installationId() {
45
+ const file = path.join(home(), "config.json");
46
+ try {
47
+ const data = JSON.parse(await readFile(file, "utf8"));
48
+ if (data.installationId) return data.installationId;
49
+ } catch {
50
+ }
51
+ const id = randomUUID();
52
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
53
+ await mkdir(home(), { recursive: true, mode: 448 });
54
+ await writeFile(temporary, `${JSON.stringify({ installationId: id }, null, 2)}
55
+ `, { mode: 384 });
56
+ await rename(temporary, file);
57
+ return id;
58
+ }
59
+ var OBJECTIVES = ["quality", "balanced", "tokens", "latency"];
60
+ var PERMISSIONS = ["inspect", "edit", "test", "network"];
61
+ function policyFile() {
62
+ return path.join(home(), "policy.json");
63
+ }
64
+ function sanitizePolicy(value) {
65
+ const policy = {};
66
+ if (typeof value.rootHarness === "string" && value.rootHarness.length > 0 && value.rootHarness.length <= 50) policy.rootHarness = value.rootHarness;
67
+ if (typeof value.rootQualityPrior === "number" && value.rootQualityPrior >= 0 && value.rootQualityPrior <= 1) policy.rootQualityPrior = value.rootQualityPrior;
68
+ if (OBJECTIVES.includes(value.objective)) policy.objective = value.objective;
69
+ if (PERMISSIONS.includes(value.permissionCeiling)) policy.permissionCeiling = value.permissionCeiling;
70
+ if (typeof value.maxDelegationDepth === "number" && Number.isInteger(value.maxDelegationDepth) && value.maxDelegationDepth >= 0 && value.maxDelegationDepth <= 3) policy.maxDelegationDepth = value.maxDelegationDepth;
71
+ if (Array.isArray(value.subscriptionHarnesses)) policy.subscriptionHarnesses = value.subscriptionHarnesses.filter((item) => typeof item === "string" && item.length > 0 && item.length <= 50).slice(0, 10);
72
+ if (typeof value.sharePromptDefault === "boolean") policy.sharePromptDefault = value.sharePromptDefault;
73
+ return policy;
74
+ }
75
+ async function readLocalPolicy() {
76
+ try {
77
+ return sanitizePolicy(JSON.parse(await readFile(policyFile(), "utf8")));
78
+ } catch {
79
+ return {};
80
+ }
81
+ }
82
+ async function saveLocalPolicy(patch) {
83
+ const current = await readLocalPolicy();
84
+ const merged = sanitizePolicy({ ...current, ...patch });
85
+ const file = policyFile();
86
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
87
+ await mkdir(home(), { recursive: true, mode: 448 });
88
+ await writeFile(temporary, `${JSON.stringify(merged, null, 2)}
89
+ `, { mode: 384 });
90
+ await rename(temporary, file);
91
+ return merged;
92
+ }
93
+ async function saveLastRoute(receipt) {
94
+ const directory = home();
95
+ const file = path.join(directory, "last-route.json");
96
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
97
+ await mkdir(directory, { recursive: true, mode: 448 });
98
+ await writeFile(temporary, `${JSON.stringify(receipt, null, 2)}
99
+ `, { mode: 384 });
100
+ await rename(temporary, file);
101
+ }
102
+ async function readLastRoute() {
103
+ const value = JSON.parse(await readFile(path.join(home(), "last-route.json"), "utf8"));
104
+ if (typeof value.routeId !== "string" || typeof value.feedbackToken !== "string" || typeof value.expiresAt !== "string") {
105
+ throw new Error("invalid local route receipt");
106
+ }
107
+ return {
108
+ routeId: value.routeId,
109
+ ...typeof value.runId === "string" ? { runId: value.runId } : {},
110
+ feedbackToken: value.feedbackToken,
111
+ expiresAt: value.expiresAt,
112
+ targetId: value.targetId ?? null
113
+ };
114
+ }
115
+
116
+ // src/local/process.ts
117
+ import { spawn } from "child_process";
118
+ function terminateProcessTree(child, signal) {
119
+ if (process.platform !== "win32" && child.pid) {
120
+ try {
121
+ process.kill(-child.pid, signal);
122
+ return;
123
+ } catch {
124
+ }
125
+ }
126
+ try {
127
+ child.kill(signal);
128
+ } catch {
129
+ }
130
+ }
131
+ async function run(command, args, options = {}) {
132
+ return await new Promise((resolve, reject) => {
133
+ const child = spawn(command, args, {
134
+ cwd: options.cwd,
135
+ shell: false,
136
+ detached: process.platform !== "win32",
137
+ env: options.env ?? process.env,
138
+ stdio: ["pipe", "pipe", "pipe"]
139
+ });
140
+ let stdout = "", stderr = "", timedOut = false;
141
+ let settled = false;
142
+ const max = options.maxBytes ?? 8e6;
143
+ child.stdout.on("data", (data) => {
144
+ if (stdout.length < max) stdout += data.toString().slice(0, max - stdout.length);
145
+ });
146
+ child.stderr.on("data", (data) => {
147
+ if (stderr.length < max) stderr += data.toString().slice(0, max - stderr.length);
148
+ });
149
+ let forceTimer;
150
+ let timer;
151
+ const finish = (code) => {
152
+ if (settled) return;
153
+ settled = true;
154
+ if (timer) clearTimeout(timer);
155
+ if (forceTimer) clearTimeout(forceTimer);
156
+ resolve({ code, stdout, stderr, timedOut });
157
+ };
158
+ child.on("error", (error) => {
159
+ if (settled) return;
160
+ settled = true;
161
+ if (timer) clearTimeout(timer);
162
+ if (forceTimer) clearTimeout(forceTimer);
163
+ reject(error);
164
+ });
165
+ timer = setTimeout(() => {
166
+ timedOut = true;
167
+ terminateProcessTree(child, "SIGTERM");
168
+ forceTimer = setTimeout(() => {
169
+ terminateProcessTree(child, "SIGKILL");
170
+ child.stdin.destroy();
171
+ child.stdout.destroy();
172
+ child.stderr.destroy();
173
+ child.unref();
174
+ finish(null);
175
+ }, 1e3);
176
+ }, options.timeoutMs ?? 15e3);
177
+ child.on("close", finish);
178
+ if (options.input) child.stdin.end(options.input);
179
+ else child.stdin.end();
180
+ });
181
+ }
182
+
183
+ // src/local/discovery.ts
184
+ var DISCOVERY_CACHE_VERSION = 5;
185
+ var DEFAULT_DISCOVERY_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
186
+ var INCOMPLETE_DISCOVERY_CACHE_TTL_MS = 5 * 60 * 1e3;
187
+ var HARNESS_PROBE_DEADLINE_MS = 12e3;
188
+ async function executable(names) {
189
+ const directories = [...new Set([process.env.NVM_BIN, process.env.VOLTA_HOME ? path2.join(process.env.VOLTA_HOME, "bin") : void 0, path2.dirname(process.execPath), ...(process.env.PATH ?? "").split(path2.delimiter), path2.join(os2.homedir(), ".local", "bin"), path2.join(os2.homedir(), ".opencode", "bin")].filter((value) => Boolean(value)))];
190
+ for (const name of names) for (const directory of directories) {
191
+ const candidate = path2.join(directory, name);
192
+ try {
193
+ await access(candidate);
194
+ return candidate;
195
+ } catch {
196
+ }
197
+ }
198
+ }
199
+ function subscriptionApproved(harness) {
200
+ return (process.env.TOKENSIZE_ALLOW_SUBSCRIPTION_HARNESSES ?? "").split(",").map((v) => v.trim()).includes(harness);
201
+ }
202
+ function discoveryCacheFile() {
203
+ return path2.join(home(), "discovery.json");
204
+ }
205
+ function discoveryCacheTtlMs() {
206
+ const value = Number(process.env.TOKENSIZE_DISCOVERY_CACHE_TTL_MS ?? DEFAULT_DISCOVERY_CACHE_TTL_MS);
207
+ return Number.isFinite(value) && value >= 0 ? value : DEFAULT_DISCOVERY_CACHE_TTL_MS;
208
+ }
209
+ function discoveryFingerprint() {
210
+ return JSON.stringify({
211
+ path: process.env.PATH ?? "",
212
+ nvmBin: process.env.NVM_BIN ?? "",
213
+ voltaHome: process.env.VOLTA_HOME ?? "",
214
+ claudeModels: process.env.TOKENSIZE_CLAUDE_MODELS ?? "",
215
+ copilotModels: process.env.TOKENSIZE_COPILOT_MODELS ?? ""
216
+ });
217
+ }
218
+ function model(harness, id, name, approved, authMode = "subscription", priors = {}) {
219
+ return { id: `${harness}:${id}`, harness, nativeModelId: id, displayName: name, readiness: "model-listed", authMode, productUseApproved: approved, capabilities: { tools: true, vision: harness === "codex" || harness === "claude", structuredOutput: harness === "codex" ? "jsonl" : "json", sessions: "resume", permissions: ["inspect", "edit", "test", "network"] }, identityProof: "runtime-reported", qualityPrior: priors.qualityPrior ?? 0.9, tokenEfficiencyPrior: priors.tokenEfficiencyPrior ?? 0.72, latencyPrior: priors.latencyPrior ?? 0.65 };
220
+ }
221
+ async function codex() {
222
+ const exe = await executable(["codex"]);
223
+ if (!exe) return { harness: "codex", installed: false, authenticated: false, models: [], warnings: [] };
224
+ const [version, auth] = await Promise.all([
225
+ run(exe, ["--version"], { timeoutMs: 5e3, maxBytes: 64e3 }),
226
+ run(exe, ["login", "status"], { timeoutMs: 5e3, maxBytes: 64e3 })
227
+ ]);
228
+ const child = await import("child_process").then(({ spawn: spawn3 }) => spawn3(exe, ["app-server", "--stdio"], {
229
+ detached: process.platform !== "win32",
230
+ shell: false,
231
+ stdio: ["pipe", "pipe", "ignore"]
232
+ }));
233
+ const models = await new Promise((resolve) => {
234
+ let buffer = "", done = false;
235
+ const finish = (value) => {
236
+ if (done) return;
237
+ done = true;
238
+ clearTimeout(timer);
239
+ terminateProcessTree(child, "SIGTERM");
240
+ resolve(value);
241
+ };
242
+ const timer = setTimeout(() => finish([]), 8e3);
243
+ child.once("error", () => finish([]));
244
+ child.stdout.on("data", (chunk) => {
245
+ if (buffer.length >= 2e6) return finish([]);
246
+ buffer += chunk.toString();
247
+ let newline;
248
+ while ((newline = buffer.indexOf("\n")) >= 0) {
249
+ const line = buffer.slice(0, newline);
250
+ buffer = buffer.slice(newline + 1);
251
+ try {
252
+ const message = JSON.parse(line);
253
+ if (message.id === 1) {
254
+ child.stdin.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
255
+ child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: 2, method: "model/list", params: { includeHidden: false, limit: 100 } }) + "\n");
256
+ }
257
+ if (message.id === 2) {
258
+ const apiKey = Boolean(process.env.OPENAI_API_KEY);
259
+ finish((message.result?.data ?? []).map((m, index) => model("codex", m.id, m.displayName ?? m.id, apiKey || subscriptionApproved("codex"), apiKey ? "api-key" : "subscription", { qualityPrior: Math.max(0.82, 0.97 - index * 0.04), tokenEfficiencyPrior: /mini/i.test(m.id) ? 0.94 : Math.min(0.88, 0.76 + index * 0.03), latencyPrior: /mini/i.test(m.id) ? 0.94 : 0.68 })));
260
+ }
261
+ } catch {
262
+ }
263
+ }
264
+ });
265
+ child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { clientInfo: { name: "tokensize", version: "0.1.0" }, capabilities: { experimentalApi: true } } }) + "\n");
266
+ });
267
+ return { harness: "codex", installed: true, executable: exe, version: version.stdout.trim(), authenticated: auth.code === 0, models, warnings: models.length ? [] : ["Codex model catalog was unavailable"] };
268
+ }
269
+ async function generic(harness, names) {
270
+ const exe = await executable(names);
271
+ if (!exe) return { harness, installed: false, authenticated: false, models: [], warnings: [] };
272
+ const [version, claudeAuth, help] = await Promise.all([
273
+ run(exe, ["--version"], { timeoutMs: 5e3, maxBytes: 64e3 }),
274
+ harness === "claude" ? run(exe, ["auth", "status"], { timeoutMs: 8e3, maxBytes: 64e3 }) : Promise.resolve(void 0),
275
+ harness === "claude" ? run(exe, ["--help"], { timeoutMs: 5e3, maxBytes: 256e3 }) : Promise.resolve(void 0)
276
+ ]);
277
+ let authenticated = harness !== "copilot";
278
+ if (harness === "claude") {
279
+ authenticated = claudeAuth?.code === 0;
280
+ }
281
+ if (harness === "copilot") authenticated = Boolean(process.env.GH_TOKEN || process.env.GITHUB_TOKEN);
282
+ const cliReported = harness === "claude" ? [.../* @__PURE__ */ new Set([...(help?.stdout.match(/'([a-z][a-z0-9.-]+)'/g) ?? []).map((value) => value.slice(1, -1)), ...help?.stdout.match(/claude-[a-z0-9.-]+/g) ?? []])].filter((id) => ["fable", "opus", "sonnet"].includes(id) || id.startsWith("claude-")) : [];
283
+ const configured = [.../* @__PURE__ */ new Set([...(process.env[`TOKENSIZE_${harness.toUpperCase()}_MODELS`] ?? "").split(",").map((v) => v.trim()).filter(Boolean), ...cliReported])];
284
+ const apiKey = harness === "claude" && Boolean(process.env.ANTHROPIC_API_KEY);
285
+ const approved = apiKey || subscriptionApproved(harness);
286
+ const warnings = configured.length ? [] : [`Set TOKENSIZE_${harness.toUpperCase()}_MODELS to advertise supported models`];
287
+ if (harness === "copilot" && !authenticated) warnings.push("Copilot CLI has no non-interactive auth-status command; set GH_TOKEN/GITHUB_TOKEN or verify authentication before advertising models");
288
+ return { harness, installed: true, executable: exe, version: version.stdout.trim(), authenticated, models: configured.map((id) => model(harness, id, id, approved, apiKey ? "api-key" : "subscription")), warnings };
289
+ }
290
+ async function cursor() {
291
+ const exe = await executable(["agent", "cursor-agent"]);
292
+ if (!exe) return { harness: "cursor", installed: false, authenticated: false, models: [], warnings: [] };
293
+ const [version, auth, catalog] = await Promise.all([
294
+ run(exe, ["--version"], { timeoutMs: 5e3, maxBytes: 64e3 }),
295
+ run(exe, ["status"], { timeoutMs: 8e3, maxBytes: 64e3 }),
296
+ run(exe, ["--list-models"], { timeoutMs: 1e4, maxBytes: 512e3 })
297
+ ]);
298
+ const available = catalog.stdout.split(/\r?\n/).flatMap((line) => {
299
+ const match = /^(\S+)\s+-\s+(.+)$/.exec(line.trim());
300
+ return match ? [{ id: match[1], name: match[2] }] : [];
301
+ });
302
+ const preferred = [
303
+ /^gpt-5\.6-sol-high$/,
304
+ /^claude-opus-4-8-high$/,
305
+ /^claude-fable-5-high$/,
306
+ /^claude-sonnet-5-high$/,
307
+ /^cursor-grok-4\.5-high$/,
308
+ /^composer-2\.5$/,
309
+ /^gpt-5\.5-high$/,
310
+ /^gpt-5\.4-high$/
311
+ ];
312
+ const selected = preferred.flatMap((pattern) => available.find((item) => pattern.test(item.id)) ?? []);
313
+ const approved = subscriptionApproved("cursor");
314
+ const models = selected.map((item, index) => {
315
+ const candidate = model("cursor", item.id, item.name, approved, "subscription", { qualityPrior: Math.max(0.84, 0.98 - index * 0.018), tokenEfficiencyPrior: /composer|grok/i.test(item.id) ? 0.86 : 0.72, latencyPrior: /composer|grok/i.test(item.id) ? 0.82 : 0.66 });
316
+ candidate.capabilities.permissions = ["inspect"];
317
+ return candidate;
318
+ });
319
+ return { harness: "cursor", installed: true, executable: exe, version: version.stdout.trim(), authenticated: auth.code === 0 && /logged in/i.test(auth.stdout), models, warnings: models.length ? [] : ["Cursor model catalog did not contain a supported frontier model"] };
320
+ }
321
+ function stripAnsi(value) {
322
+ return value.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
323
+ }
324
+ function opencodeModelIds(output) {
325
+ return [...new Set(stripAnsi(output).split(/\r?\n/).map((line) => line.trim()).filter((line) => /^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:/-]*$/i.test(line)))];
326
+ }
327
+ function isCredentialFreeOpenCodeModel(id) {
328
+ return /^opencode\/(?:big-pickle|[a-z0-9._:/-]+-free)$/i.test(id);
329
+ }
330
+ async function opencode() {
331
+ const exe = await executable(["opencode"]);
332
+ if (!exe) return { harness: "opencode", installed: false, authenticated: false, models: [], warnings: [] };
333
+ const [version, auth, initialCatalog] = await Promise.all([
334
+ run(exe, ["--version"], { timeoutMs: 5e3, maxBytes: 64e3 }),
335
+ run(exe, ["auth", "list"], { timeoutMs: 8e3, maxBytes: 128e3 }),
336
+ run(exe, ["models"], { timeoutMs: 1e4, maxBytes: 512e3 })
337
+ ]);
338
+ const available = opencodeModelIds(initialCatalog.stdout);
339
+ const credentialFree = available.filter(isCredentialFreeOpenCodeModel);
340
+ const frontier = available.filter((id) => /gpt-5\.[4-9]|opus|fable|sonnet|grok|gemini|glm|qwen|coder/i.test(id));
341
+ const ids = [.../* @__PURE__ */ new Set([...credentialFree, ...frontier.length ? frontier : available])].slice(0, 48);
342
+ const authOutput = stripAnsi(`${auth.stdout}
343
+ ${auth.stderr}`).trim();
344
+ const hasStoredCredentials = auth.code === 0 && authOutput.length > 0 && !/\b(?:no|0)\s+(?:credentials|providers|authentication)\b/i.test(authOutput);
345
+ const authenticated = hasStoredCredentials || credentialFree.length > 0;
346
+ const subscriptionIsApproved = subscriptionApproved("opencode");
347
+ const models = ids.map((id, index) => {
348
+ const isCredentialFree = isCredentialFreeOpenCodeModel(id);
349
+ const candidate = model("opencode", id, id, isCredentialFree || subscriptionIsApproved, isCredentialFree ? "cloud-provider" : "subscription", {
350
+ qualityPrior: Math.max(0.82, 0.96 - index * 0.01),
351
+ tokenEfficiencyPrior: 0.76,
352
+ latencyPrior: 0.7
353
+ });
354
+ candidate.capabilities.permissions = ["inspect"];
355
+ candidate.capabilities.vision = false;
356
+ return candidate;
357
+ });
358
+ const warnings = [];
359
+ if (!hasStoredCredentials && credentialFree.length) warnings.push(`OpenCode has no stored provider credentials; ${credentialFree.length} credential-free model(s) remain available`);
360
+ else if (!hasStoredCredentials) warnings.push("OpenCode did not report a configured provider credential");
361
+ if (!models.length) warnings.push("OpenCode model catalog was unavailable; run `opencode models --refresh`");
362
+ if (!subscriptionIsApproved && models.some((item) => item.authMode === "subscription")) warnings.push("Paid/provider OpenCode models remain ineligible; set TOKENSIZE_ALLOW_SUBSCRIPTION_HARNESSES=opencode only when provider and product terms permit delegated use");
363
+ return { harness: "opencode", installed: true, executable: exe, version: version.stdout.trim(), authenticated, models, warnings };
364
+ }
365
+ var HARNESS_IDS = ["codex", "claude", "copilot", "cursor", "opencode"];
366
+ async function discoverHarness(harness) {
367
+ let probe;
368
+ switch (harness) {
369
+ case "codex":
370
+ probe = codex;
371
+ break;
372
+ case "claude":
373
+ probe = () => generic("claude", ["claude"]);
374
+ break;
375
+ case "copilot":
376
+ probe = () => generic("copilot", ["copilot", "github-copilot-cli"]);
377
+ break;
378
+ case "cursor":
379
+ probe = cursor;
380
+ break;
381
+ case "opencode":
382
+ probe = opencode;
383
+ break;
384
+ case "custom":
385
+ return { harness: "custom", installed: false, authenticated: false, models: [], warnings: ["No custom harness adapter is configured"] };
386
+ }
387
+ let timer;
388
+ const deadline = new Promise((resolve) => {
389
+ timer = setTimeout(() => resolve({
390
+ harness,
391
+ installed: true,
392
+ authenticated: false,
393
+ models: [],
394
+ warnings: [`${harness} discovery exceeded the ${HARNESS_PROBE_DEADLINE_MS / 1e3}s safety deadline; retry with --refresh`],
395
+ probeTimedOut: true
396
+ }), HARNESS_PROBE_DEADLINE_MS);
397
+ });
398
+ return Promise.race([probe().finally(() => {
399
+ if (timer) clearTimeout(timer);
400
+ }), deadline]);
401
+ }
402
+ async function discoverHarnesses() {
403
+ const [codexResult, cursorResult] = await Promise.all([discoverHarness("codex"), discoverHarness("cursor")]);
404
+ const [claudeResult, copilotResult, opencodeResult] = await Promise.all([
405
+ discoverHarness("claude"),
406
+ discoverHarness("copilot"),
407
+ discoverHarness("opencode")
408
+ ]);
409
+ return [codexResult, claudeResult, copilotResult, cursorResult, opencodeResult];
410
+ }
411
+ function applyCurrentApproval(harnesses) {
412
+ return harnesses.map((item) => ({
413
+ ...item,
414
+ models: item.models.map((candidate) => {
415
+ const apiKeyMode = candidate.harness === "codex" && Boolean(process.env.OPENAI_API_KEY) || candidate.harness === "claude" && Boolean(process.env.ANTHROPIC_API_KEY);
416
+ const credentialFree = candidate.harness === "opencode" && isCredentialFreeOpenCodeModel(candidate.nativeModelId);
417
+ return {
418
+ ...candidate,
419
+ authMode: credentialFree ? "cloud-provider" : apiKeyMode ? "api-key" : "subscription",
420
+ productUseApproved: credentialFree || apiKeyMode || subscriptionApproved(candidate.harness)
421
+ };
422
+ })
423
+ }));
424
+ }
425
+ async function cachedExecutablesExist(harnesses) {
426
+ try {
427
+ await Promise.all(harnesses.filter((item) => item.installed && !item.probeTimedOut).map((item) => item.executable ? access(item.executable) : Promise.reject(new Error("cached installed harness has no executable"))));
428
+ return true;
429
+ } catch {
430
+ return false;
431
+ }
432
+ }
433
+ async function readDiscoveryCache() {
434
+ const file = discoveryCacheFile();
435
+ try {
436
+ const cached = JSON.parse(await readFile2(file, "utf8"));
437
+ if (cached.schemaVersion !== DISCOVERY_CACHE_VERSION || cached.fingerprint !== discoveryFingerprint() || !cached.createdAt || !cached.expiresAt || !Array.isArray(cached.harnesses) || Date.parse(cached.expiresAt) <= Date.now() || !await cachedExecutablesExist(cached.harnesses)) return null;
438
+ return {
439
+ harnesses: applyCurrentApproval(cached.harnesses),
440
+ cache: { hit: true, path: file, createdAt: cached.createdAt, expiresAt: cached.expiresAt, ageMs: Math.max(0, Date.now() - Date.parse(cached.createdAt)) }
441
+ };
442
+ } catch {
443
+ return null;
444
+ }
445
+ }
446
+ async function writeDiscoveryCache(harnesses, reason) {
447
+ const directory = home();
448
+ const file = discoveryCacheFile();
449
+ const temporary = `${file}.${process.pid}.tmp`;
450
+ const createdAt = /* @__PURE__ */ new Date();
451
+ const cacheTtl = harnesses.some((item) => item.probeTimedOut) ? Math.min(discoveryCacheTtlMs(), INCOMPLETE_DISCOVERY_CACHE_TTL_MS) : discoveryCacheTtlMs();
452
+ const value = {
453
+ schemaVersion: DISCOVERY_CACHE_VERSION,
454
+ createdAt: createdAt.toISOString(),
455
+ expiresAt: new Date(createdAt.getTime() + cacheTtl).toISOString(),
456
+ fingerprint: discoveryFingerprint(),
457
+ harnesses
458
+ };
459
+ await mkdir2(directory, { recursive: true, mode: 448 });
460
+ await writeFile2(temporary, `${JSON.stringify(value, null, 2)}
461
+ `, { mode: 384 });
462
+ await rename2(temporary, file);
463
+ return { hit: false, refreshed: true, reason, path: file, createdAt: value.createdAt, expiresAt: value.expiresAt, ageMs: 0 };
464
+ }
465
+ async function discoverHarnessesCached(options = {}) {
466
+ if (!options.forceRefresh) {
467
+ const cached = await readDiscoveryCache();
468
+ if (cached) return cached;
469
+ }
470
+ const harnesses = await discoverHarnesses();
471
+ const reason = options.reason ?? (options.forceRefresh ? "manual" : "miss-or-stale");
472
+ try {
473
+ return { harnesses, cache: await writeDiscoveryCache(harnesses, reason) };
474
+ } catch (error) {
475
+ return {
476
+ harnesses,
477
+ cache: { hit: false, refreshed: true, persisted: false, reason, warning: `could not persist discovery cache: ${error instanceof Error ? error.message : String(error)}` }
478
+ };
479
+ }
480
+ }
481
+ function flattenModels(discovery, maximum = 100) {
482
+ const queues = discovery.filter((item) => item.authenticated).map((item) => [...item.models]);
483
+ const selected = [];
484
+ while (selected.length < maximum && queues.some((queue) => queue.length)) {
485
+ for (const queue of queues) {
486
+ const next = queue.shift();
487
+ if (next) selected.push(next);
488
+ if (selected.length === maximum) break;
489
+ }
490
+ }
491
+ return selected;
492
+ }
493
+
494
+ // src/local/allowance.ts
495
+ import { spawn as spawn2 } from "child_process";
496
+ import { access as access2, mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "fs/promises";
497
+ import path3 from "path";
498
+ var CACHE_VERSION = 1;
499
+ var DEFAULT_TTL_MS = 5 * 60 * 1e3;
500
+ function observed(status2, source, remainingFraction, resetsAt) {
501
+ const normalized = remainingFraction === void 0 ? void 0 : Math.round(Math.max(0, Math.min(1, remainingFraction)) * 1e4) / 1e4;
502
+ return {
503
+ status: status2,
504
+ source,
505
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
506
+ ...normalized === void 0 ? {} : { remainingFraction: normalized },
507
+ ...resetsAt ? { resetsAt } : {}
508
+ };
509
+ }
510
+ function status(remaining) {
511
+ if (remaining <= 0) return "exhausted";
512
+ if (remaining <= 0.15) return "low";
513
+ return "available";
514
+ }
515
+ function unknown() {
516
+ return observed("unknown", "unavailable");
517
+ }
518
+ function cacheFile() {
519
+ return path3.join(home(), "allowance.json");
520
+ }
521
+ function ttlMs() {
522
+ const value = Number(process.env.TOKENSIZE_ALLOWANCE_CACHE_TTL_MS ?? DEFAULT_TTL_MS);
523
+ return Number.isFinite(value) && value >= 0 ? value : DEFAULT_TTL_MS;
524
+ }
525
+ async function codexAllowance(executable2) {
526
+ return await new Promise((resolve) => {
527
+ const child = spawn2(executable2, ["app-server", "--stdio"], { shell: false, stdio: ["pipe", "pipe", "ignore"] });
528
+ let buffer = "";
529
+ let complete = false;
530
+ const finish = (value) => {
531
+ if (complete) return;
532
+ complete = true;
533
+ clearTimeout(timer);
534
+ child.kill();
535
+ resolve(value);
536
+ };
537
+ const timer = setTimeout(() => finish(unknown()), 15e3);
538
+ child.once("error", () => finish(unknown()));
539
+ child.stdout.on("data", (chunk) => {
540
+ buffer += chunk.toString();
541
+ let newline;
542
+ while ((newline = buffer.indexOf("\n")) >= 0) {
543
+ const line = buffer.slice(0, newline);
544
+ buffer = buffer.slice(newline + 1);
545
+ try {
546
+ const message = JSON.parse(line);
547
+ if (message.id === 1) {
548
+ child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })}
549
+ `);
550
+ child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: 2, method: "account/rateLimits/read" })}
551
+ `);
552
+ }
553
+ if (message.id === 2) {
554
+ const limits = message.result?.rateLimits;
555
+ if (!limits) return finish(unknown());
556
+ if (limits.rateLimitReachedType) return finish(observed("exhausted", "runtime-reported", 0));
557
+ const windows = [limits.primary, limits.secondary].filter((value) => Boolean(value)).flatMap((value) => typeof value.usedPercent === "number" ? [{ remaining: 1 - value.usedPercent / 100, resetsAt: value.resetsAt }] : []);
558
+ if (typeof limits.individualLimit?.remainingPercent === "number") {
559
+ windows.push({ remaining: limits.individualLimit.remainingPercent / 100, resetsAt: limits.individualLimit.resetsAt });
560
+ }
561
+ if (!windows.length && limits.credits?.unlimited) return finish(observed("unmetered", "runtime-reported"));
562
+ if (!windows.length) return finish(unknown());
563
+ const constrained = windows.sort((a, b) => a.remaining - b.remaining)[0];
564
+ const remaining = Math.max(0, Math.min(1, constrained.remaining));
565
+ const resetsAt = constrained.resetsAt ? new Date(constrained.resetsAt * 1e3).toISOString() : void 0;
566
+ return finish(observed(status(remaining), "runtime-reported", remaining, resetsAt));
567
+ }
568
+ } catch {
569
+ }
570
+ }
571
+ });
572
+ child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { clientInfo: { name: "tokensize", version: "0.1.0" }, capabilities: { experimentalApi: true } } })}
573
+ `);
574
+ });
575
+ }
576
+ function stripTerminal(value) {
577
+ return value.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").replace(/\r/g, "\n");
578
+ }
579
+ function usedPercent(text, label) {
580
+ const match = label.exec(text);
581
+ if (match?.index === void 0) return void 0;
582
+ const nearby = text.slice(match.index, match.index + 260);
583
+ const percentage = /(\d{1,3})%\s*used/i.exec(nearby);
584
+ return percentage ? Math.max(0, Math.min(100, Number(percentage[1]))) : void 0;
585
+ }
586
+ function parseClaudeUsage(raw) {
587
+ const text = stripTerminal(raw);
588
+ const usageScreen = text.slice(Math.max(0, text.lastIndexOf("Settings")));
589
+ const ordered = [...usageScreen.matchAll(/(\d{1,3})\s*%\s*used/gi)].map((match) => Math.max(0, Math.min(100, Number(match[1]))));
590
+ const exactSession = usedPercent(text, /Current\s+session/i);
591
+ const exactGeneral = usedPercent(text, /Current\s+week\s*\(all\s+models\)/i);
592
+ const exactFable = usedPercent(text, /Current\s+week\s*\(Fable\)/i);
593
+ const sessionUsed = exactSession ?? ordered[0];
594
+ const weeklyUsed = exactGeneral ?? ordered[1] ?? ordered[0];
595
+ const fableUsed = exactFable ?? ordered[2];
596
+ const generalUsed = [sessionUsed, weeklyUsed].filter((value) => value !== void 0);
597
+ const generalRemaining = generalUsed.length ? 1 - Math.max(...generalUsed) / 100 : void 0;
598
+ const defaultAllowance = generalRemaining === void 0 ? unknown() : observed(status(generalRemaining), "runtime-reported", generalRemaining);
599
+ const byModelId = {};
600
+ if (fableUsed !== void 0) {
601
+ const remaining = Math.min(generalRemaining ?? 1, 1 - fableUsed / 100);
602
+ byModelId.fable = observed(status(remaining), "runtime-reported", remaining);
603
+ }
604
+ return { default: defaultAllowance, ...Object.keys(byModelId).length ? { byModelId } : {} };
605
+ }
606
+ async function claudeAllowance(executable2) {
607
+ if (process.platform !== "darwin") return { default: unknown() };
608
+ try {
609
+ await access2("/usr/bin/expect");
610
+ } catch {
611
+ return { default: unknown() };
612
+ }
613
+ return await new Promise((resolve) => {
614
+ const script = 'set timeout 10; log_user 1; spawn -noecho $env(TOKENSIZE_CLAUDE_EXECUTABLE) --safe-mode; after 1800; send -- "/usage\\r"; after 700; send -- "\\r"; expect -re {Current}; after 3500; exit 0';
615
+ const child = spawn2("/usr/bin/expect", ["-c", script], {
616
+ env: { ...process.env, TERM: "xterm-256color", TOKENSIZE_CLAUDE_EXECUTABLE: executable2 },
617
+ stdio: ["pipe", "pipe", "pipe"]
618
+ });
619
+ let raw = "";
620
+ let complete = false;
621
+ const finish = () => {
622
+ if (complete) return;
623
+ complete = true;
624
+ clearTimeout(timeout);
625
+ try {
626
+ child.kill("SIGTERM");
627
+ } catch {
628
+ }
629
+ const parsed = parseClaudeUsage(raw);
630
+ raw = "";
631
+ resolve(parsed);
632
+ };
633
+ const timeout = setTimeout(finish, 13e3);
634
+ child.once("error", finish);
635
+ const collect2 = (chunk) => {
636
+ if (raw.length < 256e3) raw += chunk.toString().slice(0, 256e3 - raw.length);
637
+ };
638
+ child.stdout.on("data", collect2);
639
+ child.stderr.on("data", collect2);
640
+ child.once("close", finish);
641
+ });
642
+ }
643
+ async function collect(discovery) {
644
+ const result = {};
645
+ for (const item of discovery) {
646
+ if (!item.installed || !item.authenticated || !item.executable) continue;
647
+ if (item.harness === "codex") {
648
+ let detected = await codexAllowance(item.executable);
649
+ if (detected.status === "unknown") detected = await codexAllowance(item.executable);
650
+ result.codex = { default: detected };
651
+ } else if (item.harness === "claude") {
652
+ let detected = await claudeAllowance(item.executable);
653
+ if (detected.default.status === "unknown") detected = await claudeAllowance(item.executable);
654
+ result.claude = detected;
655
+ } else result[item.harness] = { default: unknown() };
656
+ }
657
+ return result;
658
+ }
659
+ async function readCache() {
660
+ const file = cacheFile();
661
+ try {
662
+ const value = JSON.parse(await readFile3(file, "utf8"));
663
+ if (value.schemaVersion !== CACHE_VERSION || !value.createdAt || !value.expiresAt || !value.harnesses || Date.parse(value.expiresAt) <= Date.now()) return null;
664
+ return { harnesses: value.harnesses, cache: { hit: true, path: file, createdAt: value.createdAt, expiresAt: value.expiresAt } };
665
+ } catch {
666
+ return null;
667
+ }
668
+ }
669
+ async function allowances(discovery, forceRefresh = false) {
670
+ if (!forceRefresh) {
671
+ const cached = await readCache();
672
+ if (cached) return cached;
673
+ }
674
+ const harnesses = await collect(discovery);
675
+ const createdAt = /* @__PURE__ */ new Date();
676
+ const expiresAt = new Date(createdAt.getTime() + ttlMs());
677
+ const file = cacheFile();
678
+ const temporary = `${file}.${process.pid}.tmp`;
679
+ await mkdir3(home(), { recursive: true, mode: 448 });
680
+ await writeFile3(temporary, `${JSON.stringify({ schemaVersion: CACHE_VERSION, createdAt: createdAt.toISOString(), expiresAt: expiresAt.toISOString(), harnesses }, null, 2)}
681
+ `, { mode: 384 });
682
+ await rename3(temporary, file);
683
+ return { harnesses, cache: { hit: false, path: file, createdAt: createdAt.toISOString(), expiresAt: expiresAt.toISOString() } };
684
+ }
685
+ function forModel(model2, snapshot) {
686
+ if (model2.harness === "opencode" && /^opencode\/(?:big-pickle|[a-z0-9._:/-]+-free)$/i.test(model2.nativeModelId)) {
687
+ return observed("unmetered", "credential-free");
688
+ }
689
+ const harness = snapshot[model2.harness];
690
+ if (!harness) return unknown();
691
+ if (model2.harness === "claude" && /fable/i.test(model2.nativeModelId)) return harness.byModelId?.fable ?? unknown();
692
+ return harness.default;
693
+ }
694
+ function applyAllowances(discovery, snapshot) {
695
+ return discovery.map((item) => ({
696
+ ...item,
697
+ models: item.models.map((model2) => ({ ...model2, allowance: forModel(model2, snapshot.harnesses) }))
698
+ }));
699
+ }
700
+ function rootAllowance(harness, snapshot) {
701
+ return snapshot.harnesses[harness]?.default ?? unknown();
702
+ }
703
+ function publicAllowance(value) {
704
+ const remainingFraction = value.remainingFraction === void 0 ? void 0 : Math.round(value.remainingFraction * 1e4) / 1e4;
705
+ return {
706
+ ...value,
707
+ ...remainingFraction === void 0 ? {} : {
708
+ remainingFraction,
709
+ remainingPercent: Math.round(remainingFraction * 1e3) / 10
710
+ }
711
+ };
712
+ }
713
+ function allowanceKey(value) {
714
+ return [value.status, value.source, value.remainingFraction ?? "n/a", value.resetsAt ?? "n/a"].join(":");
715
+ }
716
+ function allowanceScopesForHarness(item) {
717
+ const scopes = /* @__PURE__ */ new Map();
718
+ for (const model2 of item.models) {
719
+ const value = model2.allowance ?? unknown();
720
+ const key = allowanceKey(value);
721
+ const existing = scopes.get(key);
722
+ if (existing) existing.modelIds.push(model2.nativeModelId);
723
+ else scopes.set(key, { modelIds: [model2.nativeModelId], allowance: value });
724
+ }
725
+ return [...scopes.values()].map((scope) => {
726
+ const compact = scope.modelIds.length > 12;
727
+ return {
728
+ ...compact ? { sampleModelIds: scope.modelIds.slice(0, 12), omittedModelCount: scope.modelIds.length - 12 } : { modelIds: scope.modelIds },
729
+ modelCount: scope.modelIds.length,
730
+ allowance: publicAllowance(scope.allowance)
731
+ };
732
+ });
733
+ }
734
+ function allowanceReport(discovery, snapshot) {
735
+ const available = applyAllowances(discovery, snapshot);
736
+ return available.filter((item) => item.installed).map((item) => ({
737
+ harness: item.harness,
738
+ harnessDefault: publicAllowance(snapshot.harnesses[item.harness]?.default ?? unknown()),
739
+ modelScopes: allowanceScopesForHarness(item)
740
+ }));
741
+ }
742
+
743
+ // src/local/execution.ts
744
+ function executionArgs(harness, model2, permission, cwd) {
745
+ if (harness === "codex") {
746
+ return ["exec", "-C", cwd, "-s", permission === "inspect" ? "read-only" : "workspace-write", "--json", "-m", model2, "-"];
747
+ }
748
+ if (harness === "claude") {
749
+ return ["-p", "--model", model2, "--output-format", "json", "--permission-mode", permission === "inspect" ? "plan" : "acceptEdits", "--no-session-persistence"];
750
+ }
751
+ if (harness === "cursor") {
752
+ return ["-p", "--output-format", "json", "--mode", "plan", "--model", model2, "--workspace", cwd, "--trust"];
753
+ }
754
+ if (harness === "opencode") {
755
+ return ["run", "--model", model2, "--agent", "plan", "--format", "json", "--dir", cwd];
756
+ }
757
+ throw new Error(`execution adapter for ${harness} is unavailable`);
758
+ }
759
+
760
+ // src/index.ts
761
+ var CLIENT_VERSION = "0.3.0-beta.0";
762
+ var MAX_OUTPUT_BYTES = 2e5;
763
+ function apiUrl(value) {
764
+ return (value ?? process.env.TOKENSIZE_API_URL ?? "https://api.tokensize.dev").replace(/\/$/, "");
765
+ }
766
+ function home2() {
767
+ return home();
768
+ }
769
+ function digest(value) {
770
+ return createHash("sha256").update(value.trim().replace(/\s+/g, " ")).digest("hex");
771
+ }
772
+ function receiptFile(receiptId) {
773
+ return path4.join(home2(), `receipt-${receiptId}.json`);
774
+ }
775
+ function receiptKeyFile() {
776
+ return path4.join(home2(), "receipt-key");
777
+ }
778
+ async function receiptKey() {
779
+ try {
780
+ const value2 = await readFile4(receiptKeyFile());
781
+ if (value2.length >= 32) return value2;
782
+ } catch {
783
+ }
784
+ const value = randomBytes(32);
785
+ await mkdir4(home2(), { recursive: true, mode: 448 });
786
+ await writeFile4(receiptKeyFile(), value, { mode: 384 });
787
+ return value;
788
+ }
789
+ function receiptMac(value, key) {
790
+ return createHmac("sha256", key).update(JSON.stringify(value)).digest("base64url");
791
+ }
792
+ async function saveReceipt(route, task, surface, runId) {
793
+ const receiptId = randomUUID2();
794
+ const value = {
795
+ receiptId,
796
+ taskDigest: digest(task),
797
+ installationId: await installationId(),
798
+ surface,
799
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
800
+ route,
801
+ ...runId ? { runId } : {}
802
+ };
803
+ const stored = { ...value, mac: receiptMac(value, await receiptKey()) };
804
+ const file = receiptFile(receiptId);
805
+ const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
806
+ await mkdir4(home2(), { recursive: true, mode: 448 });
807
+ await writeFile4(temporary, `${JSON.stringify(stored, null, 2)}
808
+ `, { mode: 384 });
809
+ await rename4(temporary, file);
810
+ return receiptId;
811
+ }
812
+ async function readReceipt(receiptId, task) {
813
+ if (!/^[0-9a-f-]{36}$/i.test(receiptId)) throw new Error("invalid route receipt");
814
+ const value = JSON.parse(await readFile4(receiptFile(receiptId), "utf8"));
815
+ if (value.receiptId !== receiptId || task !== void 0 && value.taskDigest !== digest(task) || Date.parse(value.route.expiresAt) <= Date.now()) throw new Error("route receipt is invalid, expired, or bound to a different task");
816
+ const { mac, ...unsigned } = value;
817
+ const expected = receiptMac(unsigned, await receiptKey());
818
+ const actualBytes = Buffer.from(mac ?? "", "base64url");
819
+ const expectedBytes = Buffer.from(expected, "base64url");
820
+ if (actualBytes.length !== expectedBytes.length || !timingSafeEqual(actualBytes, expectedBytes)) throw new Error("route receipt integrity check failed");
821
+ return value;
822
+ }
823
+ function summarizeHarness(item, verbose = false) {
824
+ const summary = {
825
+ harness: item.harness,
826
+ installed: item.installed,
827
+ authenticated: item.authenticated,
828
+ version: item.version,
829
+ modelCount: item.models.length,
830
+ allowanceScopes: allowanceScopesForHarness(item),
831
+ warnings: item.warnings
832
+ };
833
+ return verbose ? {
834
+ ...summary,
835
+ models: item.models.map(({ id, harness, nativeModelId, displayName, readiness, authMode, productUseApproved, capabilities, identityProof, qualityPrior, tokenEfficiencyPrior, latencyPrior, allowance }) => ({
836
+ id,
837
+ harness,
838
+ nativeModelId,
839
+ displayName,
840
+ readiness,
841
+ authMode,
842
+ productUseApproved,
843
+ capabilities,
844
+ identityProof,
845
+ qualityPrior,
846
+ tokenEfficiencyPrior,
847
+ latencyPrior,
848
+ allowance
849
+ }))
850
+ } : summary;
851
+ }
852
+ function parseRootHarness(value) {
853
+ if (["codex", "claude", "copilot", "cursor", "opencode", "custom"].includes(value ?? "")) return value;
854
+ return "codex";
855
+ }
856
+ function parseRole(value) {
857
+ return value ?? "inspect";
858
+ }
859
+ var TokenSizeClient = class {
860
+ options;
861
+ constructor(options = {}) {
862
+ this.options = {
863
+ ...options,
864
+ clientSurface: options.clientSurface ?? "cli",
865
+ clientVersion: options.clientVersion ?? CLIENT_VERSION
866
+ };
867
+ }
868
+ /**
869
+ * Resolve routing defaults: explicit constructor options win, then
870
+ * environment variables (CI overrides), then the local policy document at
871
+ * ~/.tokensize/policy.json, then fail-closed defaults.
872
+ */
873
+ async resolvedDefaults() {
874
+ const policy = await readLocalPolicy();
875
+ return {
876
+ policy,
877
+ rootHarness: this.options.rootHarness ?? parseRootHarness(process.env.TOKENSIZE_ROOT_HARNESS ?? policy.rootHarness),
878
+ rootActive: this.options.rootActive ?? process.env.TOKENSIZE_ROOT_ACTIVE === "1",
879
+ rootQualityPrior: this.options.rootQualityPrior ?? Number(process.env.TOKENSIZE_ROOT_QUALITY_PRIOR ?? policy.rootQualityPrior ?? 0.82),
880
+ maxDelegationDepth: this.options.maxDelegationDepth ?? Number(process.env.TOKENSIZE_MAX_DELEGATION_DEPTH ?? policy.maxDelegationDepth ?? 1)
881
+ };
882
+ }
883
+ async key(required = true) {
884
+ const value = process.env.TOKENSIZE_API_KEY ?? await readApiKey();
885
+ if (!value && required) throw new Error("TokenSize API key is missing; use tokensize_connect or tokensize auth login");
886
+ return value;
887
+ }
888
+ async api(pathname, options = {}) {
889
+ const key = await this.key();
890
+ const fetcher = this.options.fetchImpl ?? fetch;
891
+ const response = await fetcher(`${apiUrl(this.options.apiUrl)}${pathname}`, {
892
+ ...options,
893
+ headers: {
894
+ authorization: `Bearer ${key}`,
895
+ ...options.body ? { "content-type": "application/json" } : {},
896
+ ...options.headers
897
+ }
898
+ });
899
+ const text = await response.text();
900
+ let body;
901
+ try {
902
+ body = JSON.parse(text);
903
+ } catch {
904
+ body = { message: text.slice(0, 500) };
905
+ }
906
+ if (!response.ok) {
907
+ const record = body;
908
+ throw new Error(`service returned ${response.status}: ${record.error?.message ?? record.message ?? "request failed"}`);
909
+ }
910
+ return body;
911
+ }
912
+ async status(options = {}) {
913
+ const [key, discovered] = await Promise.all([
914
+ this.key(false),
915
+ discoverHarnessesCached({ forceRefresh: options.refresh, reason: options.refresh ? "manual" : void 0 })
916
+ ]);
917
+ const allowanceSnapshot = await allowances(discovered.harnesses, options.refresh);
918
+ const available = applyAllowances(discovered.harnesses, allowanceSnapshot);
919
+ const catalog = key ? await this.api("/v1/agent-catalog").catch((error) => ({ warning: error instanceof Error ? error.message : String(error) })) : void 0;
920
+ return {
921
+ service: { url: apiUrl(this.options.apiUrl), authenticated: Boolean(key), ...catalog === void 0 ? {} : { catalog } },
922
+ installationId: await installationId(),
923
+ cache: { discovery: discovered.cache, allowance: allowanceSnapshot.cache },
924
+ harnesses: available.map((item) => summarizeHarness(item, options.verbose === true))
925
+ };
926
+ }
927
+ async connect(surface = this.options.clientSurface) {
928
+ const state = randomUUID2();
929
+ let completed = false;
930
+ const server = createServer((request, response) => {
931
+ if (request.method === "OPTIONS" && request.url === `/callback/${state}`) {
932
+ response.writeHead(204, { "access-control-allow-origin": "https://tokensize.dev", "access-control-allow-methods": "POST", "access-control-allow-headers": "content-type" }).end();
933
+ return;
934
+ }
935
+ if (request.method !== "POST" || request.url !== `/callback/${state}`) {
936
+ response.writeHead(404).end();
937
+ return;
938
+ }
939
+ let body = "";
940
+ request.on("data", (chunk) => {
941
+ if (body.length < 16e3) body += chunk;
942
+ });
943
+ request.on("end", async () => {
944
+ try {
945
+ const payload = JSON.parse(body);
946
+ if (payload.state !== state || typeof payload.apiKey !== "string" || payload.apiKey.length < 20 || /\s/.test(payload.apiKey)) throw new Error("invalid callback");
947
+ await saveApiKey(payload.apiKey);
948
+ completed = true;
949
+ response.writeHead(200, { "content-type": "text/plain", "access-control-allow-origin": "https://tokensize.dev" }).end("TokenSize connected. You can close this window.\n");
950
+ setTimeout(() => server.close(), 100);
951
+ } catch {
952
+ response.writeHead(400, { "access-control-allow-origin": "https://tokensize.dev" }).end("Invalid TokenSize callback.\n");
953
+ }
954
+ });
955
+ });
956
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
957
+ const address = server.address();
958
+ if (!address || typeof address === "string") throw new Error("could not start local authorization callback");
959
+ const callback = `http://127.0.0.1:${address.port}/callback/${state}`;
960
+ const url = `https://tokensize.dev/authorize?callback=${encodeURIComponent(callback)}&state=${encodeURIComponent(state)}&surface=${encodeURIComponent(surface)}`;
961
+ if (this.options.openBrowser) this.options.openBrowser(url);
962
+ else execFile(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? ["/c", "start", url] : [url]);
963
+ const timeout = setTimeout(() => server.close(), 10 * 60 * 1e3);
964
+ await new Promise((resolve) => server.on("close", resolve));
965
+ clearTimeout(timeout);
966
+ if (!completed) throw new Error("browser authorization timed out or was cancelled");
967
+ }
968
+ async prepare(options) {
969
+ const defaults = await this.resolvedDefaults();
970
+ const role = parseRole(options.role);
971
+ const permission = options.permission ?? defaults.policy.permissionCeiling ?? "inspect";
972
+ if (!process.env.TOKENSIZE_ALLOW_SUBSCRIPTION_HARNESSES && defaults.policy.subscriptionHarnesses?.length) {
973
+ process.env.TOKENSIZE_ALLOW_SUBSCRIPTION_HARNESSES = defaults.policy.subscriptionHarnesses.join(",");
974
+ }
975
+ const discovered = await discoverHarnessesCached({ forceRefresh: options.refresh, reason: options.refresh ? "manual" : void 0 });
976
+ const allowanceSnapshot = await allowances(discovered.harnesses, options.refresh);
977
+ const discovery = applyAllowances(discovered.harnesses, allowanceSnapshot);
978
+ const delegationDepth = Number(process.env.TOKENSIZE_DELEGATION_DEPTH ?? 0);
979
+ const body = {
980
+ schemaVersion: AGENT_ROUTER_SCHEMA_VERSION,
981
+ client: { surface: this.options.clientSurface, version: this.options.clientVersion },
982
+ requestId: randomUUID2(),
983
+ installationId: await installationId(),
984
+ routerMode: options.sharePrompt ? "prompt-assisted" : "metadata-only",
985
+ task: featurizeAgentTask({ task: options.task, role, permissionProfile: permission }),
986
+ ...options.sharePrompt ? { prompt: options.task } : {},
987
+ candidates: flattenModels(discovery),
988
+ root: { harness: defaults.rootHarness, active: defaults.rootActive, qualityPrior: defaults.rootQualityPrior, allowance: rootAllowance(defaults.rootHarness, allowanceSnapshot) },
989
+ policy: { objective: options.objective ?? defaults.policy.objective ?? "balanced", maxDelegationDepth: defaults.maxDelegationDepth, delegationDepth, permissionCeiling: permission, wallTimeBudgetMs: options.timeoutMs ?? 9e5 }
990
+ };
991
+ return { body, discovery, cache: { discovery: discovered.cache, allowance: allowanceSnapshot.cache } };
992
+ }
993
+ async route(options) {
994
+ if (!options.task.trim()) throw new Error("task is required");
995
+ const prepared = await this.prepare(options);
996
+ const response = await this.api("/v1/agent-routes", { method: "POST", body: JSON.stringify(prepared.body) });
997
+ const runId = typeof response.runId === "string" ? response.runId : void 0;
998
+ const route = agentRouteResponseSchema.parse(response);
999
+ const receiptId = await saveReceipt(route, options.task, this.options.clientSurface, runId);
1000
+ const { feedbackToken: _feedbackToken, ...safeRoute } = route;
1001
+ return { receiptId, ...runId ? { runId } : {}, route: safeRoute, cache: prepared.cache, harnesses: prepared.discovery.map((item) => summarizeHarness(item)) };
1002
+ }
1003
+ /** List recent Run documents for this tenant. */
1004
+ async runs(options = {}) {
1005
+ return this.api(`/v1/runs?limit=${Math.min(Math.max(options.limit ?? 25, 1), 100)}`);
1006
+ }
1007
+ /** Read one Run document — the authoritative story of a delegation. */
1008
+ async runDocument(runId) {
1009
+ if (!/^[0-9a-f-]{36}$/i.test(runId)) throw new Error("invalid run id");
1010
+ return this.api(`/v1/runs/${runId}`);
1011
+ }
1012
+ /** Append one event to a Run. Execution-class events need the receipt token. */
1013
+ async appendRunEvent(runId, event, options = {}) {
1014
+ const candidate = runEventSchema.parse({ seq: event.seq ?? 0, ...event });
1015
+ return this.api(`/v1/runs/${runId}/events`, {
1016
+ method: "POST",
1017
+ body: JSON.stringify({
1018
+ event: candidate,
1019
+ autoSeq: event.seq === void 0,
1020
+ ...options.receiptToken ? { receiptToken: options.receiptToken } : {}
1021
+ })
1022
+ });
1023
+ }
1024
+ /** The human hand at the terminal: grant a pending approval on a Run. */
1025
+ async approve(runId, kind = "subscription-use") {
1026
+ return this.appendRunEvent(runId, {
1027
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1028
+ actor: "human",
1029
+ type: "approval-granted",
1030
+ data: { kind, grantedBy: "local-cli" }
1031
+ });
1032
+ }
1033
+ /** Cancel a Run from any surface that can authenticate. */
1034
+ async cancel(runId) {
1035
+ return this.appendRunEvent(runId, {
1036
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1037
+ actor: "human",
1038
+ type: "cancelled",
1039
+ data: { by: "human" }
1040
+ });
1041
+ }
1042
+ async execute(options) {
1043
+ const stored = await readReceipt(options.receiptId, options.task);
1044
+ const route = stored.route;
1045
+ if (this.options.clientSurface === "opencode-plugin" && route.plan.permissionProfile !== "inspect") throw new Error("OpenCode execution is inspect-only in this release");
1046
+ if (!route.plan.targetId) throw new Error(`no local target selected (${route.reasonCodes.join(", ")})`);
1047
+ if (route.plan.permissionProfile === "network") throw new Error("network delegation is not supported");
1048
+ const discovered = await discoverHarnessesCached();
1049
+ const snapshot = await allowances(discovered.harnesses);
1050
+ const available = applyAllowances(discovered.harnesses, snapshot);
1051
+ const target = available.flatMap((item) => item.models.map((model2) => ({ item, model: model2 }))).find(({ model: model2 }) => model2.id === route.plan.targetId);
1052
+ if (!target?.item.executable || !target.item.authenticated) throw new Error("selected target is no longer available; local discovery cache refreshed");
1053
+ try {
1054
+ await access3(target.item.executable);
1055
+ } catch {
1056
+ throw new Error("selected harness executable no longer exists; local discovery cache refreshed");
1057
+ }
1058
+ if (!["codex", "claude", "cursor", "opencode"].includes(target.model.harness)) throw new Error(`${target.model.harness} execution is unavailable`);
1059
+ if (["cursor", "opencode"].includes(target.model.harness) && route.plan.permissionProfile !== "inspect") throw new Error(`${target.model.harness} execution is inspect-only`);
1060
+ const cwd = options.cwd ?? process.cwd();
1061
+ const started = Date.now();
1062
+ await this.recordRunEvent(stored, {
1063
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1064
+ actor: "delegate",
1065
+ type: "execution-started",
1066
+ data: { targetId: target.model.id, permissionProfile: route.plan.permissionProfile, worktree: false }
1067
+ });
1068
+ const result = await run(target.item.executable, executionArgs(target.model.harness, target.model.nativeModelId, route.plan.permissionProfile, cwd), {
1069
+ cwd,
1070
+ input: options.task,
1071
+ timeoutMs: route.plan.maxWallMs,
1072
+ env: { ...process.env, TOKENSIZE_ROOT_HARNESS: target.model.harness, TOKENSIZE_ROOT_ACTIVE: "1", TOKENSIZE_DELEGATION_DEPTH: String(Number(process.env.TOKENSIZE_DELEGATION_DEPTH ?? 0) + 1) }
1073
+ });
1074
+ const elapsedMs = Date.now() - started;
1075
+ await this.recordRunEvent(stored, {
1076
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1077
+ actor: "delegate",
1078
+ type: "execution-finished",
1079
+ data: {
1080
+ outcome: result.timedOut ? "timeout" : result.code === 0 ? "success" : "failure",
1081
+ ...result.code === 0 && !result.timedOut ? {} : { failureCode: result.timedOut ? "EXECUTION_TIMEOUT" : "EXECUTION_FAILED" },
1082
+ elapsedMs,
1083
+ usage: { contextTokensEstimated: route.expected.contextTokens, source: "estimated" }
1084
+ }
1085
+ });
1086
+ return { target: target.model.id, ...stored.runId ? { runId: stored.runId } : {}, code: result.code, timedOut: result.timedOut, elapsedMs, stdout: result.stdout.slice(0, MAX_OUTPUT_BYTES), stderr: result.stderr.slice(0, MAX_OUTPUT_BYTES) };
1087
+ }
1088
+ async recordRunEvent(stored, event) {
1089
+ if (!stored.runId) return;
1090
+ try {
1091
+ await this.appendRunEvent(stored.runId, event, { receiptToken: stored.route.feedbackToken });
1092
+ } catch {
1093
+ }
1094
+ }
1095
+ async feedback(options) {
1096
+ const stored = await readReceipt(options.receiptId, options.task);
1097
+ return this.api("/v1/agent-feedback", { method: "POST", body: JSON.stringify({ schemaVersion: AGENT_ROUTER_SCHEMA_VERSION, routeId: stored.route.routeId, feedbackToken: stored.route.feedbackToken, idempotencyKey: randomUUID2(), status: options.modelChoice === "wrong" ? "failed" : "verified", confirmedTargetId: stored.route.plan.targetId, identityConfirmed: true, checksPassed: options.modelChoice === "right" ? 1 : 0, checksFailed: options.modelChoice === "wrong" ? 1 : 0, retries: 0, usage: { contextTokensEstimated: stored.route.expected.contextTokens, source: "estimated" }, timings: { totalMs: 0, routingMs: 0, executionMs: 0, verificationMs: 0 } }) });
1098
+ }
1099
+ };
1100
+ function createTokenSizeClient(options) {
1101
+ return new TokenSizeClient(options);
1102
+ }
1103
+ export {
1104
+ HARNESS_IDS,
1105
+ TokenSizeClient,
1106
+ allowanceReport,
1107
+ allowanceScopesForHarness,
1108
+ allowances,
1109
+ applyAllowances,
1110
+ createTokenSizeClient,
1111
+ discoverHarness,
1112
+ discoverHarnesses,
1113
+ discoverHarnessesCached,
1114
+ executionArgs,
1115
+ flattenModels,
1116
+ home,
1117
+ installationId,
1118
+ isCredentialFreeOpenCodeModel,
1119
+ opencodeModelIds,
1120
+ parseClaudeUsage,
1121
+ readApiKey,
1122
+ readLastRoute,
1123
+ readLocalPolicy,
1124
+ rootAllowance,
1125
+ run,
1126
+ saveApiKey,
1127
+ saveLastRoute,
1128
+ saveLocalPolicy,
1129
+ terminateProcessTree
1130
+ };
1131
+ //# sourceMappingURL=index.js.map