sigma-agents 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "sigma-agents",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Intelligent sub-agent system with model routing for Phi Code",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
- "clean": "rm -rf dist"
9
+ "clean": "shx rm -rf dist",
10
+ "prepublishOnly": "npm run clean && npm run build",
11
+ "test": "node --test dist/test/*.test.js"
10
12
  },
11
13
  "dependencies": {},
12
14
  "devDependencies": {
15
+ "shx": "^0.4.0",
13
16
  "typescript": "^5.4.0"
14
17
  },
15
18
  "repository": {
@@ -29,4 +32,4 @@
29
32
  "src",
30
33
  "README.md"
31
34
  ]
32
- }
35
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { SmartRouter } from './router.js';
2
- export { ModelProfiler } from './profiles.js';
3
- export { SubAgentManager } from './sub-agent.js';
4
- export * from './types.js';
1
+ export { ModelProfiler } from "./profiles.js";
2
+ export { SmartRouter } from "./router.js";
3
+ export { SubAgentManager } from "./sub-agent.js";
4
+ export * from "./types.js";
package/src/profiles.ts CHANGED
@@ -1,90 +1,128 @@
1
- import { ModelProfile, TaskCategory } from './types.js';
2
- import { readFile, writeFile } from 'node:fs/promises';
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import type { ModelProfile, TaskCategory } from "./types.js";
3
3
 
