heyio 4.0.3 → 4.0.4

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.
@@ -60175,13 +60175,10 @@ var require_mod2 = __commonJS({
60175
60175
 
60176
60176
  // packages/shared/dist/constants.js
60177
60177
  var APP_NAME = "io";
60178
- var APP_VERSION = "4.0.0";
60178
+ var APP_VERSION = "4.0.4";
60179
60179
  var API_PORT = 7777;
60180
60180
  var API_HOST = "0.0.0.0";
60181
- var DEFAULT_MODEL = "gpt-4.1";
60182
- var FAST_MODEL = "gpt-4.1-mini";
60183
- var STANDARD_MODEL = "claude-sonnet-4.6";
60184
- var PREMIUM_MODEL = "claude-sonnet-4.6";
60181
+ var DEFAULT_MODEL = "gpt-4o";
60185
60182
  var SESSION_RESET_THRESHOLD = 50;
60186
60183
  var SCHEDULER_INTERVAL_MS = 6e4;
60187
60184
  var QA_MAX_REVISIONS = 3;
@@ -61831,6 +61828,27 @@ var MIGRATIONS = [
61831
61828
  "CREATE INDEX IF NOT EXISTS idx_activity_objective_id ON activity(objective_id)",
61832
61829
  "CREATE INDEX IF NOT EXISTS idx_agent_history_agent_id_created_at ON agent_history(agent_id, created_at)"
61833
61830
  ]
61831
+ },
61832
+ {
61833
+ version: 2,
61834
+ name: "add-model-pricing-and-dual-costs",
61835
+ statements: [
61836
+ `CREATE TABLE IF NOT EXISTS model_pricing (
61837
+ id TEXT PRIMARY KEY,
61838
+ display_name TEXT NOT NULL,
61839
+ premium_multiplier REAL,
61840
+ token_input_multiplier REAL,
61841
+ token_output_multiplier REAL,
61842
+ cached_input_multiplier REAL,
61843
+ tier TEXT NOT NULL,
61844
+ available INTEGER NOT NULL DEFAULT 1,
61845
+ updated_at TEXT NOT NULL
61846
+ )`,
61847
+ "CREATE INDEX IF NOT EXISTS idx_model_pricing_tier ON model_pricing(tier)",
61848
+ "CREATE INDEX IF NOT EXISTS idx_model_pricing_available ON model_pricing(available)",
61849
+ "ALTER TABLE token_usage ADD COLUMN premium_request_cost REAL",
61850
+ "ALTER TABLE token_usage ADD COLUMN token_unit_cost REAL"
61851
+ ]
61834
61852
  }
61835
61853
  ];
61836
61854
  var client = null;
@@ -62515,8 +62533,8 @@ async function recordUsage(data, db) {
62515
62533
  createdAt: data.createdAt ?? nowIso()
62516
62534
  };
