github-router 0.3.316 → 0.3.318
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/{aic-ledger-BS47yb_C.js → aic-ledger-DY03LbBM.js} +2 -2
- package/dist/{aic-ledger-BS47yb_C.js.map → aic-ledger-DY03LbBM.js.map} +1 -1
- package/dist/{attribution-settings-jcsSeVur.js → attribution-settings-CyyOsjPF.js} +39 -28
- package/dist/attribution-settings-CyyOsjPF.js.map +1 -0
- package/dist/{auth-JfxL9q7z.js → auth-Iun7ftd8.js} +3 -3
- package/dist/{auth-JfxL9q7z.js.map → auth-Iun7ftd8.js.map} +1 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{check-usage-DXUEQzt_.js → check-usage-1c-0z3iy.js} +4 -4
- package/dist/{check-usage-DXUEQzt_.js.map → check-usage-1c-0z3iy.js.map} +1 -1
- package/dist/{claude-CJ4Nd2_3.js → claude-UhOOquJW.js} +92 -28
- package/dist/claude-UhOOquJW.js.map +1 -0
- package/dist/{codex-CwuLqNZF.js → codex-CyuVJFDZ.js} +5 -5
- package/dist/{codex-CwuLqNZF.js.map → codex-CyuVJFDZ.js.map} +1 -1
- package/dist/{copilot-discount-IbXR5A28.js → copilot-discount-C3jx9Q-o.js} +2 -2
- package/dist/{copilot-discount-IbXR5A28.js.map → copilot-discount-C3jx9Q-o.js.map} +1 -1
- package/dist/{debug-CA7pg8V9.js → debug-CDyXUPcC.js} +2 -2
- package/dist/{debug-CA7pg8V9.js.map → debug-CDyXUPcC.js.map} +1 -1
- package/dist/engine-D5GNXylB.js +2 -0
- package/dist/{gate-discovery-z04ywOzB.js → gate-discovery-B17pvgE0.js} +5 -5
- package/dist/{gate-discovery-z04ywOzB.js.map → gate-discovery-B17pvgE0.js.map} +1 -1
- package/dist/{get-copilot-usage-BMD_cJNT.js → get-copilot-usage-BWCAzmSc.js} +2 -2
- package/dist/{get-copilot-usage-BMD_cJNT.js.map → get-copilot-usage-BWCAzmSc.js.map} +1 -1
- package/dist/hooks.mjs +124 -117
- package/dist/hooks.sha256 +1 -1
- package/dist/{internal-aic-status-BN0xRLSF.js → internal-aic-status-BiblmImt.js} +13 -6
- package/dist/{internal-aic-status-BN0xRLSF.js.map → internal-aic-status-BiblmImt.js.map} +1 -1
- package/dist/{internal-artifact-open-B3stUu74.js → internal-artifact-open-CA1trJ15.js} +2 -2
- package/dist/{internal-artifact-open-B3stUu74.js.map → internal-artifact-open-CA1trJ15.js.map} +1 -1
- package/dist/{internal-fast-dispatch-guard-CAwYj_Yz.js → internal-fast-dispatch-guard-CZ4BZuAG.js} +2 -2
- package/dist/{internal-fast-dispatch-guard-CAwYj_Yz.js.map → internal-fast-dispatch-guard-CZ4BZuAG.js.map} +1 -1
- package/dist/{internal-fast-dispatch-guard-2QjUlhqO.js → internal-fast-dispatch-guard-D5kozMpo.js} +1 -1
- package/dist/{internal-first-mate-guard-VgnHBp0s.js → internal-first-mate-guard-B_or0eN3.js} +1 -1
- package/dist/{internal-first-mate-guard-B0rbLb0L.js → internal-first-mate-guard-CiFH6X1H.js} +3 -3
- package/dist/{internal-first-mate-guard-B0rbLb0L.js.map → internal-first-mate-guard-CiFH6X1H.js.map} +1 -1
- package/dist/{internal-max-dispatch-guard-Bz7a2Ntf.js → internal-max-dispatch-guard-BGOHRYRt.js} +1 -1
- package/dist/{internal-max-dispatch-guard-BNqQ2wMc.js → internal-max-dispatch-guard-CnBZxo5l.js} +2 -2
- package/dist/{internal-max-dispatch-guard-BNqQ2wMc.js.map → internal-max-dispatch-guard-CnBZxo5l.js.map} +1 -1
- package/dist/{internal-plan-review-QUvNZmMR.js → internal-plan-review-C73snnD8.js} +3 -3
- package/dist/{internal-plan-review-QUvNZmMR.js.map → internal-plan-review-C73snnD8.js.map} +1 -1
- package/dist/{internal-prompt-submit-fj5ccft_.js → internal-prompt-submit-dov4UvBo.js} +4 -4
- package/dist/{internal-prompt-submit-fj5ccft_.js.map → internal-prompt-submit-dov4UvBo.js.map} +1 -1
- package/dist/{internal-session-bind-0Hmak8PR.js → internal-session-bind-C3hrb9rP.js} +2 -2
- package/dist/{internal-session-bind-0Hmak8PR.js.map → internal-session-bind-C3hrb9rP.js.map} +1 -1
- package/dist/{internal-stop-hook-UN1JHtjj.js → internal-stop-hook-DZ6CCP4C.js} +5 -5
- package/dist/{internal-stop-hook-UN1JHtjj.js.map → internal-stop-hook-DZ6CCP4C.js.map} +1 -1
- package/dist/{internal-stop-review-8v2llYDB.js → internal-stop-review-MBVYtrYS.js} +2 -2
- package/dist/{internal-stop-review-8v2llYDB.js.map → internal-stop-review-MBVYtrYS.js.map} +1 -1
- package/dist/{internal-worker-guard-BI4Pl0Ew.js → internal-worker-guard-dxGawAY3.js} +2 -2
- package/dist/{internal-worker-guard-BI4Pl0Ew.js.map → internal-worker-guard-dxGawAY3.js.map} +1 -1
- package/dist/{internal-workspace-header-kg9FoNXK.js → internal-workspace-header-_aEXOz5q.js} +2 -2
- package/dist/{internal-workspace-header-kg9FoNXK.js.map → internal-workspace-header-_aEXOz5q.js.map} +1 -1
- package/dist/{lifecycle-IwotwedG.js → lifecycle-CJB2BwSJ.js} +2 -2
- package/dist/{lifecycle-IwotwedG.js.map → lifecycle-CJB2BwSJ.js.map} +1 -1
- package/dist/lifecycle-D0KsoSqz.js +2 -0
- package/dist/lifecycle-DimEguJO.js +2 -0
- package/dist/{lifecycle-Cx1wVrLb.js → lifecycle-_BE0SnNs.js} +2 -2
- package/dist/{lifecycle-Cx1wVrLb.js.map → lifecycle-_BE0SnNs.js.map} +1 -1
- package/dist/main.js +20 -20
- package/dist/{mcp-workspace-header-BYgWioLH.js → mcp-workspace-header-mXRLE8kg.js} +2 -2
- package/dist/{mcp-workspace-header-BYgWioLH.js.map → mcp-workspace-header-mXRLE8kg.js.map} +1 -1
- package/dist/{models-CVzyU5mJ.js → models-CSFIVxAH.js} +3 -3
- package/dist/{models-CVzyU5mJ.js.map → models-CSFIVxAH.js.map} +1 -1
- package/dist/{orchestration-Dc3mO0G3.js → orchestration-B8kypXcZ.js} +2 -2
- package/dist/{orchestration-Dc3mO0G3.js.map → orchestration-B8kypXcZ.js.map} +1 -1
- package/dist/{paths-CMxlQEfQ.js → paths-CHBAj_t9.js} +36 -4
- package/dist/{paths-CMxlQEfQ.js.map → paths-CHBAj_t9.js.map} +1 -1
- package/dist/paths-eHoOwFzZ.js +2 -0
- package/dist/{peer-mcp-personas-CQ-0Piyr.js → peer-mcp-personas-CRMVuMVF.js} +37 -18
- package/dist/{peer-mcp-personas-CQ-0Piyr.js.map → peer-mcp-personas-CRMVuMVF.js.map} +1 -1
- package/dist/{plan-review-hook-BIMwWkh6.js → plan-review-hook-BMmw7wxR.js} +3 -3
- package/dist/{plan-review-hook-BIMwWkh6.js.map → plan-review-hook-BMmw7wxR.js.map} +1 -1
- package/dist/{prompt-submit-hook-BcFpjHXg.js → prompt-submit-hook-CGgT5m4Q.js} +3 -3
- package/dist/{prompt-submit-hook-BcFpjHXg.js.map → prompt-submit-hook-CGgT5m4Q.js.map} +1 -1
- package/dist/{provision-_R7ZMygj.js → provision-Bn8-NDp7.js} +4 -4
- package/dist/{provision-_R7ZMygj.js.map → provision-Bn8-NDp7.js.map} +1 -1
- package/dist/{self-invocation-cRl4RqfW.js → self-invocation-DahN1gw9.js} +2 -2
- package/dist/{self-invocation-cRl4RqfW.js.map → self-invocation-DahN1gw9.js.map} +1 -1
- package/dist/{serve-DU66K5kv.js → serve-B1yhiPls.js} +12 -12
- package/dist/{serve-DU66K5kv.js.map → serve-B1yhiPls.js.map} +1 -1
- package/dist/{server-setup-iLG8LrfL.js → server-setup-BOJCIEQi.js} +177 -49
- package/dist/server-setup-BOJCIEQi.js.map +1 -0
- package/dist/{start-DQTcm0o_.js → start-DxGJKVd_.js} +3 -3
- package/dist/{start-DQTcm0o_.js.map → start-DxGJKVd_.js.map} +1 -1
- package/dist/{stop-gate-hook-BUCxa5Kc.js → stop-gate-hook-D7N759TG.js} +72 -4
- package/dist/{stop-gate-hook-BUCxa5Kc.js.map → stop-gate-hook-D7N759TG.js.map} +1 -1
- package/dist/{stop-gate-policy--bpyxxXU.js → stop-gate-policy-C0gt04R0.js} +2 -2
- package/dist/{stop-gate-policy--bpyxxXU.js.map → stop-gate-policy-C0gt04R0.js.map} +1 -1
- package/dist/{token-C1gthnbV.js → token-86uk6y4P.js} +2 -2
- package/dist/{token-C1gthnbV.js.map → token-86uk6y4P.js.map} +1 -1
- package/dist/{worker-dispatch-DMIdrlsF.js → worker-dispatch-B-OA7tvU.js} +2 -2
- package/dist/{worker-dispatch-DMIdrlsF.js.map → worker-dispatch-B-OA7tvU.js.map} +1 -1
- package/package.json +1 -1
- package/dist/attribution-settings-jcsSeVur.js.map +0 -1
- package/dist/claude-CJ4Nd2_3.js.map +0 -1
- package/dist/engine-DwrxceOS.js +0 -2
- package/dist/lifecycle-BpqAyaAj.js +0 -2
- package/dist/lifecycle-CL2WiyeQ.js +0 -2
- package/dist/paths-BXXCVFvA.js +0 -2
- package/dist/server-setup-iLG8LrfL.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as PATHS } from "./paths-
|
|
1
|
+
import { t as PATHS } from "./paths-CHBAj_t9.js";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs$1 from "node:fs/promises";
|
|
@@ -295,4 +295,4 @@ async function isPidAlive(pid) {
|
|
|
295
295
|
//#endregion
|
|
296
296
|
export { formatAicExitSummary as a, recordAic as c, extractCopilotUsage as d, formatAic as f, extractAndRecordAic as i, removeAicLedgerFile as l, aicSnapshot as n, formatAicStatus as o, nanoAiuToCredits as p, aicTotalCredits as r, readAicSnapshotFile as s, aicLedgerPath as t, sweepStaleAicLedgerFiles as u };
|
|
297
297
|
|
|
298
|
-
//# sourceMappingURL=aic-ledger-
|
|
298
|
+
//# sourceMappingURL=aic-ledger-DY03LbBM.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aic-ledger-BS47yb_C.js","names":["fsp"],"sources":["../src/lib/aic-usage.ts","../src/lib/aic-ledger.ts"],"sourcesContent":["/**\n * AIC (AI Credits) extraction from upstream Copilot `copilot_usage` fields.\n *\n * Verified live (2026-09-14) on all three native endpoints: `/responses`\n * (non-stream top-level + terminal `response.completed` event), `/chat/completions`\n * (non-stream top-level), and `/v1/messages` (non-stream top-level + terminal\n * `message_delta` event). No special request header is required — the field\n * arrives with the proxy's stock headers.\n *\n * Shape:\n * ```json\n * \"copilot_usage\": {\n * \"token_details\": [\n * {\"batch_size\":1000000,\"cost_per_batch\":20000000000,\n * \"model\":\"gpt-5.6-luna\",\"token_count\":11,\"token_type\":\"input\"},\n * ...\n * ],\n * \"total_nano_aiu\": 820000\n * }\n * ```\n * Credits = `total_nano_aiu / 1e9` (SI nano prefix — the same convention the\n * Copilot SDK docs use; treat GitHub billing docs as source of truth before\n * surfacing currency equivalents). Per-type cost reconstructs as\n * `token_count × cost_per_batch / batch_size / 1e9` (verified arithmetically\n * against `total_nano_aiu` on live captures).\n *\n * All parsing is total and never throws: malformed entries are skipped, and a\n * missing/invalid field yields `undefined` so callers fall back cleanly.\n */\n\n/** One entry of `copilot_usage.token_details`. */\nexport interface CopilotTokenDetail {\n model?: string\n tokenType?: string\n tokenCount: number\n batchSize: number\n costPerBatch: number\n}\n\n/** Parsed `copilot_usage` object. */\nexport interface CopilotUsage {\n totalNanoAiu: number\n tokenDetails: Array<CopilotTokenDetail>\n}\n\n/** Nano-AIU per AI credit (SI prefix, per Copilot SDK docs). */\nexport const NANO_AIU_PER_CREDIT = 1_000_000_000\n\nfunction nonNegativeNumber(value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n return 0\n }\n return value\n}\n\nfunction parseTokenDetail(value: unknown): CopilotTokenDetail | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined\n }\n const record = value as Record<string, unknown>\n const batchSize = nonNegativeNumber(record.batch_size)\n if (batchSize <= 0) return undefined\n return {\n ...(typeof record.model === \"string\" && record.model.length > 0\n ? { model: record.model }\n : {}),\n ...(typeof record.token_type === \"string\" && record.token_type.length > 0\n ? { tokenType: record.token_type }\n : {}),\n tokenCount: Math.floor(nonNegativeNumber(record.token_count)),\n batchSize: Math.floor(batchSize),\n costPerBatch: nonNegativeNumber(record.cost_per_batch),\n }\n}\n\n/**\n * Extract a validated `CopilotUsage` from an unknown value (typically\n * `responseBody.copilot_usage` or an SSE event's `copilot_usage`).\n * Returns `undefined` when absent or invalid — never throws.\n */\nexport function extractCopilotUsage(value: unknown): CopilotUsage | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined\n }\n const record = value as Record<string, unknown>\n if (\n typeof record.total_nano_aiu !== \"number\"\n || !Number.isFinite(record.total_nano_aiu)\n || record.total_nano_aiu < 0\n ) {\n return undefined\n }\n const details: Array<CopilotTokenDetail> = []\n if (Array.isArray(record.token_details)) {\n for (const entry of record.token_details) {\n const parsed = parseTokenDetail(entry)\n if (parsed) details.push(parsed)\n }\n }\n return {\n totalNanoAiu: Math.floor(record.total_nano_aiu),\n tokenDetails: details,\n }\n}\n\n/** Convert nano-AIU to AI credits. */\nexport function nanoAiuToCredits(nanoAiu: number): number {\n if (!Number.isFinite(nanoAiu) || nanoAiu <= 0) return 0\n return nanoAiu / NANO_AIU_PER_CREDIT\n}\n\n/** Credits for one token-detail entry. */\nexport function creditsForDetail(detail: CopilotTokenDetail): number {\n return nanoAiuToCredits(\n (detail.tokenCount * detail.costPerBatch) / detail.batchSize,\n )\n}\n\n/**\n * Compact display: `12.42` for totals ≥ 0.01, more precision below.\n * Always a plain number string — callers add units/context.\n */\nexport function formatAic(credits: number): string {\n if (!Number.isFinite(credits) || credits <= 0) return \"0\"\n if (credits >= 100) return credits.toFixed(1)\n if (credits >= 1) return credits.toFixed(2)\n if (credits >= 0.01) return credits.toFixed(3)\n return credits.toPrecision(2)\n}\n","/**\n * Per-launch AIC (AI Credits) ledger.\n *\n * Every route handler records upstream `copilot_usage` here as a side effect\n * (bytes pass through untouched). The totals feed two consumers:\n *\n * 1. `internal-aic-status` — runs as a SEPARATE process on every status-line\n * tick, so it cannot read in-process memory. Each `record()` therefore\n * also persists an atomic snapshot file; the ledger path is handed to the\n * spawned child via `GH_ROUTER_AIC_LEDGER` and baked into the statusLine\n * command.\n * 2. The `claude` exit summary — reads the in-process snapshot.\n *\n * Keyed per proxy process (one file per PID under `<APP_DIR>/aic-ledger/`),\n * so concurrent launches never mix. Snapshot writes are best-effort and never\n * throw into the request path. Stale files (dead PID, >24h) are swept at\n * startup of the next record and on shutdown.\n */\n\nimport fs from \"node:fs\"\nimport fsp from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport { PATHS } from \"~/lib/paths\"\n\nimport {\n extractCopilotUsage,\n formatAic,\n nanoAiuToCredits,\n type CopilotUsage,\n} from \"./aic-usage\"\n\nexport interface AicModelEntry {\n nanoAiu: number\n requests: number\n}\n\n/** Token counts per model per upstream `token_type`, for the cost table. */\nexport interface AicModelTokens {\n /** Upstream `input` type: fresh (uncached) input tokens. */\n input: number\n /** Upstream `cache_read` type. */\n cache_read: number\n /** Upstream `cache_write` type. */\n cache_write: number\n /** Upstream `output` type. */\n output: number\n /** Any other/missing `token_type`: never dropped silently, priced at input. */\n other: number\n}\n\nexport interface AicSnapshot {\n totalNanoAiu: number\n requests: number\n perModel: Record<string, AicModelEntry>\n /** Reconstructed nano-AIU per token_type (input/cache_read/cache_write/output). */\n perTokenType: Record<string, number>\n /** Raw token counts per model per type (exit cost table + rate math). */\n tokensByModel: Record<string, AicModelTokens>\n}\n\nfunction emptyModelTokens(): AicModelTokens {\n return { input: 0, cache_read: 0, cache_write: 0, output: 0, other: 0 }\n}\n\nfunction emptySnapshot(): AicSnapshot {\n return {\n totalNanoAiu: 0,\n requests: 0,\n perModel: {},\n perTokenType: {},\n tokensByModel: {},\n }\n}\n\nlet ledger: AicSnapshot = emptySnapshot()\n\n/** @internal — reset module state between test cases. */\nexport function __resetAicLedgerForTests(): void {\n ledger = emptySnapshot()\n}\n\nfunction ledgerDir(): string {\n return (\n process.env.GH_ROUTER_AIC_LEDGER_DIR ?? path.join(PATHS.APP_DIR, \"aic-ledger\")\n )\n}\n\n/**\n * Snapshot file for this proxy process. Overridable via `GH_ROUTER_AIC_LEDGER`\n * (used by tests and by the spawned child's env).\n */\nexport function aicLedgerPath(): string {\n const override = process.env.GH_ROUTER_AIC_LEDGER\n if (override && override.length > 0) return override\n return path.join(ledgerDir(), `${process.pid}.json`)\n}\n\n/** Best-effort persist; never throws. */\nfunction persistBestEffort(): void {\n try {\n const file = aicLedgerPath()\n fs.mkdirSync(path.dirname(file), { recursive: true })\n const tmp = `${file}.${process.pid}.tmp`\n fs.writeFileSync(tmp, `${JSON.stringify(ledger)}\\n`, { mode: 0o600 })\n fs.renameSync(tmp, file)\n } catch {\n // Telemetry must never break a request.\n }\n}\n\n/**\n * Record one upstream `copilot_usage` reading. No-op on undefined.\n * `model` is the resolved (Copilot-side) model id when known.\n */\nexport function recordAic(\n model: string | undefined,\n usage: CopilotUsage | undefined,\n): void {\n if (!usage) return\n ledger.totalNanoAiu += usage.totalNanoAiu\n ledger.requests += 1\n const key = model && model.length > 0 ? model : \"unknown\"\n const entry = ledger.perModel[key] ?? { nanoAiu: 0, requests: 0 }\n entry.nanoAiu += usage.totalNanoAiu\n entry.requests += 1\n ledger.perModel[key] = entry\n ledger.tokensByModel[key] ??= emptyModelTokens()\n for (const detail of usage.tokenDetails) {\n const type = detail.tokenType ?? \"unknown\"\n // Reconstruct nano-AIU per token type from the priced detail.\n const nano = Math.round(\n (detail.tokenCount * detail.costPerBatch) / detail.batchSize,\n )\n ledger.perTokenType[type] = (ledger.perTokenType[type] ?? 0) + nano\n // Retain raw counts per model per type for the exit cost table. The\n // hook only reads them; pricing still flows exclusively through nano.\n const counts = ledger.tokensByModel[key] ?? emptyModelTokens()\n const bucket = tokenBucket(type)\n counts[bucket] += detail.tokenCount\n ledger.tokensByModel[key] = counts\n }\n persistBestEffort()\n}\n\n/**\n * Map an upstream `token_type` to its count bucket. Unknown/missing types\n * land in `other` (priced at the model's input factor downstream) rather\n * than being dropped — the observed universe is the four known types, this\n * is the just-in-case bucket.\n */\nfunction tokenBucket(type: string): keyof AicModelTokens {\n switch (type) {\n case \"input\":\n return \"input\"\n case \"cache_read\":\n return \"cache_read\"\n case \"cache_write\":\n return \"cache_write\"\n case \"output\":\n return \"output\"\n default:\n return \"other\"\n }\n}\n\n/**\n * Extract `copilot_usage` from a response object or SSE event-data object,\n * record it under `model`, and return its nano-AIU for `logRequest`.\n * Returns `undefined` when absent/invalid. Never throws.\n */\nexport function extractAndRecordAic(\n model: string | undefined,\n container: unknown,\n): number | undefined {\n if (!container || typeof container !== \"object\" || Array.isArray(container)) {\n return undefined\n }\n const usage = extractCopilotUsage(\n (container as Record<string, unknown>).copilot_usage,\n )\n if (!usage) return undefined\n recordAic(model, usage)\n return usage.totalNanoAiu\n}\n\n/** In-process snapshot (defensive copy). */\nexport function aicSnapshot(): AicSnapshot {\n return {\n totalNanoAiu: ledger.totalNanoAiu,\n requests: ledger.requests,\n perModel: Object.fromEntries(\n Object.entries(ledger.perModel).map(([k, v]) => [k, { ...v }]),\n ),\n perTokenType: { ...ledger.perTokenType },\n tokensByModel: Object.fromEntries(\n Object.entries(ledger.tokensByModel).map(([k, v]) => [k, { ...v }]),\n ),\n }\n}\n\n/**\n * Read a persisted snapshot file (the status-hook path). Returns undefined\n * when missing/unreadable — the hook then prints nothing.\n */\nexport function readAicSnapshotFile(file: string): AicSnapshot | undefined {\n try {\n const raw = fs.readFileSync(file, \"utf8\")\n const parsed: unknown = JSON.parse(raw)\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return undefined\n }\n const record = parsed as Record<string, unknown>\n if (\n typeof record.totalNanoAiu !== \"number\"\n || typeof record.requests !== \"number\"\n ) {\n return undefined\n }\n return {\n totalNanoAiu: Math.max(0, Math.floor(record.totalNanoAiu)),\n requests: Math.max(0, Math.floor(record.requests)),\n perModel:\n record.perModel && typeof record.perModel === \"object\"\n ? (record.perModel as AicSnapshot[\"perModel\"])\n : {},\n perTokenType:\n record.perTokenType && typeof record.perTokenType === \"object\"\n ? (record.perTokenType as AicSnapshot[\"perTokenType\"])\n : {},\n // Pre-tokens files (written before this field existed) read as empty\n // rather than failing the hook.\n tokensByModel: sanitizeModelTokens(record.tokensByModel),\n }\n } catch {\n return undefined\n }\n}\n\n/** Best-effort sanitize of persisted per-model token counts; `{}` when absent. */\nfunction sanitizeModelTokens(value: unknown): Record<string, AicModelTokens> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return {}\n const out: Record<string, AicModelTokens> = {}\n for (const [model, counts] of Object.entries(value as Record<string, unknown>)) {\n if (!counts || typeof counts !== \"object\" || Array.isArray(counts)) continue\n const entry = counts as Record<string, unknown>\n const clean = emptyModelTokens()\n let ok = true\n for (const field of [\"input\", \"cache_read\", \"cache_write\", \"output\", \"other\"] as const) {\n const n = entry[field]\n if (n === undefined) continue\n if (typeof n !== \"number\" || !Number.isFinite(n) || n < 0) {\n ok = false\n break\n }\n clean[field] = Math.floor(n)\n }\n if (ok) out[model] = clean\n }\n return out\n}\n\n/** Total credits for a snapshot. */\nexport function aicTotalCredits(snapshot: AicSnapshot): number {\n return nanoAiuToCredits(snapshot.totalNanoAiu)\n}\n\n/** One-line status fragment: `[AIC 12.42]`. Empty string when nothing recorded. */\nexport function formatAicStatus(snapshot: AicSnapshot): string {\n if (snapshot.requests === 0 || snapshot.totalNanoAiu <= 0) return \"\"\n return `[AIC ${formatAic(aicTotalCredits(snapshot))}]`\n}\n\n/**\n * Multi-line exit summary. `verbose` adds per-model and per-type breakdowns\n * (only from upstream-measured values — never estimates).\n */\nexport function formatAicExitSummary(\n snapshot: AicSnapshot,\n opts: { verbose?: boolean } = {},\n): string {\n const total = aicTotalCredits(snapshot)\n const reqWord = snapshot.requests === 1 ? \"request\" : \"requests\"\n const lines = [\n `AIC consumed this session: ${formatAic(total)} credits (~$${(total / 100).toFixed(2)}) across ${snapshot.requests} ${reqWord}`,\n ]\n if (opts.verbose) {\n const models = Object.entries(snapshot.perModel).sort(\n (a, b) => b[1].nanoAiu - a[1].nanoAiu,\n )\n for (const [model, entry] of models) {\n const rWord = entry.requests === 1 ? \"request\" : \"requests\"\n lines.push(\n ` ${model}: ${formatAic(nanoAiuToCredits(entry.nanoAiu))} credits across ${entry.requests} ${rWord}`,\n )\n }\n const types = Object.entries(snapshot.perTokenType).sort(\n (a, b) => b[1] - a[1],\n )\n if (types.length > 0) {\n lines.push(\n ` by type: ${types.map(([t, n]) => `${t}=${formatAic(nanoAiuToCredits(n))}`).join(\" \")}`,\n )\n }\n }\n return lines.join(\"\\n\")\n}\n\n/** Remove this process's snapshot file; never throws. */\nexport async function removeAicLedgerFile(): Promise<void> {\n try {\n await fsp.rm(aicLedgerPath(), { force: true })\n } catch {\n // Best-effort cleanup.\n }\n}\n\n/** Sweep stale ledger files (dead PID or >24h old); never throws. */\nexport async function sweepStaleAicLedgerFiles(): Promise<void> {\n try {\n const dir = ledgerDir()\n const entries = await fsp.readdir(dir).catch(() => [] as Array<string>)\n const now = Date.now()\n await Promise.all(\n entries.map(async (entry) => {\n if (!/^\\d+\\.json$/.test(entry)) return\n const file = path.join(dir, entry)\n try {\n const stat = await fsp.stat(file)\n const pid = Number(entry.slice(0, -\".json\".length))\n const alive = await isPidAlive(pid)\n if (!alive || now - stat.mtimeMs > 24 * 60 * 60 * 1000) {\n await fsp.rm(file, { force: true })\n }\n } catch {\n // Best-effort per file.\n }\n }),\n )\n } catch {\n // Best-effort sweep.\n }\n}\n\nasync function isPidAlive(pid: number): Promise<boolean> {\n if (!Number.isInteger(pid) || pid <= 0) return false\n try {\n process.kill(pid, 0)\n return true\n } catch {\n return false\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aic-ledger-DY03LbBM.js","names":["fsp"],"sources":["../src/lib/aic-usage.ts","../src/lib/aic-ledger.ts"],"sourcesContent":["/**\n * AIC (AI Credits) extraction from upstream Copilot `copilot_usage` fields.\n *\n * Verified live (2026-09-14) on all three native endpoints: `/responses`\n * (non-stream top-level + terminal `response.completed` event), `/chat/completions`\n * (non-stream top-level), and `/v1/messages` (non-stream top-level + terminal\n * `message_delta` event). No special request header is required — the field\n * arrives with the proxy's stock headers.\n *\n * Shape:\n * ```json\n * \"copilot_usage\": {\n * \"token_details\": [\n * {\"batch_size\":1000000,\"cost_per_batch\":20000000000,\n * \"model\":\"gpt-5.6-luna\",\"token_count\":11,\"token_type\":\"input\"},\n * ...\n * ],\n * \"total_nano_aiu\": 820000\n * }\n * ```\n * Credits = `total_nano_aiu / 1e9` (SI nano prefix — the same convention the\n * Copilot SDK docs use; treat GitHub billing docs as source of truth before\n * surfacing currency equivalents). Per-type cost reconstructs as\n * `token_count × cost_per_batch / batch_size / 1e9` (verified arithmetically\n * against `total_nano_aiu` on live captures).\n *\n * All parsing is total and never throws: malformed entries are skipped, and a\n * missing/invalid field yields `undefined` so callers fall back cleanly.\n */\n\n/** One entry of `copilot_usage.token_details`. */\nexport interface CopilotTokenDetail {\n model?: string\n tokenType?: string\n tokenCount: number\n batchSize: number\n costPerBatch: number\n}\n\n/** Parsed `copilot_usage` object. */\nexport interface CopilotUsage {\n totalNanoAiu: number\n tokenDetails: Array<CopilotTokenDetail>\n}\n\n/** Nano-AIU per AI credit (SI prefix, per Copilot SDK docs). */\nexport const NANO_AIU_PER_CREDIT = 1_000_000_000\n\nfunction nonNegativeNumber(value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n return 0\n }\n return value\n}\n\nfunction parseTokenDetail(value: unknown): CopilotTokenDetail | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined\n }\n const record = value as Record<string, unknown>\n const batchSize = nonNegativeNumber(record.batch_size)\n if (batchSize <= 0) return undefined\n return {\n ...(typeof record.model === \"string\" && record.model.length > 0\n ? { model: record.model }\n : {}),\n ...(typeof record.token_type === \"string\" && record.token_type.length > 0\n ? { tokenType: record.token_type }\n : {}),\n tokenCount: Math.floor(nonNegativeNumber(record.token_count)),\n batchSize: Math.floor(batchSize),\n costPerBatch: nonNegativeNumber(record.cost_per_batch),\n }\n}\n\n/**\n * Extract a validated `CopilotUsage` from an unknown value (typically\n * `responseBody.copilot_usage` or an SSE event's `copilot_usage`).\n * Returns `undefined` when absent or invalid — never throws.\n */\nexport function extractCopilotUsage(value: unknown): CopilotUsage | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined\n }\n const record = value as Record<string, unknown>\n if (\n typeof record.total_nano_aiu !== \"number\"\n || !Number.isFinite(record.total_nano_aiu)\n || record.total_nano_aiu < 0\n ) {\n return undefined\n }\n const details: Array<CopilotTokenDetail> = []\n if (Array.isArray(record.token_details)) {\n for (const entry of record.token_details) {\n const parsed = parseTokenDetail(entry)\n if (parsed) details.push(parsed)\n }\n }\n return {\n totalNanoAiu: Math.floor(record.total_nano_aiu),\n tokenDetails: details,\n }\n}\n\n/** Convert nano-AIU to AI credits. */\nexport function nanoAiuToCredits(nanoAiu: number): number {\n if (!Number.isFinite(nanoAiu) || nanoAiu <= 0) return 0\n return nanoAiu / NANO_AIU_PER_CREDIT\n}\n\n/** Credits for one token-detail entry. */\nexport function creditsForDetail(detail: CopilotTokenDetail): number {\n return nanoAiuToCredits(\n (detail.tokenCount * detail.costPerBatch) / detail.batchSize,\n )\n}\n\n/**\n * Compact display: `12.42` for totals ≥ 0.01, more precision below.\n * Always a plain number string — callers add units/context.\n */\nexport function formatAic(credits: number): string {\n if (!Number.isFinite(credits) || credits <= 0) return \"0\"\n if (credits >= 100) return credits.toFixed(1)\n if (credits >= 1) return credits.toFixed(2)\n if (credits >= 0.01) return credits.toFixed(3)\n return credits.toPrecision(2)\n}\n","/**\n * Per-launch AIC (AI Credits) ledger.\n *\n * Every route handler records upstream `copilot_usage` here as a side effect\n * (bytes pass through untouched). The totals feed two consumers:\n *\n * 1. `internal-aic-status` — runs as a SEPARATE process on every status-line\n * tick, so it cannot read in-process memory. Each `record()` therefore\n * also persists an atomic snapshot file; the ledger path is handed to the\n * spawned child via `GH_ROUTER_AIC_LEDGER` and baked into the statusLine\n * command.\n * 2. The `claude` exit summary — reads the in-process snapshot.\n *\n * Keyed per proxy process (one file per PID under `<APP_DIR>/aic-ledger/`),\n * so concurrent launches never mix. Snapshot writes are best-effort and never\n * throw into the request path. Stale files (dead PID, >24h) are swept at\n * startup of the next record and on shutdown.\n */\n\nimport fs from \"node:fs\"\nimport fsp from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport { PATHS } from \"~/lib/paths\"\n\nimport {\n extractCopilotUsage,\n formatAic,\n nanoAiuToCredits,\n type CopilotUsage,\n} from \"./aic-usage\"\n\nexport interface AicModelEntry {\n nanoAiu: number\n requests: number\n}\n\n/** Token counts per model per upstream `token_type`, for the cost table. */\nexport interface AicModelTokens {\n /** Upstream `input` type: fresh (uncached) input tokens. */\n input: number\n /** Upstream `cache_read` type. */\n cache_read: number\n /** Upstream `cache_write` type. */\n cache_write: number\n /** Upstream `output` type. */\n output: number\n /** Any other/missing `token_type`: never dropped silently, priced at input. */\n other: number\n}\n\nexport interface AicSnapshot {\n totalNanoAiu: number\n requests: number\n perModel: Record<string, AicModelEntry>\n /** Reconstructed nano-AIU per token_type (input/cache_read/cache_write/output). */\n perTokenType: Record<string, number>\n /** Raw token counts per model per type (exit cost table + rate math). */\n tokensByModel: Record<string, AicModelTokens>\n}\n\nfunction emptyModelTokens(): AicModelTokens {\n return { input: 0, cache_read: 0, cache_write: 0, output: 0, other: 0 }\n}\n\nfunction emptySnapshot(): AicSnapshot {\n return {\n totalNanoAiu: 0,\n requests: 0,\n perModel: {},\n perTokenType: {},\n tokensByModel: {},\n }\n}\n\nlet ledger: AicSnapshot = emptySnapshot()\n\n/** @internal — reset module state between test cases. */\nexport function __resetAicLedgerForTests(): void {\n ledger = emptySnapshot()\n}\n\nfunction ledgerDir(): string {\n return (\n process.env.GH_ROUTER_AIC_LEDGER_DIR ?? path.join(PATHS.APP_DIR, \"aic-ledger\")\n )\n}\n\n/**\n * Snapshot file for this proxy process. Overridable via `GH_ROUTER_AIC_LEDGER`\n * (used by tests and by the spawned child's env).\n */\nexport function aicLedgerPath(): string {\n const override = process.env.GH_ROUTER_AIC_LEDGER\n if (override && override.length > 0) return override\n return path.join(ledgerDir(), `${process.pid}.json`)\n}\n\n/** Best-effort persist; never throws. */\nfunction persistBestEffort(): void {\n try {\n const file = aicLedgerPath()\n fs.mkdirSync(path.dirname(file), { recursive: true })\n const tmp = `${file}.${process.pid}.tmp`\n fs.writeFileSync(tmp, `${JSON.stringify(ledger)}\\n`, { mode: 0o600 })\n fs.renameSync(tmp, file)\n } catch {\n // Telemetry must never break a request.\n }\n}\n\n/**\n * Record one upstream `copilot_usage` reading. No-op on undefined.\n * `model` is the resolved (Copilot-side) model id when known.\n */\nexport function recordAic(\n model: string | undefined,\n usage: CopilotUsage | undefined,\n): void {\n if (!usage) return\n ledger.totalNanoAiu += usage.totalNanoAiu\n ledger.requests += 1\n const key = model && model.length > 0 ? model : \"unknown\"\n const entry = ledger.perModel[key] ?? { nanoAiu: 0, requests: 0 }\n entry.nanoAiu += usage.totalNanoAiu\n entry.requests += 1\n ledger.perModel[key] = entry\n ledger.tokensByModel[key] ??= emptyModelTokens()\n for (const detail of usage.tokenDetails) {\n const type = detail.tokenType ?? \"unknown\"\n // Reconstruct nano-AIU per token type from the priced detail.\n const nano = Math.round(\n (detail.tokenCount * detail.costPerBatch) / detail.batchSize,\n )\n ledger.perTokenType[type] = (ledger.perTokenType[type] ?? 0) + nano\n // Retain raw counts per model per type for the exit cost table. The\n // hook only reads them; pricing still flows exclusively through nano.\n const counts = ledger.tokensByModel[key] ?? emptyModelTokens()\n const bucket = tokenBucket(type)\n counts[bucket] += detail.tokenCount\n ledger.tokensByModel[key] = counts\n }\n persistBestEffort()\n}\n\n/**\n * Map an upstream `token_type` to its count bucket. Unknown/missing types\n * land in `other` (priced at the model's input factor downstream) rather\n * than being dropped — the observed universe is the four known types, this\n * is the just-in-case bucket.\n */\nfunction tokenBucket(type: string): keyof AicModelTokens {\n switch (type) {\n case \"input\":\n return \"input\"\n case \"cache_read\":\n return \"cache_read\"\n case \"cache_write\":\n return \"cache_write\"\n case \"output\":\n return \"output\"\n default:\n return \"other\"\n }\n}\n\n/**\n * Extract `copilot_usage` from a response object or SSE event-data object,\n * record it under `model`, and return its nano-AIU for `logRequest`.\n * Returns `undefined` when absent/invalid. Never throws.\n */\nexport function extractAndRecordAic(\n model: string | undefined,\n container: unknown,\n): number | undefined {\n if (!container || typeof container !== \"object\" || Array.isArray(container)) {\n return undefined\n }\n const usage = extractCopilotUsage(\n (container as Record<string, unknown>).copilot_usage,\n )\n if (!usage) return undefined\n recordAic(model, usage)\n return usage.totalNanoAiu\n}\n\n/** In-process snapshot (defensive copy). */\nexport function aicSnapshot(): AicSnapshot {\n return {\n totalNanoAiu: ledger.totalNanoAiu,\n requests: ledger.requests,\n perModel: Object.fromEntries(\n Object.entries(ledger.perModel).map(([k, v]) => [k, { ...v }]),\n ),\n perTokenType: { ...ledger.perTokenType },\n tokensByModel: Object.fromEntries(\n Object.entries(ledger.tokensByModel).map(([k, v]) => [k, { ...v }]),\n ),\n }\n}\n\n/**\n * Read a persisted snapshot file (the status-hook path). Returns undefined\n * when missing/unreadable — the hook then prints nothing.\n */\nexport function readAicSnapshotFile(file: string): AicSnapshot | undefined {\n try {\n const raw = fs.readFileSync(file, \"utf8\")\n const parsed: unknown = JSON.parse(raw)\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return undefined\n }\n const record = parsed as Record<string, unknown>\n if (\n typeof record.totalNanoAiu !== \"number\"\n || typeof record.requests !== \"number\"\n ) {\n return undefined\n }\n return {\n totalNanoAiu: Math.max(0, Math.floor(record.totalNanoAiu)),\n requests: Math.max(0, Math.floor(record.requests)),\n perModel:\n record.perModel && typeof record.perModel === \"object\"\n ? (record.perModel as AicSnapshot[\"perModel\"])\n : {},\n perTokenType:\n record.perTokenType && typeof record.perTokenType === \"object\"\n ? (record.perTokenType as AicSnapshot[\"perTokenType\"])\n : {},\n // Pre-tokens files (written before this field existed) read as empty\n // rather than failing the hook.\n tokensByModel: sanitizeModelTokens(record.tokensByModel),\n }\n } catch {\n return undefined\n }\n}\n\n/** Best-effort sanitize of persisted per-model token counts; `{}` when absent. */\nfunction sanitizeModelTokens(value: unknown): Record<string, AicModelTokens> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return {}\n const out: Record<string, AicModelTokens> = {}\n for (const [model, counts] of Object.entries(value as Record<string, unknown>)) {\n if (!counts || typeof counts !== \"object\" || Array.isArray(counts)) continue\n const entry = counts as Record<string, unknown>\n const clean = emptyModelTokens()\n let ok = true\n for (const field of [\"input\", \"cache_read\", \"cache_write\", \"output\", \"other\"] as const) {\n const n = entry[field]\n if (n === undefined) continue\n if (typeof n !== \"number\" || !Number.isFinite(n) || n < 0) {\n ok = false\n break\n }\n clean[field] = Math.floor(n)\n }\n if (ok) out[model] = clean\n }\n return out\n}\n\n/** Total credits for a snapshot. */\nexport function aicTotalCredits(snapshot: AicSnapshot): number {\n return nanoAiuToCredits(snapshot.totalNanoAiu)\n}\n\n/** One-line status fragment: `[AIC 12.42]`. Empty string when nothing recorded. */\nexport function formatAicStatus(snapshot: AicSnapshot): string {\n if (snapshot.requests === 0 || snapshot.totalNanoAiu <= 0) return \"\"\n return `[AIC ${formatAic(aicTotalCredits(snapshot))}]`\n}\n\n/**\n * Multi-line exit summary. `verbose` adds per-model and per-type breakdowns\n * (only from upstream-measured values — never estimates).\n */\nexport function formatAicExitSummary(\n snapshot: AicSnapshot,\n opts: { verbose?: boolean } = {},\n): string {\n const total = aicTotalCredits(snapshot)\n const reqWord = snapshot.requests === 1 ? \"request\" : \"requests\"\n const lines = [\n `AIC consumed this session: ${formatAic(total)} credits (~$${(total / 100).toFixed(2)}) across ${snapshot.requests} ${reqWord}`,\n ]\n if (opts.verbose) {\n const models = Object.entries(snapshot.perModel).sort(\n (a, b) => b[1].nanoAiu - a[1].nanoAiu,\n )\n for (const [model, entry] of models) {\n const rWord = entry.requests === 1 ? \"request\" : \"requests\"\n lines.push(\n ` ${model}: ${formatAic(nanoAiuToCredits(entry.nanoAiu))} credits across ${entry.requests} ${rWord}`,\n )\n }\n const types = Object.entries(snapshot.perTokenType).sort(\n (a, b) => b[1] - a[1],\n )\n if (types.length > 0) {\n lines.push(\n ` by type: ${types.map(([t, n]) => `${t}=${formatAic(nanoAiuToCredits(n))}`).join(\" \")}`,\n )\n }\n }\n return lines.join(\"\\n\")\n}\n\n/** Remove this process's snapshot file; never throws. */\nexport async function removeAicLedgerFile(): Promise<void> {\n try {\n await fsp.rm(aicLedgerPath(), { force: true })\n } catch {\n // Best-effort cleanup.\n }\n}\n\n/** Sweep stale ledger files (dead PID or >24h old); never throws. */\nexport async function sweepStaleAicLedgerFiles(): Promise<void> {\n try {\n const dir = ledgerDir()\n const entries = await fsp.readdir(dir).catch(() => [] as Array<string>)\n const now = Date.now()\n await Promise.all(\n entries.map(async (entry) => {\n if (!/^\\d+\\.json$/.test(entry)) return\n const file = path.join(dir, entry)\n try {\n const stat = await fsp.stat(file)\n const pid = Number(entry.slice(0, -\".json\".length))\n const alive = await isPidAlive(pid)\n if (!alive || now - stat.mtimeMs > 24 * 60 * 60 * 1000) {\n await fsp.rm(file, { force: true })\n }\n } catch {\n // Best-effort per file.\n }\n }),\n )\n } catch {\n // Best-effort sweep.\n }\n}\n\nasync function isPidAlive(pid: number): Promise<boolean> {\n if (!Number.isInteger(pid) || pid <= 0) return false\n try {\n process.kill(pid, 0)\n return true\n } catch {\n return false\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { An as oneMContextDisabled,
|
|
2
|
-
import { a as isUnderClaudeConfigMirror,
|
|
3
|
-
import {
|
|
1
|
+
import { An as oneMContextDisabled, Fn as CHEAPEST_PROFILE_NATIVE_AGENT_NAMES, Hn as CHEAP_PROFILE_NATIVE_EFFORTS, In as CHEAPEST_PROFILE_NATIVE_EFFORTS, Vn as CHEAP_PROFILE_NATIVE_AGENT_NAMES, a as buildAgentPrompt, jn as withOneMSuffix, l as maxPersonasFor, ln as CONDENSED_OPERATING_SEQUENCE, n as MCP_GROUPS, t as GROUP_META, u as personasFor, un as DEFINITION_OF_GREATNESS } from "./peer-mcp-personas-CRMVuMVF.js";
|
|
2
|
+
import { a as isUnderClaudeConfigMirror, f as writeRuntimeFileSecure, t as PATHS } from "./paths-CHBAj_t9.js";
|
|
3
|
+
import { S as LUNA_SCOUT_ALIAS_ID, _ as CHEAP_EXPLORE_ALIAS_ID, b as CHEAP_PLAN_ALIAS_ID, f as CHEAPEST_EXPLORE_ALIAS_ID, g as CHEAPEST_REVIEWER_ALIAS_ID, h as CHEAPEST_PLAN_ALIAS_ID, m as CHEAPEST_IMPLEMENTER_ALIAS_ID, p as CHEAPEST_GENERAL_PURPOSE_ALIAS_ID, v as CHEAP_GENERAL_PURPOSE_ALIAS_ID, x as CHEAP_REVIEWER_ALIAS_ID, y as CHEAP_IMPLEMENTER_ALIAS_ID } from "./server-setup-BOJCIEQi.js";
|
|
4
4
|
import { c as FAST_PROFILE_MODELS, d as FAST_PROFILE_NATIVE_MODELS, l as FAST_PROFILE_NATIVE_AGENT_NAMES, u as FAST_PROFILE_NATIVE_EFFORTS } from "./fast-profile-contract-_DhIWank.js";
|
|
5
5
|
import { C as MAX_PARALLELISM_RULE, S as MAX_COORDINATOR_PROMPT, T as maxNativePrompt, a as MAX_PROFILE_NATIVE_AGENT_NAMES, i as MAX_PROFILE_MODELS, o as MAX_PROFILE_NATIVE_EFFORTS, s as MAX_PROFILE_NATIVE_MODELS, w as maxNativeDescription, x as MAX_COORDINATOR_DESCRIPTION } from "./max-profile-contract-Bl7I_EOC.js";
|
|
6
|
-
import "./self-invocation-
|
|
7
|
-
import { a as STRIPPED_AUTH_ROUTING_ENV_KEYS, n as buildCodexProviderConfigFlags } from "./provision-
|
|
8
|
-
import { n as buildWorkspaceHeaderHelperCommand } from "./mcp-workspace-header-
|
|
9
|
-
import { a as dispatcherAgentName, c as dispatcherTools, n as activeDispatchModes, o as dispatcherDescription, s as dispatcherPrompt } from "./worker-dispatch-
|
|
6
|
+
import "./self-invocation-DahN1gw9.js";
|
|
7
|
+
import { a as STRIPPED_AUTH_ROUTING_ENV_KEYS, n as buildCodexProviderConfigFlags } from "./provision-Bn8-NDp7.js";
|
|
8
|
+
import { n as buildWorkspaceHeaderHelperCommand } from "./mcp-workspace-header-mXRLE8kg.js";
|
|
9
|
+
import { a as dispatcherAgentName, c as dispatcherTools, n as activeDispatchModes, o as dispatcherDescription, s as dispatcherPrompt } from "./worker-dispatch-B-OA7tvU.js";
|
|
10
10
|
import consola from "consola";
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import { randomBytes } from "node:crypto";
|
|
@@ -464,15 +464,21 @@ function buildFastProfileAgentDefinitions(opts) {
|
|
|
464
464
|
return out;
|
|
465
465
|
}
|
|
466
466
|
/** Build the literal `-m cheap` native roster. Identical fixed five-agent
|
|
467
|
-
* surface and roles to `-m fast`, but every SUBAGENT model is
|
|
468
|
-
* (no `
|
|
469
|
-
*
|
|
467
|
+
* surface and roles to `-m fast`, but every SUBAGENT model is a BARE
|
|
468
|
+
* router-owned alias (`gh-router-cheap-*`, no `[1m]` bracket) rather than a
|
|
469
|
+
* real catalog id. A bare real id is resolved by Claude Code against the live
|
|
470
|
+
* catalog and upgraded to `[1m]` accounting when the entry advertises >=1M,
|
|
471
|
+
* defeating the profile's whole 200K cost lever; an alias matches no catalog
|
|
472
|
+
* entry, so the client holds the 200K default while the proxy canonicalizes
|
|
473
|
+
* to the real model upstream with the alias's fixed effort. Caller-supplied
|
|
474
|
+
* `opts.cheap*Model` values are deliberately ignored: the pinned roster is
|
|
475
|
+
* fixed even when the launcher passes the contract's real ids. */
|
|
470
476
|
function buildCheapProfileAgentDefinitions(opts) {
|
|
471
|
-
const
|
|
472
|
-
const planModel =
|
|
473
|
-
const generalModel =
|
|
474
|
-
const implementerModel =
|
|
475
|
-
const reviewerModel =
|
|
477
|
+
const exploreModel = CHEAP_EXPLORE_ALIAS_ID;
|
|
478
|
+
const planModel = CHEAP_PLAN_ALIAS_ID;
|
|
479
|
+
const generalModel = CHEAP_GENERAL_PURPOSE_ALIAS_ID;
|
|
480
|
+
const implementerModel = CHEAP_IMPLEMENTER_ALIAS_ID;
|
|
481
|
+
const reviewerModel = CHEAP_REVIEWER_ALIAS_ID;
|
|
476
482
|
const searchKey = opts.groupKeys.search ?? GROUP_META.search.preferredKey;
|
|
477
483
|
const peersKey = peersKeyOf(opts.groupKeys);
|
|
478
484
|
const searchMcpServers = opts.serverUrl ? { [searchKey]: httpEntryFor(opts.serverUrl, "search", opts.nonce, opts.workspaceHeaderCmd) } : void 0;
|
|
@@ -498,7 +504,7 @@ function buildCheapProfileAgentDefinitions(opts) {
|
|
|
498
504
|
description: "Read-only codebase exploration specialist. Use proactively, and launch several in parallel via `Task(subagent_type:\"Explore\")`, to map architecture, trace call chains, or locate the files and symbols a task will touch. Use when the question spans more than a couple of files. Do not use for planning, edits, or single-file reads. Returns a structured evidence report with file:line citations. Never edits files.",
|
|
499
505
|
prompt: "You are a codebase exploration specialist. Your mission is to map repository structure, discover implementation patterns, trace call chains, and locate the exact files, symbols, and declarations that are relevant to the request. This is read-only work. Do not modify files, do not propose diffs, and do not delegate to other agents. You cannot ask clarifying questions mid-run: ground every answer in repository evidence. If the request is ambiguous, explore the most probable interpretations and record the ambiguity in your report. Start broad, then converge. Issue independent searches in parallel in one turn rather than one at a time, and pair semantic search with exact lexical and symbol search so that neither naming drift nor synonym mismatch hides a result. Include surrounding context lines in your search results so that callers, guards, and types are visible without a second round trip, and inspect the file whenever the surrounding logic determines the answer. Confirm every claim at the source before you report it. Stop when further searching stops changing your answer. When you can name the exact files and lines a change would touch, you are done. Report what the repository contains, not what it ought to contain. Do not design a solution or recommend an approach. Return a self-contained result the lead can act on immediately without needing to re-run your discovery.\n\nReturn format:\nAnswer: a direct response to what was asked, in a few sentences.\nInventory: each relevant file and symbol as file:line, with a one-line description of its role.\nEntry points: where control enters this area, as file:line.\nConventions in use: the patterns, idioms, error handling, and test style that any change here would be expected to follow, each with a file:line example.\nGaps and unknowns: what you could not confirm, and where you would look next.\n\n" + readOnlyToolSteer(),
|
|
500
506
|
tools: readSearchTools,
|
|
501
|
-
model:
|
|
507
|
+
model: exploreModel,
|
|
502
508
|
effort: effort("Explore"),
|
|
503
509
|
...searchMcpServers ? { mcpServers: searchMcpServers } : {}
|
|
504
510
|
},
|
|
@@ -541,7 +547,7 @@ function buildCheapProfileAgentDefinitions(opts) {
|
|
|
541
547
|
out["worker-browse"] = {
|
|
542
548
|
description: dispatcherDescription("browse"),
|
|
543
549
|
prompt: dispatcherPrompt("browse", workersKey),
|
|
544
|
-
model:
|
|
550
|
+
model: exploreModel,
|
|
545
551
|
effort: "high",
|
|
546
552
|
tools: dispatcherTools("browse", workersKey),
|
|
547
553
|
...opts.serverUrl ? { mcpServers: { [workersKey]: httpEntryFor(opts.serverUrl, "workers", opts.nonce, opts.workspaceHeaderCmd) } } : {}
|
|
@@ -553,15 +559,20 @@ function buildCheapProfileAgentDefinitions(opts) {
|
|
|
553
559
|
}
|
|
554
560
|
/** Build the literal `-m cheapest` native roster. Same fixed five-agent
|
|
555
561
|
* surface and roles as `-m cheap`, but Luna-led with a Gemini reviewer —
|
|
556
|
-
* every SUBAGENT model
|
|
557
|
-
*
|
|
558
|
-
*
|
|
562
|
+
* every SUBAGENT model is a BARE router-owned alias
|
|
563
|
+
* (`gh-router-cheapest-*`, no `[1m]`) rather than a real catalog id, for the
|
|
564
|
+
* same client catalog-resolution reason as the cheap builder below: a bare
|
|
565
|
+
* real id is upgraded to `[1m]` accounting by Claude Code whenever the entry
|
|
566
|
+
* advertises >=1M. Caller-supplied `opts.cheapest*Model` values are
|
|
567
|
+
* deliberately ignored. The Plan prompt additionally directs the planner to
|
|
568
|
+
* do the minimal synthesis itself and delegate discovery/execution heavily to
|
|
569
|
+
* `Explore` and `general-purpose`. */
|
|
559
570
|
function buildCheapestProfileAgentDefinitions(opts) {
|
|
560
|
-
const
|
|
561
|
-
const planModel =
|
|
562
|
-
const generalModel =
|
|
563
|
-
const implementerModel =
|
|
564
|
-
const reviewerModel =
|
|
571
|
+
const exploreModel = CHEAPEST_EXPLORE_ALIAS_ID;
|
|
572
|
+
const planModel = CHEAPEST_PLAN_ALIAS_ID;
|
|
573
|
+
const generalModel = CHEAPEST_GENERAL_PURPOSE_ALIAS_ID;
|
|
574
|
+
const implementerModel = CHEAPEST_IMPLEMENTER_ALIAS_ID;
|
|
575
|
+
const reviewerModel = CHEAPEST_REVIEWER_ALIAS_ID;
|
|
565
576
|
const searchKey = opts.groupKeys.search ?? GROUP_META.search.preferredKey;
|
|
566
577
|
const peersKey = peersKeyOf(opts.groupKeys);
|
|
567
578
|
const searchMcpServers = opts.serverUrl ? { [searchKey]: httpEntryFor(opts.serverUrl, "search", opts.nonce, opts.workspaceHeaderCmd) } : void 0;
|
|
@@ -587,7 +598,7 @@ function buildCheapestProfileAgentDefinitions(opts) {
|
|
|
587
598
|
description: "Read-only codebase exploration specialist. Use proactively, and launch several in parallel via `Task(subagent_type:\"Explore\")`, to map architecture, trace call chains, or locate the files and symbols a task will touch. Use when the question spans more than a couple of files. Do not use for planning, edits, or single-file reads. Returns a structured evidence report with file:line citations. Never edits files.",
|
|
588
599
|
prompt: "You are a codebase exploration specialist. Your mission is to map repository structure, discover implementation patterns, trace call chains, and locate the exact files, symbols, and declarations that are relevant to the request. This is read-only work. Do not modify files, do not propose diffs, and do not delegate to other agents. You cannot ask clarifying questions mid-run: ground every answer in repository evidence. If the request is ambiguous, explore the most probable interpretations and record the ambiguity in your report. Start broad, then converge. Issue independent searches in parallel in one turn rather than one at a time, and pair semantic search with exact lexical and symbol search so that neither naming drift nor synonym mismatch hides a result. Include surrounding context lines in your search results so that callers, guards, and types are visible without a second round trip, and inspect the file whenever the surrounding logic determines the answer. Confirm every claim at the source before you report it. Stop when further searching stops changing your answer. When you can name the exact files and lines a change would touch, you are done. Report what the repository contains, not what it ought to contain. Do not design a solution or recommend an approach. Return a self-contained result the lead can act on immediately without needing to re-run your discovery.\n\nReturn format:\nAnswer: a direct response to what was asked, in a few sentences.\nInventory: each relevant file and symbol as file:line, with a one-line description of its role.\nEntry points: where control enters this area, as file:line.\nConventions in use: the patterns, idioms, error handling, and test style that any change here would be expected to follow, each with a file:line example.\nGaps and unknowns: what you could not confirm, and where you would look next.\n\n" + readOnlyToolSteer(),
|
|
589
600
|
tools: readSearchTools,
|
|
590
|
-
model:
|
|
601
|
+
model: exploreModel,
|
|
591
602
|
effort: effort("Explore"),
|
|
592
603
|
...searchMcpServers ? { mcpServers: searchMcpServers } : {}
|
|
593
604
|
},
|
|
@@ -630,7 +641,7 @@ function buildCheapestProfileAgentDefinitions(opts) {
|
|
|
630
641
|
out["worker-browse"] = {
|
|
631
642
|
description: dispatcherDescription("browse"),
|
|
632
643
|
prompt: dispatcherPrompt("browse", workersKey),
|
|
633
|
-
model:
|
|
644
|
+
model: exploreModel,
|
|
634
645
|
effort: "high",
|
|
635
646
|
tools: dispatcherTools("browse", workersKey),
|
|
636
647
|
...opts.serverUrl ? { mcpServers: { [workersKey]: httpEntryFor(opts.serverUrl, "workers", opts.nonce, opts.workspaceHeaderCmd) } } : {}
|
|
@@ -2953,4 +2964,4 @@ async function injectAttributionSuppressionIntoSettingsFile(settingsPath) {
|
|
|
2953
2964
|
//#endregion
|
|
2954
2965
|
export { writePeerMcpRuntimeFiles as C, workersKeyOf as S, sanitizeServeSettingsEnv as _, appendPeerAwarenessToMirroredClaudeMd as a, resolveCodexCliBackend as b, buildOperatingDefaultsDirective as c, prependStyleDirectiveToMirroredClaudeMd as d, buildArtifactReviewSkill as f, planModeAllowRules as g, injectAllowRules as h, writeInjectedSkill as i, prependArtifactPanelDirectiveToMirroredClaudeMd as l, configureServeDefaultPermissionMode as m, INJECTED_SKILLS as n, appendToolbeltAwarenessToMirroredClaudeMd as o, SEAMLESS_BUILTIN_TOOLS as p, injectedSkillsForLaunch as r, buildOperatingDefaultsDigest as s, injectAttributionSuppressionIntoSettingsFile as t, prependOperatingDefaultsToMirroredClaudeMd as u, BUILTIN_SUBAGENT_DEFINITIONS as v, resolveGroupKeysFromMirror as x, injectPeerMcpIntoMirror as y };
|
|
2955
2966
|
|
|
2956
|
-
//# sourceMappingURL=attribution-settings-
|
|
2967
|
+
//# sourceMappingURL=attribution-settings-CyyOsjPF.js.map
|