4
4
  export class ModelProfiler {
5
- public profiles: Map<string, ModelProfile> = new Map();
6
-
7
- /**
8
- * Charge les profiles depuis un fichier JSON
9
- */
10
- async loadFromFile(path: string): Promise<void> {
11
- try {
12
- const content = await readFile(path, 'utf8');
13
- const data = JSON.parse(content);
14
-
15
- if (Array.isArray(data.profiles)) {
16
- this.profiles.clear();
17
- for (const profile of data.profiles) {
18
- this.profiles.set(profile.id, profile);
19
- }
20
- }
21
- } catch (error) {
22
- // If file doesn't exist, use default profiles
23
- console.warn(`Could not load profiles from ${path}:`, error);
24
- this.loadDefaultProfiles();
25
- }
26
- }
27
-
28
- /**
29
- * Sauvegarde les profiles vers un fichier JSON
30
- */
31
- async saveToFile(path: string): Promise<void> {
32
- const data = {
33
- profiles: Array.from(this.profiles.values())
34
- };
35
-
36
- await writeFile(path, JSON.stringify(data, null, 2), 'utf8');
37
- }
38
-
39
- /**
40
- * Ajoute un profile
41
- */
42
- addProfile(profile: ModelProfile): void {
43
- this.profiles.set(profile.id, profile);
44
- }
45
-
46
- /**
47
- * Retourne le meilleur modèle pour une tâche donnée
48
- */
49
- getBestForTask(category: TaskCategory): ModelProfile | null {
50
- const candidates = Array.from(this.profiles.values())
51
- .filter(profile => profile.strengths.includes(category))
52
- .sort((a, b) => {
53
- // Priority: quality > speed
54
- if (a.quality !== b.quality) {
55
- const qualityOrder = { high: 3, medium: 2, low: 1 };
56
- return qualityOrder[b.quality] - qualityOrder[a.quality];
57
- }
58
-
59
- if (a.speed !== b.speed) {
60
- const speedOrder = { fast: 3, medium: 2, slow: 1 };
61
- return speedOrder[b.speed] - speedOrder[a.speed];
62
- }
63
-
64
- return 0; // Equal priority
65
- });
66
-
67
- return candidates[0] || null;
68
- }
69
-
70
- /**
71
- * Charge les profiles par défaut des modèles Alibaba
72
- */
73
- private loadDefaultProfiles(): void {
74
- const defaultProfiles = this.getDefaultProfiles();
75
- this.profiles.clear();
76
-
77
- for (const profile of defaultProfiles) {
78
- this.profiles.set(profile.id, profile);
79
- }
80
- }
81
-
82
- /**
83
- * Returns empty default profiles.
84
- * Actual profiles should be populated from /phi-init or user configuration.
85
- * sigma-agents is provider-agnostic — no hardcoded model names.
86
- */
87
- getDefaultProfiles(): ModelProfile[] {
88
- return [];
89
- }
90
- }
5
+ public profiles: Map<string, ModelProfile> = new Map();
6
+
7
+ /**
8
+ * Charge les profiles depuis un fichier JSON
9
+ */
10
+ async loadFromFile(path: string): Promise<void> {
11
+ try {
12
+ const content = await readFile(path, "utf8");
13
+ const data = JSON.parse(content);
14
+
15
+ if (Array.isArray(data.profiles)) {
16
+ this.profiles.clear();
17
+ for (const profile of data.profiles) {
18
+ if (!ModelProfiler.isValidProfile(profile)) {
19
+ console.warn("Skipping invalid profile entry");
20
+ continue;
21
+ }
22
+ this.profiles.set(profile.id, profile);
23
+ }
24
+ }
25
+ } catch (error) {
26
+ // If file doesn't exist, use default profiles
27
+ console.warn(`Could not load profiles from ${path}:`, error);
28
+ this.loadDefaultProfiles();
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Sauvegarde les profiles vers un fichier JSON
34
+ */
35
+ async saveToFile(path: string): Promise<void> {
36
+ const data = {
37
+ profiles: Array.from(this.profiles.values()),
38
+ };
39
+
40
+ await writeFile(path, JSON.stringify(data, null, 2), "utf8");
41
+ }
42
+
43
+ /**
44
+ * Ajoute un profile
45
+ */
46
+ addProfile(profile: ModelProfile): void {
47
+ this.profiles.set(profile.id, profile);
48
+ }
49
+
50
+ /**
51
+ * Retourne le meilleur modèle pour une tâche donnée
52
+ */
53
+ getBestForTask(category: TaskCategory): ModelProfile | null {
54
+ const candidates = Array.from(this.profiles.values())
55
+ .filter((profile) => profile.strengths.includes(category))
56
+ .sort((a, b) => {
57
+ // Priority: quality > speed
58
+ if (a.quality !== b.quality) {
59
+ const qualityOrder = { high: 3, medium: 2, low: 1 };
60
+ return qualityOrder[b.quality] - qualityOrder[a.quality];
61
+ }
62
+
63
+ if (a.speed !== b.speed) {
64
+ const speedOrder = { fast: 3, medium: 2, slow: 1 };
65
+ return speedOrder[b.speed] - speedOrder[a.speed];
66
+ }
67
+
68
+ return 0; // Equal priority
69
+ });
70
+
71
+ return candidates[0] || null;
72
+ }
73
+
74
+ /**
75
+ * Charge les profiles par défaut des modèles Alibaba
76
+ */
77
+ private loadDefaultProfiles(): void {
78
+ const defaultProfiles = this.getDefaultProfiles();
79
+ this.profiles.clear();
80
+
81
+ for (const profile of defaultProfiles) {
82
+ this.profiles.set(profile.id, profile);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Valide la forme d'un profile chargé depuis un JSON non fiable.
88
+ * Empêche les entrées sans id (qui s'écraseraient sous la clé undefined)
89
+ * et les champs malformés qui feraient planter getBestForTask.
90
+ */
91
+ static isValidProfile(profile: unknown): profile is ModelProfile {
92
+ if (typeof profile !== "object" || profile === null) {
93
+ return false;
94
+ }
95
+
96
+ const p = profile as Record<string, unknown>;
97
+
98
+ if (typeof p.id !== "string" || p.id.length === 0) {
99
+ return false;
100
+ }
101
+ if (typeof p.provider !== "string") {
102
+ return false;
103
+ }
104
+ if (p.speed !== "fast" && p.speed !== "medium" && p.speed !== "slow") {
105
+ return false;
106
+ }
107
+ if (p.quality !== "high" && p.quality !== "medium" && p.quality !== "low") {
108
+ return false;
109
+ }
110
+ if (!Array.isArray(p.strengths) || !p.strengths.every((s) => typeof s === "string")) {
111
+ return false;
112
+ }
113
+ if (typeof p.maxTokens !== "number" || typeof p.supportsTools !== "boolean") {
114
+ return false;
115
+ }
116
+
117
+ return true;
118
+ }
119
+
120
+ /**
121
+ * Returns empty default profiles.
122
+ * Actual profiles should be populated from /phi-init or user configuration.
123
+ * sigma-agents is provider-agnostic — no hardcoded model names.
124
+ */
125
+ getDefaultProfiles(): ModelProfile[] {
126
+ return [];
127
+ }
128
+ }
package/src/router.ts CHANGED
@@ -1,138 +1,277 @@
1
- import { RoutingConfig, TaskCategory } from './types.js';
2
- import { readFile } from 'node:fs/promises';
1
+ import { readFile } from "node:fs/promises";
2
+ import type { RoutingConfig, TaskCategory } from "./types.js";
3
3
 
4
4
  export class SmartRouter {
5
- private config: RoutingConfig;
6
-
7
- constructor(config: RoutingConfig) {
8
- this.config = config;
9
- }
10
-
11
- /**
12
- * Analyse le prompt et retourne la catégorie de tâche
13
- * Priorité : debug > code > plan > review > test > explore > general
14
- */
15
- classifyTask(prompt: string): TaskCategory {
16
- const lowerPrompt = prompt.toLowerCase();
17
- const categories: TaskCategory[] = [];
18
-
19
- // Check each category
20
- for (const [category, route] of Object.entries(this.config.routes)) {
21
- const hasKeyword = route.keywords.some(keyword =>
22
- lowerPrompt.includes(keyword.toLowerCase())
23
- );
24
-
25
- if (hasKeyword) {
26
- categories.push(category as TaskCategory);
27
- }
28
- }
29
-
30
- if (categories.length === 0) {
31
- return 'general';
32
- }
33
-
34
- // Apply priorities
35
- const priorityOrder: TaskCategory[] = ['debug', 'code', 'plan', 'review', 'test', 'explore', 'general'];
36
-
37
- for (const priority of priorityOrder) {
38
- if (categories.includes(priority)) {
39
- return priority;
40
- }
41
- }
42
-
43
- return categories[0];
44
- }
45
-
46
- /**
47
- * Retourne le modèle et l'agent recommandés pour un prompt
48
- */
49
- getRecommendation(prompt: string): { model: string; agent: string | null; category: TaskCategory } {
50
- const category = this.classifyTask(prompt);
51
- const route = this.config.routes[category];
52
-
53
- if (!route) {
54
- return {
55
- model: this.config.default.model,
56
- agent: this.config.default.agent,
57
- category
58
- };
59
- }
60
-
61
- return {
62
- model: route.preferredModel,
63
- agent: route.agent,
64
- category
65
- };
66
- }
67
-
68
- /**
69
- * Charge la configuration depuis un fichier JSON
70
- */
71
- static async loadConfig(configPath: string): Promise<RoutingConfig> {
72
- try {
73
- const content = await readFile(configPath, 'utf8');
74
- return JSON.parse(content);
75
- } catch {
76
- // File doesn't exist yet — use defaults silently
77
- return SmartRouter.defaultConfig();
78
- }
79
- }
80
-
81
- /**
82
- * Default configuration with provider-agnostic model names.
83
- * Uses 'default' as model placeholder — the actual model is determined
84
- * at runtime by /phi-init or the user's routing.json.
85
- */
86
- static defaultConfig(): RoutingConfig {
87
- return {
88
- routes: {
89
- code: {
90
- preferredModel: 'default',
91
- fallback: 'default',
92
- agent: null,
93
- keywords: ['code', 'implement', 'write', 'create', 'build', 'développer', 'coder', 'programmer', 'function', 'class', 'method']
94
- },
95
- debug: {
96
- preferredModel: 'default',
97
- fallback: 'default',
98
- agent: null,
99
- keywords: ['debug', 'fix', 'error', 'bug', 'broken', 'issue', 'problem', 'repair', 'correct', 'erreur', 'problème', 'réparer']
100
- },
101
- explore: {
102
- preferredModel: 'default',
103
- fallback: 'default',
104
- agent: null,
105
- keywords: ['explore', 'understand', 'analyze', 'examine', 'investigate', 'study', 'review', 'explorer', 'analyser', 'comprendre']
106
- },
107
- plan: {
108
- preferredModel: 'default',
109
- fallback: 'default',
110
- agent: null,
111
- keywords: ['plan', 'design', 'architecture', 'strategy', 'approach', 'structure', 'organize', 'concevoir', 'planifier', 'architecture']
112
- },
113
- test: {
114
- preferredModel: 'default',
115
- fallback: 'default',
116
- agent: null,
117
- keywords: ['test', 'testing', 'unit', 'integration', 'verify', 'validate', 'check', 'tester', 'vérifier', 'valider']
118
- },
119
- review: {
120
- preferredModel: 'default',
121
- fallback: 'default',
122
- agent: null,
123
- keywords: ['review', 'audit', 'check', 'validate', 'quality', 'improve', 'optimize', 'réviser', 'améliorer', 'optimiser']
124
- },
125
- general: {
126
- preferredModel: 'default',
127
- fallback: 'default',
128
- agent: null,
129
- keywords: ['help', 'explain', 'what', 'how', 'why', 'question', 'aide', 'expliquer', 'comment', 'pourquoi']
130
- }
131
- },
132
- default: {
133
- model: 'default',
134
- agent: null
135
- }
136
- };
137
- }
138
- }
5
+ private config: RoutingConfig;
6
+
7
+ constructor(config: RoutingConfig) {
8
+ this.config = config;
9
+ }
10
+
11
+ /**
12
+ * Analyse le prompt et retourne la catégorie de tâche
13
+ * Priorité : debug > code > plan > review > test > explore > general
14
+ */
15
+ classifyTask(prompt: string): TaskCategory {
16
+ const lowerPrompt = prompt.toLowerCase();
17
+ const categories: TaskCategory[] = [];
18
+
19
+ if (!this.config?.routes) {
20
+ return "general";
21
+ }
22
+
23
+ // Check each category
24
+ for (const [category, route] of Object.entries(this.config.routes)) {
25
+ if (!Array.isArray(route?.keywords)) continue;
26
+ const hasKeyword = route.keywords.some((keyword) => lowerPrompt.includes(keyword.toLowerCase()));
27
+
28
+ if (hasKeyword) {
29
+ categories.push(category as TaskCategory);
30
+ }
31
+ }
32
+
33
+ if (categories.length === 0) {
34
+ return "general";
35
+ }
36
+
37
+ // Apply priorities
38
+ const priorityOrder: TaskCategory[] = ["debug", "code", "plan", "review", "test", "explore", "general"];
39
+
40
+ for (const priority of priorityOrder) {
41
+ if (categories.includes(priority)) {
42
+ return priority;
43
+ }
44
+ }
45
+
46
+ return categories[0];
47
+ }
48
+
49
+ /**
50
+ * Retourne le modèle et l'agent recommandés pour un prompt
51
+ */
52
+ getRecommendation(prompt: string): { model: string; agent: string | null; category: TaskCategory } {
53
+ const category = this.classifyTask(prompt);
54
+ const route = this.config.routes[category];
55
+
56
+ if (!route) {
57
+ return {
58
+ model: this.config.default.model,
59
+ agent: this.config.default.agent,
60
+ category,
61
+ };
62
+ }
63
+
64
+ return {
65
+ model: route.preferredModel,
66
+ agent: route.agent,
67
+ category,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Charge la configuration depuis un fichier JSON
73
+ */
74
+ static async loadConfig(configPath: string): Promise<RoutingConfig> {
75
+ try {
76
+ const content = await readFile(configPath, "utf8");
77
+ const parsed: unknown = JSON.parse(content);
78
+
79
+ if (!SmartRouter.validateRoutingConfig(parsed)) {
80
+ console.warn(
81
+ `Invalid routing config structure in ${configPath}; falling back to defaults`,
82
+ );
83
+ return SmartRouter.defaultConfig();
84
+ }
85
+
86
+ return parsed;
87
+ } catch {
88
+ // File doesn't exist yet — use defaults silently
89
+ return SmartRouter.defaultConfig();
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Valide la structure d'une RoutingConfig chargée depuis un fichier non fiable.
95
+ * Fail-safe : retourne false sur toute incohérence structurelle.
96
+ */
97
+ static validateRoutingConfig(config: unknown): config is RoutingConfig {
98
+ if (typeof config !== "object" || config === null) {
99
+ return false;
100
+ }
101
+
102
+ const candidate = config as Record<string, unknown>;
103
+
104
+ const routes = candidate.routes;
105
+ if (typeof routes !== "object" || routes === null) {
106
+ return false;
107
+ }
108
+
109
+ for (const route of Object.values(routes as Record<string, unknown>)) {
110
+ if (typeof route !== "object" || route === null) {
111
+ return false;
112
+ }
113
+ const r = route as Record<string, unknown>;
114
+ if (typeof r.preferredModel !== "string" || typeof r.fallback !== "string") {
115
+ return false;
116
+ }
117
+ if (!(r.agent === null || typeof r.agent === "string")) {
118
+ return false;
119
+ }
120
+ if (!Array.isArray(r.keywords) || !r.keywords.every((k) => typeof k === "string")) {
121
+ return false;
122
+ }
123
+ }
124
+
125
+ const defaults = candidate.default;
126
+ if (typeof defaults !== "object" || defaults === null) {
127
+ return false;
128
+ }
129
+ const d = defaults as Record<string, unknown>;
130
+ if (typeof d.model !== "string") {
131
+ return false;
132
+ }
133
+ if (!(d.agent === null || typeof d.agent === "string")) {
134
+ return false;
135
+ }
136
+
137
+ return true;
138
+ }
139
+
140
+ /**
141
+ * Default configuration with provider-agnostic model names.
142
+ * Uses 'default' as model placeholder — the actual model is determined
143
+ * at runtime by /phi-init or the user's routing.json.
144
+ */
145
+ static defaultConfig(): RoutingConfig {
146
+ return {
147
+ routes: {
148
+ code: {
149
+ preferredModel: "default",
150
+ fallback: "default",
151
+ agent: null,
152
+ keywords: [
153
+ "code",
154
+ "implement",
155
+ "write",
156
+ "create",
157
+ "build",
158
+ "développer",
159
+ "coder",
160
+ "programmer",
161
+ "function",
162
+ "class",
163
+ "method",
164
+ ],
165
+ },
166
+ debug: {
167
+ preferredModel: "default",
168
+ fallback: "default",
169
+ agent: null,
170
+ keywords: [
171
+ "debug",
172
+ "fix",
173
+ "error",
174
+ "bug",
175
+ "broken",
176
+ "issue",
177
+ "problem",
178
+ "repair",
179
+ "correct",
180
+ "erreur",
181
+ "problème",
182
+ "réparer",
183
+ ],
184
+ },
185
+ explore: {
186
+ preferredModel: "default",
187
+ fallback: "default",
188
+ agent: null,
189
+ keywords: [
190
+ "explore",
191
+ "understand",
192
+ "analyze",
193
+ "examine",
194
+ "investigate",
195
+ "study",
196
+ "review",
197
+ "explorer",
198
+ "analyser",
199
+ "comprendre",
200
+ ],
201
+ },
202
+ plan: {
203
+ preferredModel: "default",
204
+ fallback: "default",
205
+ agent: null,
206
+ keywords: [
207
+ "plan",
208
+ "design",
209
+ "architecture",
210
+ "strategy",
211
+ "approach",
212
+ "structure",
213
+ "organize",
214
+ "concevoir",
215
+ "planifier",
216
+ "architecture",
217
+ ],
218
+ },
219
+ test: {
220
+ preferredModel: "default",
221
+ fallback: "default",
222
+ agent: null,
223
+ keywords: [
224
+ "test",
225
+ "testing",
226
+ "unit",
227
+ "integration",
228
+ "verify",
229
+ "validate",
230
+ "check",
231
+ "tester",
232
+ "vérifier",
233
+ "valider",
234
+ ],
235
+ },
236
+ review: {
237
+ preferredModel: "default",
238
+ fallback: "default",
239
+ agent: null,
240
+ keywords: [
241
+ "review",
242
+ "audit",
243
+ "check",
244
+ "validate",
245
+ "quality",
246
+ "improve",
247
+ "optimize",
248
+ "réviser",
249
+ "améliorer",
250
+ "optimiser",
251
+ ],
252
+ },
253
+ general: {
254
+ preferredModel: "default",
255
+ fallback: "default",
256
+ agent: null,
257
+ keywords: [
258
+ "help",
259
+ "explain",
260
+ "what",
261
+ "how",
262
+ "why",
263
+ "question",
264
+ "aide",
265
+ "expliquer",
266
+ "comment",
267
+ "pourquoi",
268
+ ],
269
+ },
270
+ },
271
+ default: {
272
+ model: "default",
273
+ agent: null,
274
+ },
275
+ };
276
+ }
277
+ }