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.
@@ -75,18 +75,15 @@ var init_api = __esm({
75
75
  });
76
76
 
77
77
  // packages/shared/dist/constants.js
78
- var APP_NAME, APP_VERSION, API_PORT, API_HOST, DEFAULT_MODEL, FAST_MODEL, STANDARD_MODEL, PREMIUM_MODEL, SESSION_RESET_THRESHOLD, SCHEDULER_INTERVAL_MS, QA_MAX_REVISIONS, MANDATORY_ROLES, EVENT_NAMES;
78
+ var APP_NAME, APP_VERSION, API_PORT, API_HOST, DEFAULT_MODEL, SESSION_RESET_THRESHOLD, SCHEDULER_INTERVAL_MS, QA_MAX_REVISIONS, MANDATORY_ROLES, EVENT_NAMES;
79
79
  var init_constants = __esm({
80
80
  "packages/shared/dist/constants.js"() {
81
81
  "use strict";
82
82
  APP_NAME = "io";
83
- APP_VERSION = "4.0.0";
83
+ APP_VERSION = "4.0.4";
84
84
  API_PORT = 7777;
85
85
  API_HOST = "0.0.0.0";
86
- DEFAULT_MODEL = "gpt-4.1";
87
- FAST_MODEL = "gpt-4.1-mini";
88
- STANDARD_MODEL = "claude-sonnet-4.6";
89
- PREMIUM_MODEL = "claude-sonnet-4.6";
86
+ DEFAULT_MODEL = "gpt-4o";
90
87
  SESSION_RESET_THRESHOLD = 50;
91
88
  SCHEDULER_INTERVAL_MS = 6e4;
92
89
  QA_MAX_REVISIONS = 3;
@@ -2067,6 +2064,27 @@ var init_db = __esm({
2067
2064
  "CREATE INDEX IF NOT EXISTS idx_activity_objective_id ON activity(objective_id)",
2068
2065
  "CREATE INDEX IF NOT EXISTS idx_agent_history_agent_id_created_at ON agent_history(agent_id, created_at)"
2069
2066
  ]
2067
+ },
2068
+ {
2069
+ version: 2,
2070
+ name: "add-model-pricing-and-dual-costs",
2071
+ statements: [
2072
+ `CREATE TABLE IF NOT EXISTS model_pricing (
2073
+ id TEXT PRIMARY KEY,
2074
+ display_name TEXT NOT NULL,
2075
+ premium_multiplier REAL,
2076
+ token_input_multiplier REAL,
2077
+ token_output_multiplier REAL,
2078
+ cached_input_multiplier REAL,
2079
+ tier TEXT NOT NULL,
2080
+ available INTEGER NOT NULL DEFAULT 1,
2081
+ updated_at TEXT NOT NULL
2082
+ )`,
2083
+ "CREATE INDEX IF NOT EXISTS idx_model_pricing_tier ON model_pricing(tier)",
2084
+ "CREATE INDEX IF NOT EXISTS idx_model_pricing_available ON model_pricing(available)",
2085
+ "ALTER TABLE token_usage ADD COLUMN premium_request_cost REAL",
2086
+ "ALTER TABLE token_usage ADD COLUMN token_unit_cost REAL"
2087
+ ]
2070
2088
  }
2071
2089
  ];
2072
2090
  client = null;
@@ -11817,8 +11835,8 @@ async function recordUsage(data, db) {
11817
11835
  createdAt: data.createdAt ?? nowIso()
11818
11836
  };
11819
11837
  await database.execute({
11820
- sql: `INSERT INTO token_usage (id, squad_id, agent_id, model, input_tokens, output_tokens, cost, created_at)
11821
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
11838
+ sql: `INSERT INTO token_usage (id, squad_id, agent_id, model, input_tokens, output_tokens, cost, premium_request_cost, token_unit_cost, created_at)
11839
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11822
11840
  args: [
11823
11841
  usage.id,
11824
11842
  usage.squadId,
@@ -11827,6 +11845,8 @@ async function recordUsage(data, db) {
11827
11845
  usage.inputTokens,
11828
11846
  usage.outputTokens,
11829
11847
  usage.cost,
11848
+ data.premiumRequestCost ?? null,
11849
+ data.tokenUnitCost ?? null,
11830
11850
  usage.createdAt
11831
11851
  ]
11832
11852
  });
@@ -51543,6 +51563,9 @@ function readEnvOverrides() {
51543
51563
  if (process.env.IO_SESSION_RESET_THRESHOLD !== void 0) {
51544
51564
  overrides.sessionResetThreshold = process.env.IO_SESSION_RESET_THRESHOLD;
51545
51565
  }
51566
+ if (process.env.IO_PRICING_REFRESH_HOURS !== void 0) {
51567
+ overrides.pricingRefreshHours = process.env.IO_PRICING_REFRESH_HOURS;
51568
+ }
51546
51569
  return overrides;
51547
51570
  }
51548
51571
  function loadConfig() {
@@ -51569,7 +51592,8 @@ var init_config = __esm({
51569
51592
  telegramUserId: external_exports.string().trim().min(1).nullable().default(null),
51570
51593
  supabaseUrl: external_exports.string().trim().min(1).nullable().default(null),
51571
51594
  supabaseAnonKey: external_exports.string().trim().min(1).nullable().default(null),
51572
- sessionResetThreshold: external_exports.coerce.number().int().positive().default(SESSION_RESET_THRESHOLD)
51595
+ sessionResetThreshold: external_exports.coerce.number().int().positive().default(SESSION_RESET_THRESHOLD),
51596
+ pricingRefreshHours: external_exports.coerce.number().positive().default(24)
51573
51597
  });
51574
51598
  }
51575
51599
  });
@@ -51650,7 +51674,8 @@ var init_settings = __esm({
51650
51674
  "telegramUserId",
51651
51675
  "supabaseUrl",
51652
51676
  "supabaseAnonKey",
51653
- "sessionResetThreshold"
51677
+ "sessionResetThreshold",
51678
+ "pricingRefreshHours"
51654
51679
  ];
51655
51680
  router5.get("/api/settings", async (_req, res) => {
51656
51681
  try {
@@ -51795,7 +51820,78 @@ function chooseEntryFileName(url2, contentType) {
51795
51820
  function isMissingFileError2(error51) {
51796
51821
  return !!error51 && typeof error51 === "object" && "code" in error51 && error51.code === "ENOENT";
51797
51822
  }
51798
- var import_express6, router6;
51823
+ async function discoverSkillsSh(query) {
51824
+ if (!query) return [];
51825
+ const endpoint = `https://skills.sh/api/search?q=${encodeURIComponent(query)}&limit=50`;
51826
+ const response = await fetch(endpoint, { signal: AbortSignal.timeout(1e4) });
51827
+ if (!response.ok) {
51828
+ throw new Error(`skills.sh returned ${response.status}`);
51829
+ }
51830
+ const data = await response.json();
51831
+ const installedSkills = await readInstalledSkills();
51832
+ const installedIds = new Set(installedSkills.map((s) => s.id));
51833
+ return (data.skills ?? []).map((entry) => {
51834
+ const skillUrl = entry.source ? `https://raw.githubusercontent.com/${entry.source}/main/skills/${entry.skillId}/SKILL.md` : "";
51835
+ return {
51836
+ name: entry.name || entry.skillId,
51837
+ title: entry.name || entry.skillId,
51838
+ description: `${entry.source ?? ""}`,
51839
+ url: skillUrl,
51840
+ source: "skillssh",
51841
+ installed: installedIds.has(`skillssh:${normalizeSlug(entry.skillId || entry.name)}`),
51842
+ registrySource: entry.source ?? void 0,
51843
+ skillId: entry.skillId ?? void 0,
51844
+ installs: entry.installs ?? 0
51845
+ };
51846
+ });
51847
+ }
51848
+ async function discoverAwesomeCopilot(query) {
51849
+ const now = Date.now();
51850
+ if (!awesomeCopilotCache || now - awesomeCopilotCache.fetchedAt > AWESOME_COPILOT_CACHE_TTL) {
51851
+ awesomeCopilotCache = { skills: await fetchAwesomeCopilotList(), fetchedAt: now };
51852
+ }
51853
+ const skills = awesomeCopilotCache.skills;
51854
+ if (!query) return skills;
51855
+ const needle = query.toLowerCase();
51856
+ return skills.filter(
51857
+ (s) => s.name.toLowerCase().includes(needle) || s.title.toLowerCase().includes(needle) || s.description.toLowerCase().includes(needle)
51858
+ );
51859
+ }
51860
+ async function fetchAwesomeCopilotList() {
51861
+ const treeUrl = "https://api.github.com/repos/github/awesome-copilot/git/trees/main?recursive=1";
51862
+ const headers = {
51863
+ Accept: "application/vnd.github.v3+json",
51864
+ "User-Agent": "io-daemon"
51865
+ };
51866
+ if (process.env.GITHUB_TOKEN) {
51867
+ headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
51868
+ }
51869
+ const response = await fetch(treeUrl, { headers, signal: AbortSignal.timeout(15e3) });
51870
+ if (!response.ok) {
51871
+ throw new Error(`GitHub API returned ${response.status}`);
51872
+ }
51873
+ const data = await response.json();
51874
+ const installedSkills = await readInstalledSkills();
51875
+ const installedIds = new Set(installedSkills.map((s) => s.id));
51876
+ const skillEntries = (data.tree ?? []).filter(
51877
+ (entry) => entry.type === "blob" && entry.path.startsWith("skills/") && entry.path.endsWith("/SKILL.md")
51878
+ );
51879
+ return skillEntries.map((entry) => {
51880
+ const parts = entry.path.split("/");
51881
+ const skillName = parts[1] ?? "unknown";
51882
+ const slug = normalizeSlug(skillName);
51883
+ const rawUrl = `https://raw.githubusercontent.com/github/awesome-copilot/main/${entry.path}`;
51884
+ return {
51885
+ name: skillName,
51886
+ title: skillName.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
51887
+ description: "From github/awesome-copilot",
51888
+ url: rawUrl,
51889
+ source: "awesome-copilot",
51890
+ installed: installedIds.has(`awesome-copilot:${slug}`)
51891
+ };
51892
+ });
51893
+ }
51894
+ var import_express6, router6, awesomeCopilotCache, AWESOME_COPILOT_CACHE_TTL;
51799
51895
  var init_skills = __esm({
51800
51896
  "packages/daemon/src/api/routes/skills.ts"() {
51801
51897
  "use strict";
@@ -51844,9 +51940,28 @@ var init_skills = __esm({
51844
51940
  });
51845
51941
  }
51846
51942
  });
51847
- router6.get("/api/skills/discover", async (_req, res) => {
51848
- res.status(200).json([]);
51943
+ router6.get("/api/skills/discover", async (req, res) => {
51944
+ try {
51945
+ const source = typeof req.query.source === "string" ? req.query.source : "";
51946
+ const query = typeof req.query.q === "string" ? req.query.q.trim() : "";
51947
+ if (source === "skillssh") {
51948
+ const skills = await discoverSkillsSh(query);
51949
+ res.status(200).json(skills);
51950
+ } else if (source === "awesome-copilot") {
51951
+ const skills = await discoverAwesomeCopilot(query);
51952
+ res.status(200).json(skills);
51953
+ } else {
51954
+ res.status(400).json({ error: `Unknown source: ${source}` });
51955
+ }
51956
+ } catch (error51) {
51957
+ res.status(500).json({
51958
+ error: "Failed to discover skills",
51959
+ details: error51 instanceof Error ? error51.message : "Unknown error"
51960
+ });
51961
+ }
51849
51962
  });
51963
+ awesomeCopilotCache = null;
51964
+ AWESOME_COPILOT_CACHE_TTL = 60 * 60 * 1e3;
51850
51965
  }
51851
51966
  });
51852
51967
 
@@ -59905,6 +60020,12 @@ function createApiServer(config2) {
59905
60020
  next();
59906
60021
  });
59907
60022
  app.use(import_express10.default.json({ limit: "1mb" }));
60023
+ app.get("/api/auth/config", (_req, res) => {
60024
+ res.json({
60025
+ supabaseUrl: config2.supabaseUrl ?? null,
60026
+ supabaseAnonKey: config2.supabaseAnonKey ?? null
60027
+ });
60028
+ });
59908
60029
  app.use("/api", createAuthMiddleware(config2));
59909
60030
  app.use(router2);
59910
60031
  app.use(router7);
@@ -66894,6 +67015,493 @@ var init_logger = __esm({
66894
67015
  }
66895
67016
  });
66896
67017
 
67018
+ // packages/daemon/src/models/catalog.ts
67019
+ import { execFileSync } from "node:child_process";
67020
+ function resolveGitHubToken() {
67021
+ const envToken = process.env.GITHUB_TOKEN?.trim();
67022
+ if (envToken && envToken.length > 0) {
67023
+ return envToken;
67024
+ }
67025
+ try {
67026
+ const token = execFileSync("gh", ["auth", "token"], {
67027
+ encoding: "utf8",
67028
+ stdio: ["ignore", "pipe", "pipe"]
67029
+ }).trim();
67030
+ return token.length > 0 ? token : void 0;
67031
+ } catch {
67032
+ return void 0;
67033
+ }
67034
+ }
67035
+ async function fetchModelCatalog() {
67036
+ const token = resolveGitHubToken();
67037
+ if (!token) {
67038
+ throw new Error("No GitHub token available for model catalog fetch");
67039
+ }
67040
+ const response = await fetch(CATALOG_URL, {
67041
+ headers: {
67042
+ Accept: "application/json",
67043
+ Authorization: `Bearer ${token}`,
67044
+ "X-GitHub-Api-Version": "2024-12-01"
67045
+ }
67046
+ });
67047
+ if (!response.ok) {
67048
+ throw new Error(`Model catalog fetch failed: ${response.status} ${response.statusText}`);
67049
+ }
67050
+ const data = await response.json();
67051
+ if (!Array.isArray(data)) {
67052
+ throw new Error("Model catalog response is not an array");
67053
+ }
67054
+ const models = [];
67055
+ for (const entry of data) {
67056
+ const id = entry.id ?? entry.name;
67057
+ const displayName = entry.friendly_name ?? entry.name ?? id;
67058
+ if (id && typeof id === "string") {
67059
+ models.push({ id, displayName: displayName ?? id });
67060
+ }
67061
+ }
67062
+ return models;
67063
+ }
67064
+ var CATALOG_URL;
67065
+ var init_catalog = __esm({
67066
+ "packages/daemon/src/models/catalog.ts"() {
67067
+ "use strict";
67068
+ CATALOG_URL = "https://models.github.ai/catalog/models";
67069
+ }
67070
+ });
67071
+
67072
+ // packages/daemon/src/models/pricing-scraper.ts
67073
+ async function scrapeTokenUnitPricing() {
67074
+ const html = await fetchPage(TOKEN_UNIT_COSTS_URL);
67075
+ return parseTokenUnitTable(html);
67076
+ }
67077
+ async function scrapePremiumRequestPricing() {
67078
+ const html = await fetchPage(PREMIUM_MULTIPLIERS_URL);
67079
+ return parsePremiumMultiplierTable(html);
67080
+ }
67081
+ async function fetchPage(url2) {
67082
+ const response = await fetch(url2, {
67083
+ headers: {
67084
+ Accept: "text/html",
67085
+ "User-Agent": "IO-Daemon/4.0 (pricing-refresh)"
67086
+ },
67087
+ redirect: "follow"
67088
+ });
67089
+ if (!response.ok) {
67090
+ throw new Error(`Failed to fetch ${url2}: ${response.status} ${response.statusText}`);
67091
+ }
67092
+ return response.text();
67093
+ }
67094
+ function parseTokenUnitTable(html) {
67095
+ const results = [];
67096
+ const tableRowPattern = /<tr[^>]*>\s*<td[^>]*>([^<]+)<\/td>\s*<td[^>]*>([^<]+)<\/td>\s*<td[^>]*>([^<]*)<\/td>\s*<td[^>]*>([^<]+)<\/td>/gi;
67097
+ for (const match of html.matchAll(tableRowPattern)) {
67098
+ const modelName = cleanCellText(match[1]);
67099
+ const inputMultiplier = Number.parseFloat(match[2]);
67100
+ const cachedInput = match[3].trim().toLowerCase();
67101
+ const outputMultiplier = Number.parseFloat(match[4]);
67102
+ if (modelName && !Number.isNaN(inputMultiplier) && !Number.isNaN(outputMultiplier)) {
67103
+ results.push({
67104
+ modelName,
67105
+ inputMultiplier,
67106
+ cachedInputMultiplier: cachedInput === "n/a" || cachedInput === "" ? null : Number.parseFloat(cachedInput) || null,
67107
+ outputMultiplier
67108
+ });
67109
+ }
67110
+ }
67111
+ return results;
67112
+ }
67113
+ function parsePremiumMultiplierTable(html) {
67114
+ const results = [];
67115
+ const tableRowPattern = /<tr[^>]*>\s*<td[^>]*>([^<]+)<\/td>\s*<td[^>]*>([^<]+)<\/td>\s*<\/tr>/gi;
67116
+ for (const match of html.matchAll(tableRowPattern)) {
67117
+ const modelName = cleanCellText(match[1]);
67118
+ const multiplier = Number.parseFloat(match[2]);
67119
+ if (modelName && !Number.isNaN(multiplier) && multiplier > 0) {
67120
+ results.push({ modelName, multiplier });
67121
+ }
67122
+ }
67123
+ return results;
67124
+ }
67125
+ function cleanCellText(text) {
67126
+ return text.replace(/<[^>]*>/g, "").replace(/&[^;]+;/g, " ").trim();
67127
+ }
67128
+ var TOKEN_UNIT_COSTS_URL, PREMIUM_MULTIPLIERS_URL;
67129
+ var init_pricing_scraper = __esm({
67130
+ "packages/daemon/src/models/pricing-scraper.ts"() {
67131
+ "use strict";
67132
+ TOKEN_UNIT_COSTS_URL = "https://docs.github.com/en/billing/reference/costs-for-github-models";
67133
+ PREMIUM_MULTIPLIERS_URL = "https://docs.github.com/en/copilot/reference/copilot-billing/request-based-billing-legacy/model-multipliers-for-annual-plans";
67134
+ }
67135
+ });
67136
+
67137
+ // packages/daemon/src/models/seed.ts
67138
+ var SEED_MODELS;
67139
+ var init_seed = __esm({
67140
+ "packages/daemon/src/models/seed.ts"() {
67141
+ "use strict";
67142
+ SEED_MODELS = [
67143
+ {
67144
+ id: "gpt-4o-mini",
67145
+ displayName: "OpenAI GPT-4o mini",
67146
+ premiumMultiplier: 0.33,
67147
+ tokenInputMultiplier: 0.015,
67148
+ tokenOutputMultiplier: 0.06,
67149
+ cachedInputMultiplier: 75e-4,
67150
+ tier: "trivial",
67151
+ available: true
67152
+ },
67153
+ {
67154
+ id: "gpt-4o",
67155
+ displayName: "OpenAI GPT-4o",
67156
+ premiumMultiplier: 0.33,
67157
+ tokenInputMultiplier: 0.25,
67158
+ tokenOutputMultiplier: 1,
67159
+ cachedInputMultiplier: 0.125,
67160
+ tier: "trivial",
67161
+ available: true
67162
+ },
67163
+ {
67164
+ id: "gpt-5-mini",
67165
+ displayName: "GPT-5 mini",
67166
+ premiumMultiplier: 0.33,
67167
+ tokenInputMultiplier: null,
67168
+ tokenOutputMultiplier: null,
67169
+ cachedInputMultiplier: null,
67170
+ tier: "trivial",
67171
+ available: true
67172
+ },
67173
+ {
67174
+ id: "claude-sonnet-4",
67175
+ displayName: "Claude Sonnet 4",
67176
+ premiumMultiplier: 6,
67177
+ tokenInputMultiplier: 0.6,
67178
+ tokenOutputMultiplier: 2.4,
67179
+ cachedInputMultiplier: null,
67180
+ tier: "premium",
67181
+ available: true
67182
+ },
67183
+ {
67184
+ id: "claude-haiku-4.5",
67185
+ displayName: "Claude Haiku 4.5",
67186
+ premiumMultiplier: 0.33,
67187
+ tokenInputMultiplier: null,
67188
+ tokenOutputMultiplier: null,
67189
+ cachedInputMultiplier: null,
67190
+ tier: "trivial",
67191
+ available: true
67192
+ },
67193
+ {
67194
+ id: "gemini-2.5-pro",
67195
+ displayName: "Gemini 2.5 Pro",
67196
+ premiumMultiplier: 1,
67197
+ tokenInputMultiplier: null,
67198
+ tokenOutputMultiplier: null,
67199
+ cachedInputMultiplier: null,
67200
+ tier: "fast",
67201
+ available: true
67202
+ },
67203
+ {
67204
+ id: "gpt-5.1",
67205
+ displayName: "GPT-5.1",
67206
+ premiumMultiplier: 3,
67207
+ tokenInputMultiplier: null,
67208
+ tokenOutputMultiplier: null,
67209
+ cachedInputMultiplier: null,
67210
+ tier: "standard",
67211
+ available: true
67212
+ },
67213
+ {
67214
+ id: "gpt-5.2",
67215
+ displayName: "GPT-5.2",
67216
+ premiumMultiplier: 3,
67217
+ tokenInputMultiplier: null,
67218
+ tokenOutputMultiplier: null,
67219
+ cachedInputMultiplier: null,
67220
+ tier: "standard",
67221
+ available: true
67222
+ },
67223
+ {
67224
+ id: "gpt-5.4",
67225
+ displayName: "GPT-5.4",
67226
+ premiumMultiplier: 6,
67227
+ tokenInputMultiplier: null,
67228
+ tokenOutputMultiplier: null,
67229
+ cachedInputMultiplier: null,
67230
+ tier: "premium",
67231
+ available: true
67232
+ },
67233
+ {
67234
+ id: "claude-opus-4.5",
67235
+ displayName: "Claude Opus 4.5",
67236
+ premiumMultiplier: 15,
67237
+ tokenInputMultiplier: null,
67238
+ tokenOutputMultiplier: null,
67239
+ cachedInputMultiplier: null,
67240
+ tier: "premium",
67241
+ available: true
67242
+ },
67243
+ {
67244
+ id: "claude-opus-4.6",
67245
+ displayName: "Claude Opus 4.6",
67246
+ premiumMultiplier: 27,
67247
+ tokenInputMultiplier: null,
67248
+ tokenOutputMultiplier: null,
67249
+ cachedInputMultiplier: null,
67250
+ tier: "ultra",
67251
+ available: true
67252
+ },
67253
+ {
67254
+ id: "claude-opus-4.7",
67255
+ displayName: "Claude Opus 4.7",
67256
+ premiumMultiplier: 27,
67257
+ tokenInputMultiplier: null,
67258
+ tokenOutputMultiplier: null,
67259
+ cachedInputMultiplier: null,
67260
+ tier: "ultra",
67261
+ available: true
67262
+ },
67263
+ {
67264
+ id: "gpt-5.5",
67265
+ displayName: "GPT-5.5",
67266
+ premiumMultiplier: 57,
67267
+ tokenInputMultiplier: null,
67268
+ tokenOutputMultiplier: null,
67269
+ cachedInputMultiplier: null,
67270
+ tier: "ultra",
67271
+ available: true
67272
+ }
67273
+ ];
67274
+ }
67275
+ });
67276
+
67277
+ // packages/daemon/src/models/types.ts
67278
+ function computeTierFromMultiplier(premiumMultiplier) {
67279
+ if (premiumMultiplier === null) {
67280
+ return "standard";
67281
+ }
67282
+ for (const [tier, range] of Object.entries(TIER_RANGES)) {
67283
+ if (premiumMultiplier >= range.min && premiumMultiplier <= range.max) {
67284
+ return tier;
67285
+ }
67286
+ }
67287
+ return "ultra";
67288
+ }
67289
+ var TIER_RANGES, TOKEN_UNIT_PRICE;
67290
+ var init_types = __esm({
67291
+ "packages/daemon/src/models/types.ts"() {
67292
+ "use strict";
67293
+ TIER_RANGES = {
67294
+ trivial: { min: 0, max: 0.33 },
67295
+ fast: { min: 0.34, max: 1 },
67296
+ standard: { min: 1.1, max: 5 },
67297
+ premium: { min: 5.1, max: 15 },
67298
+ ultra: { min: 15.1, max: Number.POSITIVE_INFINITY }
67299
+ };
67300
+ TOKEN_UNIT_PRICE = 1e-5;
67301
+ }
67302
+ });
67303
+
67304
+ // packages/daemon/src/models/registry.ts
67305
+ function normalizeModelName(name) {
67306
+ return name.toLowerCase().replace(/^openai\s+/i, "").replace(/\s+/g, "-").replace(/[^a-z0-9.\-]/g, "").trim();
67307
+ }
67308
+ async function fetchCatalogIntoMap(modelMap, result, logger) {
67309
+ try {
67310
+ const catalogModels = await fetchModelCatalog();
67311
+ result.catalogFetched = true;
67312
+ for (const m of catalogModels) {
67313
+ const key = normalizeModelName(m.id);
67314
+ modelMap.set(key, { id: m.id, displayName: m.displayName, available: true });
67315
+ }
67316
+ } catch (error51) {
67317
+ const msg = error51 instanceof Error ? error51.message : String(error51);
67318
+ result.errors.push(`Catalog fetch failed: ${msg}`);
67319
+ logger?.warn(`Model catalog fetch failed: ${msg}`);
67320
+ }
67321
+ }
67322
+ async function scrapeTokenPricingIntoMap(modelMap, result, logger) {
67323
+ try {
67324
+ const tokenPricing = await scrapeTokenUnitPricing();
67325
+ result.tokenPricingScraped = true;
67326
+ for (const tp of tokenPricing) {
67327
+ const key = normalizeModelName(tp.modelName);
67328
+ const existing = modelMap.get(key) ?? findClosestKey(modelMap, key);
67329
+ if (existing) {
67330
+ existing.tokenInputMultiplier = tp.inputMultiplier;
67331
+ existing.tokenOutputMultiplier = tp.outputMultiplier;
67332
+ existing.cachedInputMultiplier = tp.cachedInputMultiplier;
67333
+ } else {
67334
+ modelMap.set(key, {
67335
+ id: key,
67336
+ displayName: tp.modelName,
67337
+ tokenInputMultiplier: tp.inputMultiplier,
67338
+ tokenOutputMultiplier: tp.outputMultiplier,
67339
+ cachedInputMultiplier: tp.cachedInputMultiplier,
67340
+ available: true
67341
+ });
67342
+ }
67343
+ }
67344
+ } catch (error51) {
67345
+ const msg = error51 instanceof Error ? error51.message : String(error51);
67346
+ result.errors.push(`Token pricing scrape failed: ${msg}`);
67347
+ logger?.warn(`Token pricing scrape failed: ${msg}`);
67348
+ }
67349
+ }
67350
+ async function scrapePremiumPricingIntoMap(modelMap, result, logger) {
67351
+ try {
67352
+ const premiumPricing = await scrapePremiumRequestPricing();
67353
+ result.premiumPricingScraped = true;
67354
+ for (const pp of premiumPricing) {
67355
+ const key = normalizeModelName(pp.modelName);
67356
+ const existing = modelMap.get(key) ?? findClosestKey(modelMap, key);
67357
+ if (existing) {
67358
+ existing.premiumMultiplier = pp.multiplier;
67359
+ } else {
67360
+ modelMap.set(key, {
67361
+ id: key,
67362
+ displayName: pp.modelName,
67363
+ premiumMultiplier: pp.multiplier,
67364
+ available: true
67365
+ });
67366
+ }
67367
+ }
67368
+ } catch (error51) {
67369
+ const msg = error51 instanceof Error ? error51.message : String(error51);
67370
+ result.errors.push(`Premium pricing scrape failed: ${msg}`);
67371
+ logger?.warn(`Premium pricing scrape failed: ${msg}`);
67372
+ }
67373
+ }
67374
+ async function refreshModelPricing(logger) {
67375
+ const result = {
67376
+ modelsUpdated: 0,
67377
+ catalogFetched: false,
67378
+ tokenPricingScraped: false,
67379
+ premiumPricingScraped: false,
67380
+ errors: []
67381
+ };
67382
+ const modelMap = /* @__PURE__ */ new Map();
67383
+ await fetchCatalogIntoMap(modelMap, result, logger);
67384
+ await scrapeTokenPricingIntoMap(modelMap, result, logger);
67385
+ await scrapePremiumPricingIntoMap(modelMap, result, logger);
67386
+ if (!result.catalogFetched && !result.tokenPricingScraped && !result.premiumPricingScraped) {
67387
+ logger?.warn("All pricing sources failed, using seed data");
67388
+ await seedFromFallback();
67389
+ result.modelsUpdated = SEED_MODELS.length;
67390
+ return result;
67391
+ }
67392
+ const db = await getDatabase();
67393
+ const now = nowIso();
67394
+ for (const model of modelMap.values()) {
67395
+ const tier = computeTierFromMultiplier(model.premiumMultiplier ?? null);
67396
+ await upsertModel(db, {
67397
+ id: model.id,
67398
+ displayName: model.displayName,
67399
+ premiumMultiplier: model.premiumMultiplier ?? null,
67400
+ tokenInputMultiplier: model.tokenInputMultiplier ?? null,
67401
+ tokenOutputMultiplier: model.tokenOutputMultiplier ?? null,
67402
+ cachedInputMultiplier: model.cachedInputMultiplier ?? null,
67403
+ tier,
67404
+ available: model.available ?? true,
67405
+ updatedAt: now
67406
+ });
67407
+ result.modelsUpdated++;
67408
+ }
67409
+ return result;
67410
+ }
67411
+ async function seedFromFallback() {
67412
+ const db = await getDatabase();
67413
+ const now = nowIso();
67414
+ for (const model of SEED_MODELS) {
67415
+ await upsertModel(db, { ...model, updatedAt: now });
67416
+ }
67417
+ }
67418
+ async function getModelPricing(modelId) {
67419
+ const db = await getDatabase();
67420
+ const result = await db.execute({
67421
+ sql: "SELECT * FROM model_pricing WHERE id = ?",
67422
+ args: [modelId]
67423
+ });
67424
+ if (result.rows.length === 0) {
67425
+ return null;
67426
+ }
67427
+ return rowToModelPricing(result.rows[0]);
67428
+ }
67429
+ function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outputMultiplier) {
67430
+ if (inputMultiplier === null || outputMultiplier === null) {
67431
+ return 0;
67432
+ }
67433
+ const tokenUnits = inputTokens * inputMultiplier + outputTokens * outputMultiplier;
67434
+ return tokenUnits * TOKEN_UNIT_PRICE;
67435
+ }
67436
+ async function upsertModel(db, model) {
67437
+ await db.execute({
67438
+ sql: `INSERT INTO model_pricing (id, display_name, premium_multiplier, token_input_multiplier, token_output_multiplier, cached_input_multiplier, tier, available, updated_at)
67439
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
67440
+ ON CONFLICT(id) DO UPDATE SET
67441
+ display_name = excluded.display_name,
67442
+ premium_multiplier = COALESCE(excluded.premium_multiplier, model_pricing.premium_multiplier),
67443
+ token_input_multiplier = COALESCE(excluded.token_input_multiplier, model_pricing.token_input_multiplier),
67444
+ token_output_multiplier = COALESCE(excluded.token_output_multiplier, model_pricing.token_output_multiplier),
67445
+ cached_input_multiplier = COALESCE(excluded.cached_input_multiplier, model_pricing.cached_input_multiplier),
67446
+ tier = excluded.tier,
67447
+ available = excluded.available,
67448
+ updated_at = excluded.updated_at`,
67449
+ args: [
67450
+ model.id,
67451
+ model.displayName,
67452
+ model.premiumMultiplier,
67453
+ model.tokenInputMultiplier,
67454
+ model.tokenOutputMultiplier,
67455
+ model.cachedInputMultiplier,
67456
+ model.tier,
67457
+ model.available ? 1 : 0,
67458
+ model.updatedAt
67459
+ ]
67460
+ });
67461
+ }
67462
+ function rowToModelPricing(row) {
67463
+ return {
67464
+ id: asString(row.id),
67465
+ displayName: asString(row.display_name),
67466
+ premiumMultiplier: asNullableNumber(row.premium_multiplier),
67467
+ tokenInputMultiplier: asNullableNumber(row.token_input_multiplier),
67468
+ tokenOutputMultiplier: asNullableNumber(row.token_output_multiplier),
67469
+ cachedInputMultiplier: asNullableNumber(row.cached_input_multiplier),
67470
+ tier: asString(row.tier),
67471
+ available: asNumber(row.available) === 1,
67472
+ updatedAt: asString(row.updated_at)
67473
+ };
67474
+ }
67475
+ function findClosestKey(map2, targetKey) {
67476
+ for (const [key, value] of map2) {
67477
+ if (key.includes(targetKey) || targetKey.includes(key)) {
67478
+ return value;
67479
+ }
67480
+ }
67481
+ return void 0;
67482
+ }
67483
+ var init_registry = __esm({
67484
+ "packages/daemon/src/models/registry.ts"() {
67485
+ "use strict";
67486
+ init_db();
67487
+ init_catalog();
67488
+ init_pricing_scraper();
67489
+ init_seed();
67490
+ init_types();
67491
+ }
67492
+ });
67493
+
67494
+ // packages/daemon/src/models/index.ts
67495
+ var init_models = __esm({
67496
+ "packages/daemon/src/models/index.ts"() {
67497
+ "use strict";
67498
+ init_catalog();
67499
+ init_registry();
67500
+ init_seed();
67501
+ init_types();
67502
+ }
67503
+ });
67504
+
66897
67505
  // packages/daemon/src/orchestrator/system-prompt.ts
66898
67506
  function formatSquadRoster(squads) {
66899
67507
  if (squads.length === 0) {
@@ -66997,148 +67605,8 @@ var init_reset = __esm({
66997
67605
  }
66998
67606
  });
66999
67607
 
67000
- // packages/daemon/src/copilot/router.ts
67001
- function normalizeMessage(message2) {
67002
- return message2.trim().toLowerCase();
67003
- }
67004
- function getWordCount(message2) {
67005
- const normalized = message2.trim();
67006
- if (normalized.length === 0) {
67007
- return 0;
67008
- }
67009
- return normalized.split(/\s+/u).length;
67010
- }
67011
- function containsAny(message2, keywords) {
67012
- return keywords.some((keyword) => message2.includes(keyword));
67013
- }
67014
- function startsWithSimpleQuestion(message2) {
67015
- return SIMPLE_QUESTION_PREFIXES.some(
67016
- (prefix) => message2 === prefix || message2.startsWith(`${prefix} `)
67017
- );
67018
- }
67019
- function classifyMessage(message2) {
67020
- const normalized = normalizeMessage(message2);
67021
- const wordCount = getWordCount(normalized);
67022
- if (normalized.length === 0) {
67023
- return "fast";
67024
- }
67025
- if (containsAny(normalized, PREMIUM_KEYWORDS) || normalized.includes("```") || wordCount >= 80) {
67026
- return "premium";
67027
- }
67028
- if (wordCount <= 20 && (containsAny(normalized, FAST_KEYWORDS) || startsWithSimpleQuestion(normalized) || /^(yes|no|sure|okay|ok|thanks)[!.?]*$/u.test(normalized))) {
67029
- return "fast";
67030
- }
67031
- if (wordCount <= 8 && /^(what|when|where|who)\b/u.test(normalized)) {
67032
- return "fast";
67033
- }
67034
- return "standard";
67035
- }
67036
- function getModelForTier(tier) {
67037
- switch (tier) {
67038
- case "fast":
67039
- return FAST_MODEL;
67040
- case "premium":
67041
- return PREMIUM_MODEL;
67042
- default:
67043
- return STANDARD_MODEL;
67044
- }
67045
- }
67046
- function routeMessage(message2) {
67047
- const requestedTier = classifyMessage(message2);
67048
- if (activeTier === null) {
67049
- activeTier = requestedTier;
67050
- messagesSinceLastSwitch = 0;
67051
- return {
67052
- requestedTier,
67053
- effectiveTier: activeTier,
67054
- model: getModelForTier(activeTier),
67055
- switched: true,
67056
- cooldownRemaining: MODEL_SWITCH_COOLDOWN_MESSAGES
67057
- };
67058
- }
67059
- if (requestedTier !== activeTier && messagesSinceLastSwitch >= MODEL_SWITCH_COOLDOWN_MESSAGES) {
67060
- activeTier = requestedTier;
67061
- messagesSinceLastSwitch = 0;
67062
- return {
67063
- requestedTier,
67064
- effectiveTier: activeTier,
67065
- model: getModelForTier(activeTier),
67066
- switched: true,
67067
- cooldownRemaining: MODEL_SWITCH_COOLDOWN_MESSAGES
67068
- };
67069
- }
67070
- messagesSinceLastSwitch += 1;
67071
- return {
67072
- requestedTier,
67073
- effectiveTier: activeTier,
67074
- model: getModelForTier(activeTier),
67075
- switched: false,
67076
- cooldownRemaining: Math.max(MODEL_SWITCH_COOLDOWN_MESSAGES - messagesSinceLastSwitch, 0)
67077
- };
67078
- }
67079
- var FAST_KEYWORDS, PREMIUM_KEYWORDS, SIMPLE_QUESTION_PREFIXES, MODEL_SWITCH_COOLDOWN_MESSAGES, activeTier, messagesSinceLastSwitch;
67080
- var init_router = __esm({
67081
- "packages/daemon/src/copilot/router.ts"() {
67082
- "use strict";
67083
- init_dist();
67084
- FAST_KEYWORDS = [
67085
- "hi",
67086
- "hello",
67087
- "hey",
67088
- "thanks",
67089
- "thank you",
67090
- "status",
67091
- "list",
67092
- "show",
67093
- "ping",
67094
- "uptime",
67095
- "what time",
67096
- "ok",
67097
- "okay"
67098
- ];
67099
- PREMIUM_KEYWORDS = [
67100
- "architecture",
67101
- "architect",
67102
- "design",
67103
- "tradeoff",
67104
- "trade-off",
67105
- "plan",
67106
- "planning",
67107
- "strategy",
67108
- "roadmap",
67109
- "migration",
67110
- "refactor",
67111
- "generate",
67112
- "implement",
67113
- "build",
67114
- "codebase",
67115
- "repository",
67116
- "system prompt",
67117
- "multi-step",
67118
- "complex",
67119
- "reason",
67120
- "compare"
67121
- ];
67122
- SIMPLE_QUESTION_PREFIXES = [
67123
- "is",
67124
- "are",
67125
- "do",
67126
- "does",
67127
- "did",
67128
- "can",
67129
- "could",
67130
- "should",
67131
- "would",
67132
- "will"
67133
- ];
67134
- MODEL_SWITCH_COOLDOWN_MESSAGES = 3;
67135
- activeTier = null;
67136
- messagesSinceLastSwitch = MODEL_SWITCH_COOLDOWN_MESSAGES;
67137
- }
67138
- });
67139
-
67140
67608
  // packages/daemon/src/copilot/client.ts
67141
- import { execFileSync } from "node:child_process";
67609
+ import { execFileSync as execFileSync2 } from "node:child_process";
67142
67610
  import { mkdirSync as mkdirSync2 } from "node:fs";
67143
67611
  import { join as join6 } from "node:path";
67144
67612
  import { CopilotClient } from "@github/copilot-sdk";
@@ -67148,7 +67616,7 @@ function readTokenFromEnvironment() {
67148
67616
  }
67149
67617
  function readTokenFromGhCli() {
67150
67618
  try {
67151
- const token = execFileSync("gh", ["auth", "token"], {
67619
+ const token = execFileSync2("gh", ["auth", "token"], {
67152
67620
  encoding: "utf8",
67153
67621
  stdio: ["ignore", "pipe", "pipe"]
67154
67622
  }).trim();
@@ -67157,7 +67625,7 @@ function readTokenFromGhCli() {
67157
67625
  return void 0;
67158
67626
  }
67159
67627
  }
67160
- function resolveGitHubToken() {
67628
+ function resolveGitHubToken2() {
67161
67629
  return readTokenFromEnvironment() ?? readTokenFromGhCli();
67162
67630
  }
67163
67631
  function buildClientOptions(token) {
@@ -67193,7 +67661,7 @@ async function initCopilotClient() {
67193
67661
  }
67194
67662
  copilotClientInitPromise = (async () => {
67195
67663
  try {
67196
- const token = resolveGitHubToken();
67664
+ const token = resolveGitHubToken2();
67197
67665
  if (token === void 0) {
67198
67666
  throw new Error(
67199
67667
  `Unable to find a GitHub token for Copilot SDK authentication. ${COPILOT_AUTH_ERROR_HINT}`
@@ -68109,13 +68577,24 @@ function mergeUsage(target, usage) {
68109
68577
  }
68110
68578
  async function persistUsage(member, usageEvents) {
68111
68579
  for (const usage of usageEvents) {
68580
+ const model = usage.model;
68581
+ const pricing = await getModelPricing(model);
68582
+ const premiumRequestCost = pricing?.premiumMultiplier ?? 0;
68583
+ const tokenUnitCost = pricing ? calculateTokenUnitCost(
68584
+ usage.inputTokens ?? 0,
68585
+ usage.outputTokens ?? 0,
68586
+ pricing.tokenInputMultiplier,
68587
+ pricing.tokenOutputMultiplier
68588
+ ) : 0;
68112
68589
  await recordUsage({
68113
68590
  squadId: member.squadId,
68114
68591
  agentId: member.id,
68115
- model: usage.model,
68592
+ model,
68116
68593
  inputTokens: usage.inputTokens ?? 0,
68117
68594
  outputTokens: usage.outputTokens ?? 0,
68118
- cost: usage.cost ?? 0
68595
+ cost: 0,
68596
+ premiumRequestCost,
68597
+ tokenUnitCost
68119
68598
  });
68120
68599
  }
68121
68600
  }
@@ -68333,7 +68812,7 @@ async function executeAgentTask(member, task, worktreePath, options2) {
68333
68812
  client2 = new CopilotClient2({ workingDirectory: worktreePath });
68334
68813
  await client2.start();
68335
68814
  const session = await client2.createSession({
68336
- model: member.model ?? STANDARD_MODEL,
68815
+ model: member.model ?? DEFAULT_MODEL,
68337
68816
  workingDirectory: worktreePath,
68338
68817
  tools,
68339
68818
  availableTools: ["custom:*"],
@@ -68390,6 +68869,7 @@ var init_agent = __esm({
68390
68869
  "packages/daemon/src/execution/agent.ts"() {
68391
68870
  "use strict";
68392
68871
  init_dist();
68872
+ init_registry();
68393
68873
  init_store2();
68394
68874
  init_history();
68395
68875
  execAsync2 = promisify2(exec2);
@@ -68588,7 +69068,7 @@ Return strict JSON in this shape:
68588
69068
  client2 = new CopilotClient3({ workingDirectory: repoPath });
68589
69069
  await client2.start();
68590
69070
  const session = await client2.createSession({
68591
- model: PREMIUM_MODEL,
69071
+ model: DEFAULT_MODEL,
68592
69072
  workingDirectory: repoPath,
68593
69073
  onPermissionRequest: approveAll3,
68594
69074
  systemMessage: {
@@ -68879,7 +69359,7 @@ Return strict JSON:
68879
69359
  client2 = new CopilotClient4({ workingDirectory: worktreePath });
68880
69360
  await client2.start();
68881
69361
  const session = await client2.createSession({
68882
- model: qaMember.model ?? STANDARD_MODEL,
69362
+ model: qaMember.model ?? DEFAULT_MODEL,
68883
69363
  workingDirectory: worktreePath,
68884
69364
  onPermissionRequest: approveAll4,
68885
69365
  systemMessage: {
@@ -69011,7 +69491,7 @@ Return strict JSON:
69011
69491
  client2 = new CopilotClient5();
69012
69492
  await client2.start();
69013
69493
  const session = await client2.createSession({
69014
- model: teamLead.model ?? PREMIUM_MODEL,
69494
+ model: teamLead.model ?? DEFAULT_MODEL,
69015
69495
  onPermissionRequest: approveAll5,
69016
69496
  systemMessage: {
69017
69497
  content: `${TEAM_LEAD_PROMPT}
@@ -69501,12 +69981,9 @@ function buildMandatoryRoles() {
69501
69981
  function modelForRole(role) {
69502
69982
  const normalized = slugifyRole(role);
69503
69983
  if (normalized === "team-lead") {
69504
- return PREMIUM_MODEL;
69984
+ return DEFAULT_MODEL;
69505
69985
  }
69506
- if (normalized === "qa") {
69507
- return STANDARD_MODEL;
69508
- }
69509
- return STANDARD_MODEL;
69986
+ return DEFAULT_MODEL;
69510
69987
  }
69511
69988
  function systemPromptForRole(role, repoContext) {
69512
69989
  const normalized = slugifyRole(role);
@@ -69538,7 +70015,7 @@ ${ROLE_GENERATION_PROMPT}`;
69538
70015
  client2 = new CopilotClient6();
69539
70016
  await client2.start();
69540
70017
  const session = await client2.createSession({
69541
- model: PREMIUM_MODEL,
70018
+ model: DEFAULT_MODEL,
69542
70019
  onPermissionRequest: approveAll6,
69543
70020
  systemMessage: {
69544
70021
  content: ROLE_GENERATION_PROMPT
@@ -69977,9 +70454,9 @@ var init_orchestrator = __esm({
69977
70454
  "use strict";
69978
70455
  init_dist();
69979
70456
  init_reset();
69980
- init_router();
69981
70457
  init_session();
69982
70458
  init_logger();
70459
+ init_registry();
69983
70460
  init_skills2();
69984
70461
  init_store2();
69985
70462
  init_episodes();
@@ -70072,8 +70549,7 @@ var init_orchestrator = __esm({
70072
70549
  skillsContext,
70073
70550
  conversationSummary: [this.latestResetSummary, conversationSummary].filter(Boolean).join("\n\n")
70074
70551
  });
70075
- const route = routeMessage(normalizedMessage);
70076
- await this.refreshSession(route.model, systemPrompt);
70552
+ await this.refreshSession(this.config.defaultModel, systemPrompt);
70077
70553
  const sendResult = await sendMessage(
70078
70554
  this.requireActiveSession(),
70079
70555
  normalizedMessage,
@@ -70201,11 +70677,22 @@ var init_orchestrator = __esm({
70201
70677
  if (usage.inputTokens === 0 && usage.outputTokens === 0) {
70202
70678
  return;
70203
70679
  }
70680
+ const model = usage.model || this.activeModel || this.config.defaultModel;
70681
+ const pricing = await getModelPricing(model);
70682
+ const premiumRequestCost = pricing?.premiumMultiplier ?? 0;
70683
+ const tokenUnitCost = pricing ? calculateTokenUnitCost(
70684
+ usage.inputTokens,
70685
+ usage.outputTokens,
70686
+ pricing.tokenInputMultiplier,
70687
+ pricing.tokenOutputMultiplier
70688
+ ) : 0;
70204
70689
  await recordUsage({
70205
- model: usage.model || this.activeModel || this.config.defaultModel,
70690
+ model,
70206
70691
  inputTokens: usage.inputTokens,
70207
70692
  outputTokens: usage.outputTokens,
70208
- cost: 0
70693
+ cost: 0,
70694
+ premiumRequestCost,
70695
+ tokenUnitCost
70209
70696
  });
70210
70697
  }
70211
70698
  };
@@ -83812,6 +84299,7 @@ function registerShutdownHandlers(logger, onShutdown) {
83812
84299
  }
83813
84300
  async function main() {
83814
84301
  let logger;
84302
+ let pricingRefreshTimer = null;
83815
84303
  try {
83816
84304
  ensureDataDirectories();
83817
84305
  const config2 = loadConfig();
@@ -83819,6 +84307,12 @@ async function main() {
83819
84307
  logger.info(`IO Daemon v${APP_VERSION} starting...`);
83820
84308
  await initDatabase();
83821
84309
  logger.info("Database initialized");
84310
+ const pricingResult = await refreshModelPricing(logger);
84311
+ if (pricingResult.modelsUpdated === 0) {
84312
+ logger.warn("Model pricing refresh returned 0 models, seeding with fallback");
84313
+ await seedFromFallback();
84314
+ }
84315
+ logger.info({ modelsUpdated: pricingResult.modelsUpdated }, "Model pricing initialized");
83822
84316
  await scanSkills();
83823
84317
  logger.info("Skills scanned");
83824
84318
  const orchestrator2 = createOrchestrator(config2, eventBus);
@@ -83827,12 +84321,21 @@ async function main() {
83827
84321
  const scheduler = createScheduler(orchestrator2, eventBus);
83828
84322
  scheduler.start();
83829
84323
  logger.info("Scheduler started");
84324
+ const refreshIntervalMs = config2.pricingRefreshHours * 60 * 60 * 1e3;
84325
+ pricingRefreshTimer = setInterval(() => {
84326
+ void refreshModelPricing(logger).catch((err) => {
84327
+ logger?.warn({ err }, "Periodic model pricing refresh failed");
84328
+ });
84329
+ }, refreshIntervalMs);
83830
84330
  setChatOrchestrator(orchestrator2);
83831
84331
  const apiServer = createApiServer(config2);
83832
84332
  const telegramBot = createTelegramBot(config2, orchestrator2);
83833
84333
  telegramBot?.start();
83834
84334
  createTelegramNotifier(telegramBot, config2, eventBus);
83835
84335
  registerShutdownHandlers(logger, async () => {
84336
+ if (pricingRefreshTimer) {
84337
+ clearInterval(pricingRefreshTimer);
84338
+ }
83836
84339
  scheduler.stop();
83837
84340
  telegramBot?.stop();
83838
84341
  apiServer.server.close();
@@ -83857,6 +84360,7 @@ var init_index = __esm({
83857
84360
  init_data_dir();
83858
84361
  init_event_bus();
83859
84362
  init_logger();
84363
+ init_models();
83860
84364
  init_orchestrator2();
83861
84365
  init_scheduler();
83862
84366
  init_skills2();