heyio 0.1.16 → 0.1.17

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.
@@ -5,6 +5,7 @@ import { join, dirname, resolve } from "path";
5
5
  import { defineTool, approveAll } from "@github/copilot-sdk";
6
6
  import { z } from "zod";
7
7
  import { getClient } from "./client.js";
8
+ import { getModelForTask } from "./model-router.js";
8
9
  import { getSquad, updateSquadSession, updateSquadStatus, getDecisionsSummary, logDecision, } from "../store/squads.js";
9
10
  import { createTask, completeTask, failTask, getActiveTasks, } from "../store/tasks.js";
10
11
  import { SESSIONS_DIR } from "../paths.js";
@@ -40,7 +41,7 @@ export async function delegateToAgent(squadSlug, task, onComplete) {
40
41
  if (!squad) {
41
42
  throw new Error(`Squad not found: ${squadSlug}`);
42
43
  }
43
- const session = await getOrCreateSession(squadSlug);
44
+ const session = await getOrCreateSession(squadSlug, task);
44
45
  const taskId = randomUUID();
45
46
  createTask(taskId, squadSlug, task);
46
47
  updateSquadStatus(squadSlug, "working");
@@ -83,7 +84,7 @@ export function getActiveAgentTasks() {
83
84
  // ---------------------------------------------------------------------------
84
85
  // Internal helpers
85
86
  // ---------------------------------------------------------------------------
86
- async function getOrCreateSession(squadSlug) {
87
+ async function getOrCreateSession(squadSlug, taskDescription) {
87
88
  const existing = agentSessions.get(squadSlug);
88
89
  if (existing)
89
90
  return existing;
@@ -91,8 +92,9 @@ async function getOrCreateSession(squadSlug) {
91
92
  const client = await getClient();
92
93
  const decisions = getDecisionsSummary(squadSlug);
93
94
  const agentTools = buildAgentTools(squadSlug);
95
+ const model = getModelForTask(taskDescription ?? "", squad.model);
94
96
  const commonConfig = {
95
- model: "gpt-4.1",
97
+ model,
96
98
  configDir: SESSIONS_DIR,
97
99
  streaming: false,
98
100
  systemMessage: {
@@ -0,0 +1,84 @@
1
+ import { config } from "../config.js";
2
+ const DEFAULT_TIERS = {
3
+ high: ["claude-opus-4.7", "claude-opus-4.6"],
4
+ medium: ["claude-sonnet-4.6", "gpt-5.5", "claude-opus-4.5"],
5
+ low: ["claude-haiku-4.5", "gpt-5.4-mini"],
6
+ };
7
+ // Resolved model for each tier (populated at startup)
8
+ let resolvedTiers = null;
9
+ const HIGH_KEYWORDS = [
10
+ "architect", "refactor", "redesign", "debug", "design",
11
+ "complex", "migration", "security", "performance", "optimize",
12
+ "rewrite", "overhaul", "investigate", "diagnose", "plan",
13
+ ];
14
+ const LOW_KEYWORDS = [
15
+ "read", "list", "format", "lookup", "check", "status",
16
+ "simple", "rename", "typo", "log", "print", "echo",
17
+ "delete file", "remove file", "copy file", "move file",
18
+ ];
19
+ /**
20
+ * Resolve each tier to the first available model from its preference list.
21
+ * Call once at startup with the result of `client.listModels()`.
22
+ */
23
+ export function resolveModelTiers(availableModelIds) {
24
+ const available = new Set(availableModelIds);
25
+ const tiers = config.modelTiers ?? {};
26
+ const result = {};
27
+ for (const tier of ["high", "medium", "low"]) {
28
+ const candidates = tiers[tier] ?? DEFAULT_TIERS[tier];
29
+ const match = candidates.find((m) => available.has(m));
30
+ if (match) {
31
+ result[tier] = match;
32
+ }
33
+ else {
34
+ // Fallback: use defaultModel, then first candidate regardless of availability
35
+ result[tier] = config.defaultModel || candidates[0];
36
+ }
37
+ console.error(`[io] Model tier "${tier}" resolved to: ${result[tier]}`);
38
+ }
39
+ resolvedTiers = result;
40
+ return result;
41
+ }
42
+ /**
43
+ * Classify a task description into a complexity tier.
44
+ */
45
+ export function classifyComplexity(taskDescription) {
46
+ const lower = taskDescription.toLowerCase();
47
+ const highScore = HIGH_KEYWORDS.filter((kw) => lower.includes(kw)).length;
48
+ const lowScore = LOW_KEYWORDS.filter((kw) => lower.includes(kw)).length;
49
+ // Strong signals
50
+ if (highScore >= 2)
51
+ return "high";
52
+ if (lowScore >= 2)
53
+ return "low";
54
+ // Weak signals
55
+ if (highScore > lowScore)
56
+ return "high";
57
+ if (lowScore > highScore)
58
+ return "low";
59
+ // Default to medium for ambiguous tasks
60
+ return "medium";
61
+ }
62
+ /**
63
+ * Get the resolved model for a tier.
64
+ */
65
+ export function getModelForTier(tier) {
66
+ if (!resolvedTiers) {
67
+ console.error("[io] Warning: model tiers not yet resolved, using defaults");
68
+ return config.defaultModel || DEFAULT_TIERS[tier][0];
69
+ }
70
+ return resolvedTiers[tier];
71
+ }
72
+ /**
73
+ * Get the best model for a task based on its description.
74
+ * If squadModel is provided, it always takes priority.
75
+ */
76
+ export function getModelForTask(taskDescription, squadModel) {
77
+ if (squadModel)
78
+ return squadModel;
79
+ const tier = classifyComplexity(taskDescription);
80
+ const model = getModelForTier(tier);
81
+ console.error(`[io] Task classified as "${tier}" → model: ${model}`);
82
+ return model;
83
+ }
84
+ //# sourceMappingURL=model-router.js.map
@@ -5,6 +5,7 @@ import { getState, setState, deleteState, logConversation } from "../store/db.js
5
5
  import { clearStaleTasks } from "../store/tasks.js";
6
6
  import { getSquad, listSquads, createSquad, logDecision, getDecisionsSummary, updateSquadStatus, } from "../store/squads.js";
7
7
  import { readPage, writePage, assertPagePath } from "../wiki/fs.js";
8
+ import { resolveModelTiers } from "./model-router.js";
8
9
  import { searchWiki, getWikiSummary } from "../wiki/search.js";
9
10
  import { getOrchestratorSystemMessage } from "./system-message.js";
10
11
  import { createTools } from "./tools.js";
@@ -293,7 +294,7 @@ async function processQueue() {
293
294
  export async function initOrchestrator(copilotClient) {
294
295
  client = copilotClient;
295
296
  clearStaleTasks();
296
- // Validate the configured model
297
+ // Validate the configured model and resolve model tiers
297
298
  try {
298
299
  const models = await copilotClient.listModels();
299
300
  const defaultModel = config.defaultModel || "gpt-4.1";
@@ -304,6 +305,7 @@ export async function initOrchestrator(copilotClient) {
304
305
  else {
305
306
  console.error(`[io] Model validated: ${defaultModel}`);
306
307
  }
308
+ resolveModelTiers(modelIds);
307
309
  }
308
310
  catch (err) {
309
311
  console.error("[io] Could not validate models:", err instanceof Error ? err.message : err);
@@ -62,6 +62,14 @@ Squads are persistent project teams. When a user works on a codebase:
62
62
  3. Recall squad context with \`squad_recall\` before doing project work.
63
63
  4. Check squad status with \`squad_status\`.
64
64
 
65
+ ### Model Selection
66
+ Squad agents are automatically assigned a model based on task complexity:
67
+ - **High complexity** (architecture, refactoring, debugging, design) → most capable model
68
+ - **Medium complexity** (implementing features, writing tests, reviews) → balanced model
69
+ - **Low complexity** (file reads, formatting, lookups) → fast/cheap model
70
+
71
+ The model is selected automatically. Tell the user which model tier was chosen when delegating tasks.
72
+
65
73
  ## Tool Usage
66
74
 
67
75
  ### Knowledge Base
package/dist/store/db.js CHANGED
@@ -21,6 +21,7 @@ export function getDb() {
21
21
  name TEXT NOT NULL,
22
22
  project_path TEXT NOT NULL,
23
23
  copilot_session_id TEXT,
24
+ model TEXT,
24
25
  status TEXT NOT NULL DEFAULT 'idle',
25
26
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
26
27
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
@@ -53,6 +54,13 @@ export function getDb() {
53
54
  completed_at DATETIME
54
55
  );
55
56
  `);
57
+ // Migration: add model column to squads (for existing databases)
58
+ try {
59
+ db.exec(`ALTER TABLE squads ADD COLUMN model TEXT`);
60
+ }
61
+ catch {
62
+ // Column already exists — ignore
63
+ }
56
64
  return db;
57
65
  }
58
66
  export function closeDb() {
@@ -22,6 +22,11 @@ export function updateSquadStatus(slug, status) {
22
22
  .prepare("UPDATE squads SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?")
23
23
  .run(status, slug);
24
24
  }
25
+ export function updateSquadModel(slug, model) {
26
+ getDb()
27
+ .prepare("UPDATE squads SET model = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?")
28
+ .run(model, slug);
29
+ }
25
30
  export function deleteSquad(slug) {
26
31
  const db = getDb();
27
32
  db.prepare("DELETE FROM squad_decisions WHERE squad_slug = ?").run(slug);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heyio",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "IO — a personal AI assistant built on the GitHub Copilot SDK",
5
5
  "bin": {
6
6
  "io": "dist/index.js"