heyio 4.0.2 → 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);
@@ -78994,6 +79109,12 @@ function createApiServer(config2) {
78994
79109
  next();
78995
79110
  });
78996
79111
  app.use(import_express10.default.json({ limit: "1mb" }));
79112
+ app.get("/api/auth/config", (_req, res) => {
79113
+ res.json({
79114
+ supabaseUrl: config2.supabaseUrl ?? null,
79115
+ supabaseAnonKey: config2.supabaseAnonKey ?? null
79116
+ });
79117
+ });
78997
79118
  app.use("/api", createAuthMiddleware(config2));
78998
79119
  app.use(router2);
78999
79120
  app.use(router7);
@@ -79108,6 +79229,448 @@ function createLogger(name) {
79108
79229
  return getRootLogger().child({ subsystem: name });
79109
79230
  }
79110
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
+
79111
79674
  // packages/daemon/src/orchestrator/system-prompt.ts
79112
79675
  function formatSquadRoster(squads) {
79113
79676
  if (squads.length === 0) {
@@ -79200,146 +79763,13 @@ function buildFreshSessionContext(summary, wikiContext) {
79200
79763
  return sections.join("\n\n");
79201
79764
  }
79202
79765
 
79203
- // packages/daemon/src/copilot/router.ts
79204
- var FAST_KEYWORDS = [
79205
- "hi",
79206
- "hello",
79207
- "hey",
79208
- "thanks",
79209
- "thank you",
79210
- "status",
79211
- "list",
79212
- "show",
79213
- "ping",
79214
- "uptime",
79215
- "what time",
79216
- "ok",
79217
- "okay"
79218
- ];
79219
- var PREMIUM_KEYWORDS = [
79220
- "architecture",
79221
- "architect",
79222
- "design",
79223
- "tradeoff",
79224
- "trade-off",
79225
- "plan",
79226
- "planning",
79227
- "strategy",
79228
- "roadmap",
79229
- "migration",
79230
- "refactor",
79231
- "generate",
79232
- "implement",
79233
- "build",
79234
- "codebase",
79235
- "repository",
79236
- "system prompt",
79237
- "multi-step",
79238
- "complex",
79239
- "reason",
79240
- "compare"
79241
- ];
79242
- var SIMPLE_QUESTION_PREFIXES = [
79243
- "is",
79244
- "are",
79245
- "do",
79246
- "does",
79247
- "did",
79248
- "can",
79249
- "could",
79250
- "should",
79251
- "would",
79252
- "will"
79253
- ];
79254
- var MODEL_SWITCH_COOLDOWN_MESSAGES = 3;
79255
- var activeTier = null;
79256
- var messagesSinceLastSwitch = MODEL_SWITCH_COOLDOWN_MESSAGES;
79257
- function normalizeMessage(message2) {
79258
- return message2.trim().toLowerCase();
79259
- }
79260
- function getWordCount(message2) {
79261
- const normalized = message2.trim();
79262
- if (normalized.length === 0) {
79263
- return 0;
79264
- }
79265
- return normalized.split(/\s+/u).length;
79266
- }
79267
- function containsAny(message2, keywords) {
79268
- return keywords.some((keyword) => message2.includes(keyword));
79269
- }
79270
- function startsWithSimpleQuestion(message2) {
79271
- return SIMPLE_QUESTION_PREFIXES.some(
79272
- (prefix) => message2 === prefix || message2.startsWith(`${prefix} `)
79273
- );
79274
- }
79275
- function classifyMessage(message2) {
79276
- const normalized = normalizeMessage(message2);
79277
- const wordCount = getWordCount(normalized);
79278
- if (normalized.length === 0) {
79279
- return "fast";
79280
- }
79281
- if (containsAny(normalized, PREMIUM_KEYWORDS) || normalized.includes("```") || wordCount >= 80) {
79282
- return "premium";
79283
- }
79284
- if (wordCount <= 20 && (containsAny(normalized, FAST_KEYWORDS) || startsWithSimpleQuestion(normalized) || /^(yes|no|sure|okay|ok|thanks)[!.?]*$/u.test(normalized))) {
79285
- return "fast";
79286
- }
79287
- if (wordCount <= 8 && /^(what|when|where|who)\b/u.test(normalized)) {
79288
- return "fast";
79289
- }
79290
- return "standard";
79291
- }
79292
- function getModelForTier(tier) {
79293
- switch (tier) {
79294
- case "fast":
79295
- return FAST_MODEL;
79296
- case "premium":
79297
- return PREMIUM_MODEL;
79298
- default:
79299
- return STANDARD_MODEL;
79300
- }
79301
- }
79302
- function routeMessage(message2) {
79303
- const requestedTier = classifyMessage(message2);
79304
- if (activeTier === null) {
79305
- activeTier = requestedTier;
79306
- messagesSinceLastSwitch = 0;
79307
- return {
79308
- requestedTier,
79309
- effectiveTier: activeTier,
79310
- model: getModelForTier(activeTier),
79311
- switched: true,
79312
- cooldownRemaining: MODEL_SWITCH_COOLDOWN_MESSAGES
79313
- };
79314
- }
79315
- if (requestedTier !== activeTier && messagesSinceLastSwitch >= MODEL_SWITCH_COOLDOWN_MESSAGES) {
79316
- activeTier = requestedTier;
79317
- messagesSinceLastSwitch = 0;
79318
- return {
79319
- requestedTier,
79320
- effectiveTier: activeTier,
79321
- model: getModelForTier(activeTier),
79322
- switched: true,
79323
- cooldownRemaining: MODEL_SWITCH_COOLDOWN_MESSAGES
79324
- };
79325
- }
79326
- messagesSinceLastSwitch += 1;
79327
- return {
79328
- requestedTier,
79329
- effectiveTier: activeTier,
79330
- model: getModelForTier(activeTier),
79331
- switched: false,
79332
- cooldownRemaining: Math.max(MODEL_SWITCH_COOLDOWN_MESSAGES - messagesSinceLastSwitch, 0)
79333
- };
79334
- }
79335
-
79336
79766
  // packages/daemon/src/copilot/session.ts
79337
79767
  import {
79338
79768
  approveAll
79339
79769
  } from "@github/copilot-sdk";
79340
79770
 
79341
79771
  // packages/daemon/src/copilot/client.ts
79342
- import { execFileSync } from "node:child_process";
79772
+ import { execFileSync as execFileSync2 } from "node:child_process";
79343
79773
  import { mkdirSync as mkdirSync2 } from "node:fs";
79344
79774
  import { join as join6 } from "node:path";
79345
79775
  import { CopilotClient } from "@github/copilot-sdk";
@@ -79353,7 +79783,7 @@ function readTokenFromEnvironment() {
79353
79783
  }
79354
79784
  function readTokenFromGhCli() {
79355
79785
  try {
79356
- const token = execFileSync("gh", ["auth", "token"], {
79786
+ const token = execFileSync2("gh", ["auth", "token"], {
79357
79787
  encoding: "utf8",
79358
79788
  stdio: ["ignore", "pipe", "pipe"]
79359
79789
  }).trim();
@@ -79362,7 +79792,7 @@ function readTokenFromGhCli() {
79362
79792
  return void 0;
79363
79793
  }
79364
79794
  }
79365
- function resolveGitHubToken() {
79795
+ function resolveGitHubToken2() {
79366
79796
  return readTokenFromEnvironment() ?? readTokenFromGhCli();
79367
79797
  }
79368
79798
  function buildClientOptions(token) {
@@ -79398,7 +79828,7 @@ async function initCopilotClient() {
79398
79828
  }
79399
79829
  copilotClientInitPromise = (async () => {
79400
79830
  try {
79401
- const token = resolveGitHubToken();
79831
+ const token = resolveGitHubToken2();
79402
79832
  if (token === void 0) {
79403
79833
  throw new Error(
79404
79834
  `Unable to find a GitHub token for Copilot SDK authentication. ${COPILOT_AUTH_ERROR_HINT}`
@@ -80226,13 +80656,24 @@ function mergeUsage(target, usage) {
80226
80656
  }
80227
80657
  async function persistUsage(member, usageEvents) {
80228
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;
80229
80668
  await recordUsage({
80230
80669
  squadId: member.squadId,
80231
80670
  agentId: member.id,
80232
- model: usage.model,
80671
+ model,
80233
80672
  inputTokens: usage.inputTokens ?? 0,
80234
80673
  outputTokens: usage.outputTokens ?? 0,
80235
- cost: usage.cost ?? 0
80674
+ cost: 0,
80675
+ premiumRequestCost,
80676
+ tokenUnitCost
80236
80677
  });
80237
80678
  }
80238
80679
  }
@@ -80450,7 +80891,7 @@ async function executeAgentTask(member, task, worktreePath, options2) {
80450
80891
  client2 = new CopilotClient2({ workingDirectory: worktreePath });
80451
80892
  await client2.start();
80452
80893
  const session = await client2.createSession({
80453
- model: member.model ?? STANDARD_MODEL,
80894
+ model: member.model ?? DEFAULT_MODEL,
80454
80895
  workingDirectory: worktreePath,
80455
80896
  tools,
80456
80897
  availableTools: ["custom:*"],
@@ -80691,7 +81132,7 @@ Return strict JSON in this shape:
80691
81132
  client2 = new CopilotClient3({ workingDirectory: repoPath });
80692
81133
  await client2.start();
80693
81134
  const session = await client2.createSession({
80694
- model: PREMIUM_MODEL,
81135
+ model: DEFAULT_MODEL,
80695
81136
  workingDirectory: repoPath,
80696
81137
  onPermissionRequest: approveAll3,
80697
81138
  systemMessage: {
@@ -80962,7 +81403,7 @@ Return strict JSON:
80962
81403
  client2 = new CopilotClient4({ workingDirectory: worktreePath });
80963
81404
  await client2.start();
80964
81405
  const session = await client2.createSession({
80965
- model: qaMember.model ?? STANDARD_MODEL,
81406
+ model: qaMember.model ?? DEFAULT_MODEL,
80966
81407
  workingDirectory: worktreePath,
80967
81408
  onPermissionRequest: approveAll4,
80968
81409
  systemMessage: {
@@ -81082,7 +81523,7 @@ Return strict JSON:
81082
81523
  client2 = new CopilotClient5();
81083
81524
  await client2.start();
81084
81525
  const session = await client2.createSession({
81085
- model: teamLead.model ?? PREMIUM_MODEL,
81526
+ model: teamLead.model ?? DEFAULT_MODEL,
81086
81527
  onPermissionRequest: approveAll5,
81087
81528
  systemMessage: {
81088
81529
  content: `${TEAM_LEAD_PROMPT}
@@ -81537,12 +81978,9 @@ function buildMandatoryRoles() {
81537
81978
  function modelForRole(role) {
81538
81979
  const normalized = slugifyRole(role);
81539
81980
  if (normalized === "team-lead") {
81540
- return PREMIUM_MODEL;
81541
- }
81542
- if (normalized === "qa") {
81543
- return STANDARD_MODEL;
81981
+ return DEFAULT_MODEL;
81544
81982
  }
81545
- return STANDARD_MODEL;
81983
+ return DEFAULT_MODEL;
81546
81984
  }
81547
81985
  function systemPromptForRole(role, repoContext) {
81548
81986
  const normalized = slugifyRole(role);
@@ -81574,7 +82012,7 @@ ${ROLE_GENERATION_PROMPT}`;
81574
82012
  client2 = new CopilotClient6();
81575
82013
  await client2.start();
81576
82014
  const session = await client2.createSession({
81577
- model: PREMIUM_MODEL,
82015
+ model: DEFAULT_MODEL,
81578
82016
  onPermissionRequest: approveAll6,
81579
82017
  systemMessage: {
81580
82018
  content: ROLE_GENERATION_PROMPT
@@ -82048,8 +82486,7 @@ var Orchestrator = class {
82048
82486
  skillsContext,
82049
82487
  conversationSummary: [this.latestResetSummary, conversationSummary].filter(Boolean).join("\n\n")
82050
82488
  });
82051
- const route = routeMessage(normalizedMessage);
82052
- await this.refreshSession(route.model, systemPrompt);
82489
+ await this.refreshSession(this.config.defaultModel, systemPrompt);
82053
82490
  const sendResult = await sendMessage(
82054
82491
  this.requireActiveSession(),
82055
82492
  normalizedMessage,
@@ -82177,11 +82614,22 @@ var Orchestrator = class {
82177
82614
  if (usage.inputTokens === 0 && usage.outputTokens === 0) {
82178
82615
  return;
82179
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;
82180
82626
  await recordUsage({
82181
- model: usage.model || this.activeModel || this.config.defaultModel,
82627
+ model,
82182
82628
  inputTokens: usage.inputTokens,
82183
82629
  outputTokens: usage.outputTokens,
82184
- cost: 0
82630
+ cost: 0,
82631
+ premiumRequestCost,
82632
+ tokenUnitCost
82185
82633
  });
82186
82634
  }
82187
82635
  };
@@ -82478,6 +82926,7 @@ function registerShutdownHandlers(logger, onShutdown) {
82478
82926
  }
82479
82927
  async function main() {
82480
82928
  let logger;
82929
+ let pricingRefreshTimer = null;
82481
82930
  try {
82482
82931
  ensureDataDirectories();
82483
82932
  const config2 = loadConfig();
@@ -82485,6 +82934,12 @@ async function main() {
82485
82934
  logger.info(`IO Daemon v${APP_VERSION} starting...`);
82486
82935
  await initDatabase();
82487
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");
82488
82943
  await scanSkills();
82489
82944
  logger.info("Skills scanned");
82490
82945
  const orchestrator2 = createOrchestrator(config2, eventBus);
@@ -82493,12 +82948,21 @@ async function main() {
82493
82948
  const scheduler = createScheduler(orchestrator2, eventBus);
82494
82949
  scheduler.start();
82495
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);
82496
82957
  setChatOrchestrator(orchestrator2);
82497
82958
  const apiServer = createApiServer(config2);
82498
82959
  const telegramBot = createTelegramBot(config2, orchestrator2);
82499
82960
  telegramBot?.start();
82500
82961
  createTelegramNotifier(telegramBot, config2, eventBus);
82501
82962
  registerShutdownHandlers(logger, async () => {
82963
+ if (pricingRefreshTimer) {
82964
+ clearInterval(pricingRefreshTimer);
82965
+ }
82502
82966
  scheduler.stop();
82503
82967
  telegramBot?.stop();
82504
82968
  apiServer.server.close();