62517
62535
  await database.execute({
62518
- sql: `INSERT INTO token_usage (id, squad_id, agent_id, model, input_tokens, output_tokens, cost, created_at)
62519
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
62536
+ sql: `INSERT INTO token_usage (id, squad_id, agent_id, model, input_tokens, output_tokens, cost, premium_request_cost, token_unit_cost, created_at)
62537
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
62520
62538
  args: [
62521
62539
  usage.id,
62522
62540
  usage.squadId,
@@ -62525,6 +62543,8 @@ async function recordUsage(data, db) {
62525
62543
  usage.inputTokens,
62526
62544
  usage.outputTokens,
62527
62545
  usage.cost,
62546
+ data.premiumRequestCost ?? null,
62547
+ data.tokenUnitCost ?? null,
62528
62548
  usage.createdAt
62529
62549
  ]
62530
62550
  });
@@ -77848,7 +77868,8 @@ var configSchema = external_exports.object({
77848
77868
  telegramUserId: external_exports.string().trim().min(1).nullable().default(null),
77849
77869
  supabaseUrl: external_exports.string().trim().min(1).nullable().default(null),
77850
77870
  supabaseAnonKey: external_exports.string().trim().min(1).nullable().default(null),
77851
- sessionResetThreshold: external_exports.coerce.number().int().positive().default(SESSION_RESET_THRESHOLD)
77871
+ sessionResetThreshold: external_exports.coerce.number().int().positive().default(SESSION_RESET_THRESHOLD),
77872
+ pricingRefreshHours: external_exports.coerce.number().positive().default(24)
77852
77873
  });
77853
77874
  function parseConfigFile() {
77854
77875
  if (!existsSync(CONFIG_PATH)) {
@@ -77901,6 +77922,9 @@ function readEnvOverrides() {
77901
77922
  if (process.env.IO_SESSION_RESET_THRESHOLD !== void 0) {
77902
77923
  overrides.sessionResetThreshold = process.env.IO_SESSION_RESET_THRESHOLD;
77903
77924
  }
77925
+ if (process.env.IO_PRICING_REFRESH_HOURS !== void 0) {
77926
+ overrides.pricingRefreshHours = process.env.IO_PRICING_REFRESH_HOURS;
77927
+ }
77904
77928
  return overrides;
77905
77929
  }
77906
77930
  function loadConfig() {
@@ -77922,7 +77946,8 @@ var APP_SETTING_KEYS = [
77922
77946
  "telegramUserId",
77923
77947
  "supabaseUrl",
77924
77948
  "supabaseAnonKey",
77925
- "sessionResetThreshold"
77949
+ "sessionResetThreshold",
77950
+ "pricingRefreshHours"
77926
77951
  ];
77927
77952
  router5.get("/api/settings", async (_req, res) => {
77928
77953
  try {
@@ -78063,8 +78088,25 @@ router6.delete("/api/skills/:id", async (req, res) => {
78063
78088
  });
78064
78089
  }
78065
78090
  });
78066
- router6.get("/api/skills/discover", async (_req, res) => {
78067
- res.status(200).json([]);
78091
+ router6.get("/api/skills/discover", async (req, res) => {
78092
+ try {
78093
+ const source = typeof req.query.source === "string" ? req.query.source : "";
78094
+ const query = typeof req.query.q === "string" ? req.query.q.trim() : "";
78095
+ if (source === "skillssh") {
78096
+ const skills = await discoverSkillsSh(query);
78097
+ res.status(200).json(skills);
78098
+ } else if (source === "awesome-copilot") {
78099
+ const skills = await discoverAwesomeCopilot(query);
78100
+ res.status(200).json(skills);
78101
+ } else {
78102
+ res.status(400).json({ error: `Unknown source: ${source}` });
78103
+ }
78104
+ } catch (error51) {
78105
+ res.status(500).json({
78106
+ error: "Failed to discover skills",
78107
+ details: error51 instanceof Error ? error51.message : "Unknown error"
78108
+ });
78109
+ }
78068
78110
  });
78069
78111
  async function installSkill(request) {
78070
78112
  await mkdir3(SKILLS_DIR, { recursive: true });
@@ -78169,6 +78211,79 @@ function chooseEntryFileName(url2, contentType) {
78169
78211
  function isMissingFileError2(error51) {
78170
78212
  return !!error51 && typeof error51 === "object" && "code" in error51 && error51.code === "ENOENT";
78171
78213
  }
78214
+ async function discoverSkillsSh(query) {
78215
+ if (!query) return [];
78216
+ const endpoint = `https://skills.sh/api/search?q=${encodeURIComponent(query)}&limit=50`;
78217
+ const response = await fetch(endpoint, { signal: AbortSignal.timeout(1e4) });
78218
+ if (!response.ok) {
78219
+ throw new Error(`skills.sh returned ${response.status}`);
78220
+ }
78221
+ const data = await response.json();
78222
+ const installedSkills = await readInstalledSkills();
78223
+ const installedIds = new Set(installedSkills.map((s) => s.id));
78224
+ return (data.skills ?? []).map((entry) => {
78225
+ const skillUrl = entry.source ? `https://raw.githubusercontent.com/${entry.source}/main/skills/${entry.skillId}/SKILL.md` : "";
78226
+ return {
78227
+ name: entry.name || entry.skillId,
78228
+ title: entry.name || entry.skillId,
78229
+ description: `${entry.source ?? ""}`,
78230
+ url: skillUrl,
78231
+ source: "skillssh",
78232
+ installed: installedIds.has(`skillssh:${normalizeSlug(entry.skillId || entry.name)}`),
78233
+ registrySource: entry.source ?? void 0,
78234
+ skillId: entry.skillId ?? void 0,
78235
+ installs: entry.installs ?? 0
78236
+ };
78237
+ });
78238
+ }
78239
+ var awesomeCopilotCache = null;
78240
+ var AWESOME_COPILOT_CACHE_TTL = 60 * 60 * 1e3;
78241
+ async function discoverAwesomeCopilot(query) {
78242
+ const now = Date.now();
78243
+ if (!awesomeCopilotCache || now - awesomeCopilotCache.fetchedAt > AWESOME_COPILOT_CACHE_TTL) {
78244
+ awesomeCopilotCache = { skills: await fetchAwesomeCopilotList(), fetchedAt: now };
78245
+ }
78246
+ const skills = awesomeCopilotCache.skills;
78247
+ if (!query) return skills;
78248
+ const needle = query.toLowerCase();
78249
+ return skills.filter(
78250
+ (s) => s.name.toLowerCase().includes(needle) || s.title.toLowerCase().includes(needle) || s.description.toLowerCase().includes(needle)
78251
+ );
78252
+ }
78253
+ async function fetchAwesomeCopilotList() {
78254
+ const treeUrl = "https://api.github.com/repos/github/awesome-copilot/git/trees/main?recursive=1";
78255
+ const headers = {
78256
+ Accept: "application/vnd.github.v3+json",
78257
+ "User-Agent": "io-daemon"
78258
+ };
78259
+ if (process.env.GITHUB_TOKEN) {
78260
+ headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
78261
+ }
78262
+ const response = await fetch(treeUrl, { headers, signal: AbortSignal.timeout(15e3) });
78263
+ if (!response.ok) {
78264
+ throw new Error(`GitHub API returned ${response.status}`);
78265
+ }
78266
+ const data = await response.json();
78267
+ const installedSkills = await readInstalledSkills();
78268
+ const installedIds = new Set(installedSkills.map((s) => s.id));
78269
+ const skillEntries = (data.tree ?? []).filter(
78270
+ (entry) => entry.type === "blob" && entry.path.startsWith("skills/") && entry.path.endsWith("/SKILL.md")
78271
+ );
78272
+ return skillEntries.map((entry) => {
78273
+ const parts = entry.path.split("/");
78274
+ const skillName = parts[1] ?? "unknown";
78275
+ const slug = normalizeSlug(skillName);
78276
+ const rawUrl = `https://raw.githubusercontent.com/github/awesome-copilot/main/${entry.path}`;
78277
+ return {
78278
+ name: skillName,
78279
+ title: skillName.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
78280
+ description: "From github/awesome-copilot",
78281
+ url: rawUrl,
78282
+ source: "awesome-copilot",
78283
+ installed: installedIds.has(`awesome-copilot:${slug}`)
78284
+ };
78285
+ });
78286
+ }
78172
78287
 
78173
78288
  // packages/daemon/src/api/routes/squads.ts
78174
78289
  var import_express7 = __toESM(require_express2(), 1);
@@ -79114,6 +79229,448 @@ function createLogger(name) {
79114
79229
  return getRootLogger().child({ subsystem: name });
79115
79230
  }
79116
79231
 
79232
+ // packages/daemon/src/models/catalog.ts
79233
+ import { execFileSync } from "node:child_process";
79234
+ var CATALOG_URL = "https://models.github.ai/catalog/models";
79235
+ function resolveGitHubToken() {
79236
+ const envToken = process.env.GITHUB_TOKEN?.trim();
79237
+ if (envToken && envToken.length > 0) {
79238
+ return envToken;
79239
+ }
79240
+ try {
79241
+ const token = execFileSync("gh", ["auth", "token"], {
79242
+ encoding: "utf8",
79243
+ stdio: ["ignore", "pipe", "pipe"]
79244
+ }).trim();
79245
+ return token.length > 0 ? token : void 0;
79246
+ } catch {
79247
+ return void 0;
79248
+ }
79249
+ }
79250
+ async function fetchModelCatalog() {
79251
+ const token = resolveGitHubToken();
79252
+ if (!token) {
79253
+ throw new Error("No GitHub token available for model catalog fetch");
79254
+ }
79255
+ const response = await fetch(CATALOG_URL, {
79256
+ headers: {
79257
+ Accept: "application/json",
79258
+ Authorization: `Bearer ${token}`,
79259
+ "X-GitHub-Api-Version": "2024-12-01"
79260
+ }
79261
+ });
79262
+ if (!response.ok) {
79263
+ throw new Error(`Model catalog fetch failed: ${response.status} ${response.statusText}`);
79264
+ }
79265
+ const data = await response.json();
79266
+ if (!Array.isArray(data)) {
79267
+ throw new Error("Model catalog response is not an array");
79268
+ }
79269
+ const models = [];
79270
+ for (const entry of data) {
79271
+ const id = entry.id ?? entry.name;
79272
+ const displayName = entry.friendly_name ?? entry.name ?? id;
79273
+ if (id && typeof id === "string") {
79274
+ models.push({ id, displayName: displayName ?? id });
79275
+ }
79276
+ }
79277
+ return models;
79278
+ }
79279
+
79280
+ // packages/daemon/src/models/pricing-scraper.ts
79281
+ var TOKEN_UNIT_COSTS_URL = "https://docs.github.com/en/billing/reference/costs-for-github-models";
79282
+ var PREMIUM_MULTIPLIERS_URL = "https://docs.github.com/en/copilot/reference/copilot-billing/request-based-billing-legacy/model-multipliers-for-annual-plans";
79283
+ async function scrapeTokenUnitPricing() {
79284
+ const html = await fetchPage(TOKEN_UNIT_COSTS_URL);
79285
+ return parseTokenUnitTable(html);
79286
+ }
79287
+ async function scrapePremiumRequestPricing() {
79288
+ const html = await fetchPage(PREMIUM_MULTIPLIERS_URL);
79289
+ return parsePremiumMultiplierTable(html);
79290
+ }
79291
+ async function fetchPage(url2) {
79292
+ const response = await fetch(url2, {
79293
+ headers: {
79294
+ Accept: "text/html",
79295
+ "User-Agent": "IO-Daemon/4.0 (pricing-refresh)"
79296
+ },
79297
+ redirect: "follow"
79298
+ });
79299
+ if (!response.ok) {
79300
+ throw new Error(`Failed to fetch ${url2}: ${response.status} ${response.statusText}`);
79301
+ }
79302
+ return response.text();
79303
+ }
79304
+ function parseTokenUnitTable(html) {
79305
+ const results = [];
79306
+ const tableRowPattern = /<tr[^>]*>\s*<td[^>]*>([^<]+)<\/td>\s*<td[^>]*>([^<]+)<\/td>\s*<td[^>]*>([^<]*)<\/td>\s*<td[^>]*>([^<]+)<\/td>/gi;
79307
+ for (const match of html.matchAll(tableRowPattern)) {
79308
+ const modelName = cleanCellText(match[1]);
79309
+ const inputMultiplier = Number.parseFloat(match[2]);
79310
+ const cachedInput = match[3].trim().toLowerCase();
79311
+ const outputMultiplier = Number.parseFloat(match[4]);
79312
+ if (modelName && !Number.isNaN(inputMultiplier) && !Number.isNaN(outputMultiplier)) {
79313
+ results.push({
79314
+ modelName,
79315
+ inputMultiplier,
79316
+ cachedInputMultiplier: cachedInput === "n/a" || cachedInput === "" ? null : Number.parseFloat(cachedInput) || null,
79317
+ outputMultiplier
79318
+ });
79319
+ }
79320
+ }
79321
+ return results;
79322
+ }
79323
+ function parsePremiumMultiplierTable(html) {
79324
+ const results = [];
79325
+ const tableRowPattern = /<tr[^>]*>\s*<td[^>]*>([^<]+)<\/td>\s*<td[^>]*>([^<]+)<\/td>\s*<\/tr>/gi;
79326
+ for (const match of html.matchAll(tableRowPattern)) {
79327
+ const modelName = cleanCellText(match[1]);
79328
+ const multiplier = Number.parseFloat(match[2]);
79329
+ if (modelName && !Number.isNaN(multiplier) && multiplier > 0) {
79330
+ results.push({ modelName, multiplier });
79331
+ }
79332
+ }
79333
+ return results;
79334
+ }
79335
+ function cleanCellText(text) {
79336
+ return text.replace(/<[^>]*>/g, "").replace(/&[^;]+;/g, " ").trim();
79337
+ }
79338
+
79339
+ // packages/daemon/src/models/seed.ts
79340
+ var SEED_MODELS = [
79341
+ {
79342
+ id: "gpt-4o-mini",
79343
+ displayName: "OpenAI GPT-4o mini",
79344
+ premiumMultiplier: 0.33,
79345
+ tokenInputMultiplier: 0.015,
79346
+ tokenOutputMultiplier: 0.06,
79347
+ cachedInputMultiplier: 75e-4,
79348
+ tier: "trivial",
79349
+ available: true
79350
+ },
79351
+ {
79352
+ id: "gpt-4o",
79353
+ displayName: "OpenAI GPT-4o",
79354
+ premiumMultiplier: 0.33,
79355
+ tokenInputMultiplier: 0.25,
79356
+ tokenOutputMultiplier: 1,
79357
+ cachedInputMultiplier: 0.125,
79358
+ tier: "trivial",
79359
+ available: true
79360
+ },
79361
+ {
79362
+ id: "gpt-5-mini",
79363
+ displayName: "GPT-5 mini",
79364
+ premiumMultiplier: 0.33,
79365
+ tokenInputMultiplier: null,
79366
+ tokenOutputMultiplier: null,
79367
+ cachedInputMultiplier: null,
79368
+ tier: "trivial",
79369
+ available: true
79370
+ },
79371
+ {
79372
+ id: "claude-sonnet-4",
79373
+ displayName: "Claude Sonnet 4",
79374
+ premiumMultiplier: 6,
79375
+ tokenInputMultiplier: 0.6,
79376
+ tokenOutputMultiplier: 2.4,
79377
+ cachedInputMultiplier: null,
79378
+ tier: "premium",
79379
+ available: true
79380
+ },
79381
+ {
79382
+ id: "claude-haiku-4.5",
79383
+ displayName: "Claude Haiku 4.5",
79384
+ premiumMultiplier: 0.33,
79385
+ tokenInputMultiplier: null,
79386
+ tokenOutputMultiplier: null,
79387
+ cachedInputMultiplier: null,
79388
+ tier: "trivial",
79389
+ available: true
79390
+ },
79391
+ {
79392
+ id: "gemini-2.5-pro",
79393
+ displayName: "Gemini 2.5 Pro",
79394
+ premiumMultiplier: 1,
79395
+ tokenInputMultiplier: null,
79396
+ tokenOutputMultiplier: null,
79397
+ cachedInputMultiplier: null,
79398
+ tier: "fast",
79399
+ available: true
79400
+ },
79401
+ {
79402
+ id: "gpt-5.1",
79403
+ displayName: "GPT-5.1",
79404
+ premiumMultiplier: 3,
79405
+ tokenInputMultiplier: null,
79406
+ tokenOutputMultiplier: null,
79407
+ cachedInputMultiplier: null,
79408
+ tier: "standard",
79409
+ available: true
79410
+ },
79411
+ {
79412
+ id: "gpt-5.2",
79413
+ displayName: "GPT-5.2",
79414
+ premiumMultiplier: 3,
79415
+ tokenInputMultiplier: null,
79416
+ tokenOutputMultiplier: null,
79417
+ cachedInputMultiplier: null,
79418
+ tier: "standard",
79419
+ available: true
79420
+ },
79421
+ {
79422
+ id: "gpt-5.4",
79423
+ displayName: "GPT-5.4",
79424
+ premiumMultiplier: 6,
79425
+ tokenInputMultiplier: null,
79426
+ tokenOutputMultiplier: null,
79427
+ cachedInputMultiplier: null,
79428
+ tier: "premium",
79429
+ available: true
79430
+ },
79431
+ {
79432
+ id: "claude-opus-4.5",
79433
+ displayName: "Claude Opus 4.5",
79434
+ premiumMultiplier: 15,
79435
+ tokenInputMultiplier: null,
79436
+ tokenOutputMultiplier: null,
79437
+ cachedInputMultiplier: null,
79438
+ tier: "premium",
79439
+ available: true
79440
+ },
79441
+ {
79442
+ id: "claude-opus-4.6",
79443
+ displayName: "Claude Opus 4.6",
79444
+ premiumMultiplier: 27,
79445
+ tokenInputMultiplier: null,
79446
+ tokenOutputMultiplier: null,
79447
+ cachedInputMultiplier: null,
79448
+ tier: "ultra",
79449
+ available: true
79450
+ },
79451
+ {
79452
+ id: "claude-opus-4.7",
79453
+ displayName: "Claude Opus 4.7",
79454
+ premiumMultiplier: 27,
79455
+ tokenInputMultiplier: null,
79456
+ tokenOutputMultiplier: null,
79457
+ cachedInputMultiplier: null,
79458
+ tier: "ultra",
79459
+ available: true
79460
+ },
79461
+ {
79462
+ id: "gpt-5.5",
79463
+ displayName: "GPT-5.5",
79464
+ premiumMultiplier: 57,
79465
+ tokenInputMultiplier: null,
79466
+ tokenOutputMultiplier: null,
79467
+ cachedInputMultiplier: null,
79468
+ tier: "ultra",
79469
+ available: true
79470
+ }
79471
+ ];
79472
+
79473
+ // packages/daemon/src/models/types.ts
79474
+ var TIER_RANGES = {
79475
+ trivial: { min: 0, max: 0.33 },
79476
+ fast: { min: 0.34, max: 1 },
79477
+ standard: { min: 1.1, max: 5 },
79478
+ premium: { min: 5.1, max: 15 },
79479
+ ultra: { min: 15.1, max: Number.POSITIVE_INFINITY }
79480
+ };
79481
+ function computeTierFromMultiplier(premiumMultiplier) {
79482
+ if (premiumMultiplier === null) {
79483
+ return "standard";
79484
+ }
79485
+ for (const [tier, range] of Object.entries(TIER_RANGES)) {
79486
+ if (premiumMultiplier >= range.min && premiumMultiplier <= range.max) {
79487
+ return tier;
79488
+ }
79489
+ }
79490
+ return "ultra";
79491
+ }
79492
+ var TOKEN_UNIT_PRICE = 1e-5;
79493
+
79494
+ // packages/daemon/src/models/registry.ts
79495
+ function normalizeModelName(name) {
79496
+ return name.toLowerCase().replace(/^openai\s+/i, "").replace(/\s+/g, "-").replace(/[^a-z0-9.\-]/g, "").trim();
79497
+ }
79498
+ async function fetchCatalogIntoMap(modelMap, result, logger) {
79499
+ try {
79500
+ const catalogModels = await fetchModelCatalog();
79501
+ result.catalogFetched = true;
79502
+ for (const m of catalogModels) {
79503
+ const key = normalizeModelName(m.id);
79504
+ modelMap.set(key, { id: m.id, displayName: m.displayName, available: true });
79505
+ }
79506
+ } catch (error51) {
79507
+ const msg = error51 instanceof Error ? error51.message : String(error51);
79508
+ result.errors.push(`Catalog fetch failed: ${msg}`);
79509
+ logger?.warn(`Model catalog fetch failed: ${msg}`);
79510
+ }
79511
+ }
79512
+ async function scrapeTokenPricingIntoMap(modelMap, result, logger) {
79513
+ try {
79514
+ const tokenPricing = await scrapeTokenUnitPricing();
79515
+ result.tokenPricingScraped = true;
79516
+ for (const tp of tokenPricing) {
79517
+ const key = normalizeModelName(tp.modelName);
79518
+ const existing = modelMap.get(key) ?? findClosestKey(modelMap, key);
79519
+ if (existing) {
79520
+ existing.tokenInputMultiplier = tp.inputMultiplier;
79521
+ existing.tokenOutputMultiplier = tp.outputMultiplier;
79522
+ existing.cachedInputMultiplier = tp.cachedInputMultiplier;
79523
+ } else {
79524
+ modelMap.set(key, {
79525
+ id: key,
79526
+ displayName: tp.modelName,
79527
+ tokenInputMultiplier: tp.inputMultiplier,
79528
+ tokenOutputMultiplier: tp.outputMultiplier,
79529
+ cachedInputMultiplier: tp.cachedInputMultiplier,
79530
+ available: true
79531
+ });
79532
+ }
79533
+ }
79534
+ } catch (error51) {
79535
+ const msg = error51 instanceof Error ? error51.message : String(error51);
79536
+ result.errors.push(`Token pricing scrape failed: ${msg}`);
79537
+ logger?.warn(`Token pricing scrape failed: ${msg}`);
79538
+ }
79539
+ }
79540
+ async function scrapePremiumPricingIntoMap(modelMap, result, logger) {
79541
+ try {
79542
+ const premiumPricing = await scrapePremiumRequestPricing();
79543
+ result.premiumPricingScraped = true;
79544
+ for (const pp of premiumPricing) {
79545
+ const key = normalizeModelName(pp.modelName);
79546
+ const existing = modelMap.get(key) ?? findClosestKey(modelMap, key);
79547
+ if (existing) {
79548
+ existing.premiumMultiplier = pp.multiplier;
79549
+ } else {
79550
+ modelMap.set(key, {
79551
+ id: key,
79552
+ displayName: pp.modelName,
79553
+ premiumMultiplier: pp.multiplier,
79554
+ available: true
79555
+ });
79556
+ }
79557
+ }
79558
+ } catch (error51) {
79559
+ const msg = error51 instanceof Error ? error51.message : String(error51);
79560
+ result.errors.push(`Premium pricing scrape failed: ${msg}`);
79561
+ logger?.warn(`Premium pricing scrape failed: ${msg}`);
79562
+ }
79563
+ }
79564
+ async function refreshModelPricing(logger) {
79565
+ const result = {
79566
+ modelsUpdated: 0,
79567
+ catalogFetched: false,
79568
+ tokenPricingScraped: false,
79569
+ premiumPricingScraped: false,
79570
+ errors: []
79571
+ };
79572
+ const modelMap = /* @__PURE__ */ new Map();
79573
+ await fetchCatalogIntoMap(modelMap, result, logger);
79574
+ await scrapeTokenPricingIntoMap(modelMap, result, logger);
79575
+ await scrapePremiumPricingIntoMap(modelMap, result, logger);
79576
+ if (!result.catalogFetched && !result.tokenPricingScraped && !result.premiumPricingScraped) {
79577
+ logger?.warn("All pricing sources failed, using seed data");
79578
+ await seedFromFallback();
79579
+ result.modelsUpdated = SEED_MODELS.length;
79580
+ return result;
79581
+ }
79582
+ const db = await getDatabase();
79583
+ const now = nowIso();
79584
+ for (const model of modelMap.values()) {
79585
+ const tier = computeTierFromMultiplier(model.premiumMultiplier ?? null);
79586
+ await upsertModel(db, {
79587
+ id: model.id,
79588
+ displayName: model.displayName,
79589
+ premiumMultiplier: model.premiumMultiplier ?? null,
79590
+ tokenInputMultiplier: model.tokenInputMultiplier ?? null,
79591
+ tokenOutputMultiplier: model.tokenOutputMultiplier ?? null,
79592
+ cachedInputMultiplier: model.cachedInputMultiplier ?? null,
79593
+ tier,
79594
+ available: model.available ?? true,
79595
+ updatedAt: now
79596
+ });
79597
+ result.modelsUpdated++;
79598
+ }
79599
+ return result;
79600
+ }
79601
+ async function seedFromFallback() {
79602
+ const db = await getDatabase();
79603
+ const now = nowIso();
79604
+ for (const model of SEED_MODELS) {
79605
+ await upsertModel(db, { ...model, updatedAt: now });
79606
+ }
79607
+ }
79608
+ async function getModelPricing(modelId) {
79609
+ const db = await getDatabase();
79610
+ const result = await db.execute({
79611
+ sql: "SELECT * FROM model_pricing WHERE id = ?",
79612
+ args: [modelId]
79613
+ });
79614
+ if (result.rows.length === 0) {
79615
+ return null;
79616
+ }
79617
+ return rowToModelPricing(result.rows[0]);
79618
+ }
79619
+ function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outputMultiplier) {
79620
+ if (inputMultiplier === null || outputMultiplier === null) {
79621
+ return 0;
79622
+ }
79623
+ const tokenUnits = inputTokens * inputMultiplier + outputTokens * outputMultiplier;
79624
+ return tokenUnits * TOKEN_UNIT_PRICE;
79625
+ }
79626
+ async function upsertModel(db, model) {
79627
+ await db.execute({
79628
+ sql: `INSERT INTO model_pricing (id, display_name, premium_multiplier, token_input_multiplier, token_output_multiplier, cached_input_multiplier, tier, available, updated_at)
79629
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
79630
+ ON CONFLICT(id) DO UPDATE SET
79631
+ display_name = excluded.display_name,
79632
+ premium_multiplier = COALESCE(excluded.premium_multiplier, model_pricing.premium_multiplier),
79633
+ token_input_multiplier = COALESCE(excluded.token_input_multiplier, model_pricing.token_input_multiplier),
79634
+ token_output_multiplier = COALESCE(excluded.token_output_multiplier, model_pricing.token_output_multiplier),
79635
+ cached_input_multiplier = COALESCE(excluded.cached_input_multiplier, model_pricing.cached_input_multiplier),
79636
+ tier = excluded.tier,
79637
+ available = excluded.available,
79638
+ updated_at = excluded.updated_at`,
79639
+ args: [
79640
+ model.id,
79641
+ model.displayName,
79642
+ model.premiumMultiplier,
79643
+ model.tokenInputMultiplier,
79644
+ model.tokenOutputMultiplier,
79645
+ model.cachedInputMultiplier,
79646
+ model.tier,
79647
+ model.available ? 1 : 0,
79648
+ model.updatedAt
79649
+ ]
79650
+ });
79651
+ }
79652
+ function rowToModelPricing(row) {
79653
+ return {
79654
+ id: asString(row.id),
79655
+ displayName: asString(row.display_name),
79656
+ premiumMultiplier: asNullableNumber(row.premium_multiplier),
79657
+ tokenInputMultiplier: asNullableNumber(row.token_input_multiplier),
79658
+ tokenOutputMultiplier: asNullableNumber(row.token_output_multiplier),
79659
+ cachedInputMultiplier: asNullableNumber(row.cached_input_multiplier),
79660
+ tier: asString(row.tier),
79661
+ available: asNumber(row.available) === 1,
79662
+ updatedAt: asString(row.updated_at)
79663
+ };
79664
+ }
79665
+ function findClosestKey(map2, targetKey) {
79666
+ for (const [key, value] of map2) {
79667
+ if (key.includes(targetKey) || targetKey.includes(key)) {
79668
+ return value;
79669
+ }
79670
+ }
79671
+ return void 0;
79672
+ }
79673
+
79117
79674
  // packages/daemon/src/orchestrator/system-prompt.ts
79118
79675
  function formatSquadRoster(squads) {
79119
79676
  if (squads.length === 0) {
@@ -79206,146 +79763,13 @@ function buildFreshSessionContext(summary, wikiContext) {
79206
79763
  return sections.join("\n\n");
79207
79764
  }
79208
79765
 
79209
- // packages/daemon/src/copilot/router.ts
79210
- var FAST_KEYWORDS = [
79211
- "hi",
79212
- "hello",
79213
- "hey",
79214
- "thanks",
79215
- "thank you",
79216
- "status",
79217
- "list",
79218
- "show",
79219
- "ping",
79220
- "uptime",
79221
- "what time",
79222
- "ok",
79223
- "okay"
79224
- ];
79225
- var PREMIUM_KEYWORDS = [
79226
- "architecture",
79227
- "architect",
79228
- "design",
79229
- "tradeoff",
79230
- "trade-off",
79231
- "plan",
79232
- "planning",
79233
- "strategy",
79234
- "roadmap",
79235
- "migration",
79236
- "refactor",
79237
- "generate",
79238
- "implement",
79239
- "build",
79240
- "codebase",
79241
- "repository",
79242
- "system prompt",
79243
- "multi-step",
79244
- "complex",
79245
- "reason",
79246
- "compare"
79247
- ];
79248
- var SIMPLE_QUESTION_PREFIXES = [
79249
- "is",
79250
- "are",
79251
- "do",
79252
- "does",
79253
- "did",
79254
- "can",
79255
- "could",
79256
- "should",
79257
- "would",
79258
- "will"
79259
- ];
79260
- var MODEL_SWITCH_COOLDOWN_MESSAGES = 3;
79261
- var activeTier = null;
79262
- var messagesSinceLastSwitch = MODEL_SWITCH_COOLDOWN_MESSAGES;
79263
- function normalizeMessage(message2) {
79264
- return message2.trim().toLowerCase();
79265
- }
79266
- function getWordCount(message2) {
79267
- const normalized = message2.trim();
79268
- if (normalized.length === 0) {
79269
- return 0;
79270
- }
79271
- return normalized.split(/\s+/u).length;
79272
- }
79273
- function containsAny(message2, keywords) {
79274
- return keywords.some((keyword) => message2.includes(keyword));
79275
- }
79276
- function startsWithSimpleQuestion(message2) {
79277
- return SIMPLE_QUESTION_PREFIXES.some(
79278
- (prefix) => message2 === prefix || message2.startsWith(`${prefix} `)
79279
- );
79280
- }
79281
- function classifyMessage(message2) {
79282
- const normalized = normalizeMessage(message2);
79283
- const wordCount = getWordCount(normalized);
79284
- if (normalized.length === 0) {
79285
- return "fast";
79286
- }
79287
- if (containsAny(normalized, PREMIUM_KEYWORDS) || normalized.includes("```") || wordCount >= 80) {
79288
- return "premium";
79289
- }
79290
- if (wordCount <= 20 && (containsAny(normalized, FAST_KEYWORDS) || startsWithSimpleQuestion(normalized) || /^(yes|no|sure|okay|ok|thanks)[!.?]*$/u.test(normalized))) {
79291
- return "fast";
79292
- }
79293
- if (wordCount <= 8 && /^(what|when|where|who)\b/u.test(normalized)) {
79294
- return "fast";
79295
- }
79296
- return "standard";
79297
- }
79298
- function getModelForTier(tier) {
79299
- switch (tier) {
79300
- case "fast":
79301
- return FAST_MODEL;
79302
- case "premium":
79303
- return PREMIUM_MODEL;
79304
- default:
79305
- return STANDARD_MODEL;
79306
- }
79307
- }
79308
- function routeMessage(message2) {
79309
- const requestedTier = classifyMessage(message2);
79310
- if (activeTier === null) {
79311
- activeTier = requestedTier;
79312
- messagesSinceLastSwitch = 0;
79313
- return {
79314
- requestedTier,
79315
- effectiveTier: activeTier,
79316
- model: getModelForTier(activeTier),
79317
- switched: true,
79318
- cooldownRemaining: MODEL_SWITCH_COOLDOWN_MESSAGES
79319
- };
79320
- }
79321
- if (requestedTier !== activeTier && messagesSinceLastSwitch >= MODEL_SWITCH_COOLDOWN_MESSAGES) {
79322
- activeTier = requestedTier;
79323
- messagesSinceLastSwitch = 0;
79324
- return {
79325
- requestedTier,
79326
- effectiveTier: activeTier,
79327
- model: getModelForTier(activeTier),
79328
- switched: true,
79329
- cooldownRemaining: MODEL_SWITCH_COOLDOWN_MESSAGES
79330
- };
79331
- }
79332
- messagesSinceLastSwitch += 1;
79333
- return {
79334
- requestedTier,
79335
- effectiveTier: activeTier,
79336
- model: getModelForTier(activeTier),
79337
- switched: false,
79338
- cooldownRemaining: Math.max(MODEL_SWITCH_COOLDOWN_MESSAGES - messagesSinceLastSwitch, 0)
79339
- };
79340
- }
79341
-
79342
79766
  // packages/daemon/src/copilot/session.ts
79343
79767
  import {
79344
79768
  approveAll
79345
79769
  } from "@github/copilot-sdk";
79346
79770
 
79347
79771
  // packages/daemon/src/copilot/client.ts
79348
- import { execFileSync } from "node:child_process";
79772
+ import { execFileSync as execFileSync2 } from "node:child_process";
79349
79773
  import { mkdirSync as mkdirSync2 } from "node:fs";
79350
79774
  import { join as join6 } from "node:path";
79351
79775
  import { CopilotClient } from "@github/copilot-sdk";
@@ -79359,7 +79783,7 @@ function readTokenFromEnvironment() {
79359
79783
  }
79360
79784
  function readTokenFromGhCli() {
79361
79785
  try {
79362
- const token = execFileSync("gh", ["auth", "token"], {
79786
+ const token = execFileSync2("gh", ["auth", "token"], {
79363
79787
  encoding: "utf8",
79364
79788
  stdio: ["ignore", "pipe", "pipe"]
79365
79789
  }).trim();
@@ -79368,7 +79792,7 @@ function readTokenFromGhCli() {
79368
79792
  return void 0;
79369
79793
  }
79370
79794
  }
79371
- function resolveGitHubToken() {
79795
+ function resolveGitHubToken2() {
79372
79796
  return readTokenFromEnvironment() ?? readTokenFromGhCli();
79373
79797
  }
79374
79798
  function buildClientOptions(token) {
@@ -79404,7 +79828,7 @@ async function initCopilotClient() {
79404
79828
  }
79405
79829
  copilotClientInitPromise = (async () => {
79406
79830
  try {
79407
- const token = resolveGitHubToken();
79831
+ const token = resolveGitHubToken2();
79408
79832
  if (token === void 0) {
79409
79833
  throw new Error(
79410
79834
  `Unable to find a GitHub token for Copilot SDK authentication. ${COPILOT_AUTH_ERROR_HINT}`
@@ -80232,13 +80656,24 @@ function mergeUsage(target, usage) {
80232
80656
  }
80233
80657
  async function persistUsage(member, usageEvents) {
80234
80658
  for (const usage of usageEvents) {
80659
+ const model = usage.model;
80660
+ const pricing = await getModelPricing(model);
80661
+ const premiumRequestCost = pricing?.premiumMultiplier ?? 0;
80662
+ const tokenUnitCost = pricing ? calculateTokenUnitCost(
80663
+ usage.inputTokens ?? 0,
80664
+ usage.outputTokens ?? 0,
80665
+ pricing.tokenInputMultiplier,
80666
+ pricing.tokenOutputMultiplier
80667
+ ) : 0;
80235
80668
  await recordUsage({
80236
80669
  squadId: member.squadId,
80237
80670
  agentId: member.id,
80238
- model: usage.model,
80671
+ model,
80239
80672
  inputTokens: usage.inputTokens ?? 0,
80240
80673
  outputTokens: usage.outputTokens ?? 0,
80241
- cost: usage.cost ?? 0
80674
+ cost: 0,
80675
+ premiumRequestCost,
80676
+ tokenUnitCost
80242
80677
  });
80243
80678
  }
80244
80679
  }
@@ -80456,7 +80891,7 @@ async function executeAgentTask(member, task, worktreePath, options2) {
80456
80891
  client2 = new CopilotClient2({ workingDirectory: worktreePath });
80457
80892
  await client2.start();
80458
80893
  const session = await client2.createSession({
80459
- model: member.model ?? STANDARD_MODEL,
80894
+ model: member.model ?? DEFAULT_MODEL,
80460
80895
  workingDirectory: worktreePath,
80461
80896
  tools,
80462
80897
  availableTools: ["custom:*"],
@@ -80697,7 +81132,7 @@ Return strict JSON in this shape:
80697
81132
  client2 = new CopilotClient3({ workingDirectory: repoPath });
80698
81133
  await client2.start();
80699
81134
  const session = await client2.createSession({
80700
- model: PREMIUM_MODEL,
81135
+ model: DEFAULT_MODEL,
80701
81136
  workingDirectory: repoPath,
80702
81137
  onPermissionRequest: approveAll3,
80703
81138
  systemMessage: {
@@ -80968,7 +81403,7 @@ Return strict JSON:
80968
81403
  client2 = new CopilotClient4({ workingDirectory: worktreePath });
80969
81404
  await client2.start();
80970
81405
  const session = await client2.createSession({
80971
- model: qaMember.model ?? STANDARD_MODEL,
81406
+ model: qaMember.model ?? DEFAULT_MODEL,
80972
81407
  workingDirectory: worktreePath,
80973
81408
  onPermissionRequest: approveAll4,
80974
81409
  systemMessage: {
@@ -81088,7 +81523,7 @@ Return strict JSON:
81088
81523
  client2 = new CopilotClient5();
81089
81524
  await client2.start();
81090
81525
  const session = await client2.createSession({
81091
- model: teamLead.model ?? PREMIUM_MODEL,
81526
+ model: teamLead.model ?? DEFAULT_MODEL,
81092
81527
  onPermissionRequest: approveAll5,
81093
81528
  systemMessage: {
81094
81529
  content: `${TEAM_LEAD_PROMPT}
@@ -81543,12 +81978,9 @@ function buildMandatoryRoles() {
81543
81978
  function modelForRole(role) {
81544
81979
  const normalized = slugifyRole(role);
81545
81980
  if (normalized === "team-lead") {
81546
- return PREMIUM_MODEL;
81547
- }
81548
- if (normalized === "qa") {
81549
- return STANDARD_MODEL;
81981
+ return DEFAULT_MODEL;
81550
81982
  }
81551
- return STANDARD_MODEL;
81983
+ return DEFAULT_MODEL;
81552
81984
  }
81553
81985
  function systemPromptForRole(role, repoContext) {
81554
81986
  const normalized = slugifyRole(role);
@@ -81580,7 +82012,7 @@ ${ROLE_GENERATION_PROMPT}`;
81580
82012
  client2 = new CopilotClient6();
81581
82013
  await client2.start();
81582
82014
  const session = await client2.createSession({
81583
- model: PREMIUM_MODEL,
82015
+ model: DEFAULT_MODEL,
81584
82016
  onPermissionRequest: approveAll6,
81585
82017
  systemMessage: {
81586
82018
  content: ROLE_GENERATION_PROMPT
@@ -82054,8 +82486,7 @@ var Orchestrator = class {
82054
82486
  skillsContext,
82055
82487
  conversationSummary: [this.latestResetSummary, conversationSummary].filter(Boolean).join("\n\n")
82056
82488
  });
82057
- const route = routeMessage(normalizedMessage);
82058
- await this.refreshSession(route.model, systemPrompt);
82489
+ await this.refreshSession(this.config.defaultModel, systemPrompt);
82059
82490
  const sendResult = await sendMessage(
82060
82491
  this.requireActiveSession(),
82061
82492
  normalizedMessage,
@@ -82183,11 +82614,22 @@ var Orchestrator = class {
82183
82614
  if (usage.inputTokens === 0 && usage.outputTokens === 0) {
82184
82615
  return;
82185
82616
  }
82617
+ const model = usage.model || this.activeModel || this.config.defaultModel;
82618
+ const pricing = await getModelPricing(model);
82619
+ const premiumRequestCost = pricing?.premiumMultiplier ?? 0;
82620
+ const tokenUnitCost = pricing ? calculateTokenUnitCost(
82621
+ usage.inputTokens,
82622
+ usage.outputTokens,
82623
+ pricing.tokenInputMultiplier,
82624
+ pricing.tokenOutputMultiplier
82625
+ ) : 0;
82186
82626
  await recordUsage({
82187
- model: usage.model || this.activeModel || this.config.defaultModel,
82627
+ model,
82188
82628
  inputTokens: usage.inputTokens,
82189
82629
  outputTokens: usage.outputTokens,
82190
- cost: 0
82630
+ cost: 0,
82631
+ premiumRequestCost,
82632
+ tokenUnitCost
82191
82633
  });
82192
82634
  }
82193
82635
  };
@@ -82484,6 +82926,7 @@ function registerShutdownHandlers(logger, onShutdown) {
82484
82926
  }
82485
82927
  async function main() {
82486
82928
  let logger;
82929
+ let pricingRefreshTimer = null;
82487
82930
  try {
82488
82931
  ensureDataDirectories();
82489
82932
  const config2 = loadConfig();
@@ -82491,6 +82934,12 @@ async function main() {
82491
82934
  logger.info(`IO Daemon v${APP_VERSION} starting...`);
82492
82935
  await initDatabase();
82493
82936
  logger.info("Database initialized");
82937
+ const pricingResult = await refreshModelPricing(logger);
82938
+ if (pricingResult.modelsUpdated === 0) {
82939
+ logger.warn("Model pricing refresh returned 0 models, seeding with fallback");
82940
+ await seedFromFallback();
82941
+ }
82942
+ logger.info({ modelsUpdated: pricingResult.modelsUpdated }, "Model pricing initialized");
82494
82943
  await scanSkills();
82495
82944
  logger.info("Skills scanned");
82496
82945
  const orchestrator2 = createOrchestrator(config2, eventBus);
@@ -82499,12 +82948,21 @@ async function main() {
82499
82948
  const scheduler = createScheduler(orchestrator2, eventBus);
82500
82949
  scheduler.start();
82501
82950
  logger.info("Scheduler started");
82951
+ const refreshIntervalMs = config2.pricingRefreshHours * 60 * 60 * 1e3;
82952
+ pricingRefreshTimer = setInterval(() => {
82953
+ void refreshModelPricing(logger).catch((err) => {
82954
+ logger?.warn({ err }, "Periodic model pricing refresh failed");
82955
+ });
82956
+ }, refreshIntervalMs);
82502
82957
  setChatOrchestrator(orchestrator2);
82503
82958
  const apiServer = createApiServer(config2);
82504
82959
  const telegramBot = createTelegramBot(config2, orchestrator2);
82505
82960
  telegramBot?.start();
82506
82961
  createTelegramNotifier(telegramBot, config2, eventBus);
82507
82962
  registerShutdownHandlers(logger, async () => {
82963
+ if (pricingRefreshTimer) {
82964
+ clearInterval(pricingRefreshTimer);
82965
+ }
82508
82966
  scheduler.stop();
82509
82967
  telegramBot?.stop();
82510
82968
  apiServer.server.close();