@dungle-scrubs/synapse 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0](https://github.com/dungle-scrubs/synapse/releases/tag/v0.1.0) (2026-02-17)
9
+
10
+ ### Added
11
+
12
+ - **matrix:** `MODEL_MATRIX` capability ratings for 30+ models across code, vision, text
13
+ - **matrix:** `getModelRatings` with longest-prefix matching and provider stripping
14
+ - **matrix:** `modelSupportsTask` for capability/complexity filtering
15
+ - **resolver:** `resolveModelFuzzy` with 6-tier resolution cascade (exact → case-insensitive →
16
+ normalized → provider/id → token overlap → substring)
17
+ - **resolver:** `resolveModelCandidates` for scoped routing (returns all tied matches)
18
+ - **resolver:** `listAvailableModels` for error messages
19
+ - **classifier:** `classifyTask` using in-process `completeSimple` instead of CLI subprocess
20
+ - **classifier:** `findCheapestModel` by effective cost
21
+ - **selector:** `selectModels` with eco/balanced/premium cost preference sorting
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kevin Frilot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # synapse
2
+
3
+ Model capability matrix, fuzzy resolver, task classifier, and selection
4
+ algorithm for [pi-ai](https://github.com/nicobrinkkemper/pi-ai) models.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npm install @dungle-scrubs/synapse
10
+ ```
11
+
12
+ Requires `@mariozechner/pi-ai` as a peer dependency.
13
+
14
+ ## Usage
15
+
16
+ ```typescript
17
+ import {
18
+ classifyTask,
19
+ resolveModelFuzzy,
20
+ selectModels,
21
+ MODEL_MATRIX,
22
+ } from "synapse";
23
+
24
+ // Fuzzy resolve a human-friendly model name
25
+ const model = resolveModelFuzzy("opus");
26
+ // → { provider: "anthropic", id: "claude-opus-4-6", displayName: "anthropic/claude-opus-4-6" }
27
+
28
+ // Classify a task's type and complexity
29
+ const classification = await classifyTask("Refactor the auth module", "code");
30
+ // → { type: "code", complexity: 3, reasoning: "..." }
31
+
32
+ // Select ranked models for the task
33
+ const ranked = selectModels(classification, "balanced");
34
+ // → [{ provider: "anthropic", id: "claude-sonnet-4-5-...", ... }, ...]
35
+ ```
36
+
37
+ ## API
38
+
39
+ ### Matrix
40
+
41
+ - `MODEL_MATRIX` — Capability ratings for known models
42
+ - `getModelRatings(modelId)` — Look up ratings by model ID (prefix matching)
43
+ - `modelSupportsTask(modelId, type, minRating)` — Check if a model supports a task type
44
+
45
+ ### Resolver
46
+
47
+ - `resolveModelFuzzy(query)` — Fuzzy resolve a name to a single model
48
+ - `resolveModelCandidates(query)` — Resolve to all tied candidates
49
+ - `listAvailableModels()` — List all registered models
50
+
51
+ ### Classifier
52
+
53
+ - `classifyTask(task, primaryType, agentRole?)` — Classify task type and complexity
54
+ - `findCheapestModel()` — Find the cheapest available model
55
+
56
+ ### Selector
57
+
58
+ - `selectModels(classification, costPreference, pool?)` — Rank models for a classified task
59
+
60
+ ## License
61
+
62
+ MIT
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Task classification via cheap LLM call.
3
+ *
4
+ * Determines a task's type (code/vision/text) and complexity (1-5)
5
+ * using the cheapest available model from the registry. Uses pi-ai's
6
+ * `completeSimple` for in-process inference — no CLI subprocess.
7
+ */
8
+ import type { ClassificationResult, TaskType } from "./types.js";
9
+ /** Cheapest model info with the data needed to call completeSimple. */
10
+ interface CheapestModelInfo {
11
+ provider: string;
12
+ id: string;
13
+ }
14
+ /**
15
+ * Finds the cheapest available model by effective cost.
16
+ *
17
+ * @returns Provider and ID of the cheapest model, or undefined if no models available
18
+ */
19
+ export declare function findCheapestModel(): CheapestModelInfo | undefined;
20
+ /**
21
+ * Classify a task's type and complexity using the cheapest available LLM.
22
+ *
23
+ * Picks the cheapest model from the registry by (input + output) / 2 cost,
24
+ * sends a structured classification prompt via `completeSimple`, and parses
25
+ * the JSON response. Falls back to primaryType + complexity 3 on any failure.
26
+ *
27
+ * @param task - The task description to classify
28
+ * @param primaryType - Agent's default type (used when ambiguous or on failure)
29
+ * @param agentRole - Optional agent role for additional context
30
+ * @returns Classification result with type, complexity, and reasoning
31
+ */
32
+ export declare function classifyTask(task: string, primaryType: TaskType, agentRole?: string): Promise<ClassificationResult>;
33
+ export {};
34
+ //# sourceMappingURL=classifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.d.ts","sourceRoot":"","sources":["../src/classifier.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAkB,QAAQ,EAAE,MAAM,YAAY,CAAC;AAQjF,uEAAuE;AACvE,UAAU,iBAAiB;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACX;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,GAAG,SAAS,CAejE;AA0ED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CACjC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,QAAQ,EACrB,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,oBAAoB,CAAC,CAwC/B"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Task classification via cheap LLM call.
3
+ *
4
+ * Determines a task's type (code/vision/text) and complexity (1-5)
5
+ * using the cheapest available model from the registry. Uses pi-ai's
6
+ * `completeSimple` for in-process inference — no CLI subprocess.
7
+ */
8
+ import { completeSimple, getModel, getModels, getProviders } from "@mariozechner/pi-ai";
9
+ /** Valid task types for validation. */
10
+ const VALID_TYPES = new Set(["code", "vision", "text"]);
11
+ /** Valid complexity values for validation. */
12
+ const VALID_COMPLEXITIES = new Set([1, 2, 3, 4, 5]);
13
+ /**
14
+ * Finds the cheapest available model by effective cost.
15
+ *
16
+ * @returns Provider and ID of the cheapest model, or undefined if no models available
17
+ */
18
+ export function findCheapestModel() {
19
+ let cheapest;
20
+ let cheapestCost = Number.POSITIVE_INFINITY;
21
+ for (const provider of getProviders()) {
22
+ for (const m of getModels(provider)) {
23
+ const effective = (m.cost.input + m.cost.output) / 2;
24
+ if (effective < cheapestCost) {
25
+ cheapestCost = effective;
26
+ cheapest = { provider: m.provider, id: m.id };
27
+ }
28
+ }
29
+ }
30
+ return cheapest;
31
+ }
32
+ /**
33
+ * Builds the classification prompt for the LLM.
34
+ *
35
+ * @param task - Task description
36
+ * @param primaryType - Agent's default type
37
+ * @param agentRole - Optional agent role context
38
+ * @returns Formatted prompt string
39
+ */
40
+ function buildPrompt(task, primaryType, agentRole) {
41
+ const roleLine = agentRole ? `\nAgent role: ${agentRole}` : "";
42
+ return `Classify this task on two axes.
43
+
44
+ TYPE — what LLM capability is needed:
45
+ - code: writing, refactoring, debugging, reviewing code
46
+ - vision: analyzing images, screenshots, UI mockups
47
+ - text: writing docs, planning, general reasoning, research
48
+
49
+ COMPLEXITY (1-5):
50
+ 1 = Trivial (rename file, simple lookup, basic edit)
51
+ 2 = Simple (single-file change, add test, fix typo)
52
+ 3 = Moderate (multi-file change, implement function, debug)
53
+ 4 = Complex (design + implement feature, architecture)
54
+ 5 = Expert (cross-system design, security audit, optimization)
55
+
56
+ Default type for this agent: ${primaryType}
57
+ Use the default unless the task clearly requires a different type.
58
+
59
+ Task: ${task}${roleLine}
60
+
61
+ Respond with JSON only: {"type": "<type>", "complexity": <1-5>, "reasoning": "<one line>"}`;
62
+ }
63
+ /**
64
+ * Extracts and parses JSON from LLM output that may contain markdown fences.
65
+ *
66
+ * @param raw - Raw LLM output string
67
+ * @returns Parsed object, or undefined on failure
68
+ */
69
+ function extractJson(raw) {
70
+ // Strip markdown code fences if present
71
+ const fenceMatch = raw.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
72
+ const jsonStr = fenceMatch ? fenceMatch[1] : raw;
73
+ try {
74
+ return JSON.parse(jsonStr.trim());
75
+ }
76
+ catch {
77
+ // Try to find a JSON object anywhere in the string
78
+ const objectMatch = jsonStr.match(/\{[\s\S]*\}/);
79
+ if (objectMatch) {
80
+ try {
81
+ return JSON.parse(objectMatch[0]);
82
+ }
83
+ catch {
84
+ return undefined;
85
+ }
86
+ }
87
+ return undefined;
88
+ }
89
+ }
90
+ /**
91
+ * Extracts text content from an AssistantMessage's content array.
92
+ *
93
+ * @param content - Array of content blocks from pi-ai response
94
+ * @returns Concatenated text content
95
+ */
96
+ function extractText(content) {
97
+ return content
98
+ .filter((c) => c.type === "text" && c.text)
99
+ .map((c) => c.text)
100
+ .join("");
101
+ }
102
+ /**
103
+ * Classify a task's type and complexity using the cheapest available LLM.
104
+ *
105
+ * Picks the cheapest model from the registry by (input + output) / 2 cost,
106
+ * sends a structured classification prompt via `completeSimple`, and parses
107
+ * the JSON response. Falls back to primaryType + complexity 3 on any failure.
108
+ *
109
+ * @param task - The task description to classify
110
+ * @param primaryType - Agent's default type (used when ambiguous or on failure)
111
+ * @param agentRole - Optional agent role for additional context
112
+ * @returns Classification result with type, complexity, and reasoning
113
+ */
114
+ export async function classifyTask(task, primaryType, agentRole) {
115
+ const fallback = {
116
+ type: primaryType,
117
+ complexity: 3,
118
+ reasoning: "fallback: classification unavailable",
119
+ };
120
+ const cheapest = findCheapestModel();
121
+ if (!cheapest)
122
+ return fallback;
123
+ const prompt = buildPrompt(task, primaryType, agentRole);
124
+ try {
125
+ const model = getModel(cheapest.provider, cheapest.id);
126
+ const response = await completeSimple(model, {
127
+ messages: [
128
+ { role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() },
129
+ ],
130
+ });
131
+ const output = extractText(response.content);
132
+ const parsed = extractJson(output);
133
+ if (!parsed)
134
+ return fallback;
135
+ const type = String(parsed.type);
136
+ const complexity = Number(parsed.complexity);
137
+ const reasoning = String(parsed.reasoning ?? "");
138
+ if (!VALID_TYPES.has(type) || !VALID_COMPLEXITIES.has(complexity)) {
139
+ return fallback;
140
+ }
141
+ return {
142
+ type: type,
143
+ complexity: complexity,
144
+ reasoning,
145
+ };
146
+ }
147
+ catch {
148
+ return fallback;
149
+ }
150
+ }
151
+ //# sourceMappingURL=classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.js","sourceRoot":"","sources":["../src/classifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * synapse — Model capability matrix, fuzzy resolver, task classifier,
3
+ * and selection algorithm for pi-ai models.
4
+ *
5
+ * @module synapse
6
+ */
7
+ export type { CandidateModel, ClassificationResult, CostPreference, ModelRatings, ModelSource, ResolvedModel, TaskComplexity, TaskType, } from "./types.js";
8
+ export { getModelRatings, MODEL_MATRIX, modelSupportsTask } from "./matrix.js";
9
+ export { listAvailableModels, resolveModelCandidates, resolveModelFuzzy } from "./resolver.js";
10
+ export { classifyTask, findCheapestModel } from "./classifier.js";
11
+ export { selectModels } from "./selector.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACX,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,YAAY,EACZ,WAAW,EACX,aAAa,EACb,cAAc,EACd,QAAQ,GACR,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAG/E,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAG/F,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGlE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * synapse — Model capability matrix, fuzzy resolver, task classifier,
3
+ * and selection algorithm for pi-ai models.
4
+ *
5
+ * @module synapse
6
+ */
7
+ // Matrix
8
+ export { getModelRatings, MODEL_MATRIX, modelSupportsTask } from "./matrix.js";
9
+ // Resolver
10
+ export { listAvailableModels, resolveModelCandidates, resolveModelFuzzy } from "./resolver.js";
11
+ // Classifier
12
+ export { classifyTask, findCheapestModel } from "./classifier.js";
13
+ // Selector
14
+ export { selectModels } from "./selector.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAcH,SAAS;AACT,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE/E,WAAW;AACX,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE/F,aAAa;AACb,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAElE,WAAW;AACX,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Multi-dimensional model capability matrix.
3
+ *
4
+ * Maps model ID prefixes to per-task-type capability ratings.
5
+ * Source: Arena leaderboards (arena.ai/leaderboard/*).
6
+ */
7
+ import type { ModelRatings, TaskType } from "./types.js";
8
+ /**
9
+ * Multi-dimensional model capability matrix.
10
+ *
11
+ * Source: Arena leaderboards (arena.ai/leaderboard/*), Feb 2026.
12
+ *
13
+ * ELO → tier mapping per leaderboard (each has different ELO scale):
14
+ * Code: 5=≥1440 4=1370-1439 3=1280-1369 2=1180-1279 1=<1180
15
+ * Vision: 5=≥1250 4=1200-1249 3=1150-1199 2=1100-1149 1=<1100
16
+ * Text: 5=≥1460 4=1410-1459 3=1370-1409 2=1320-1369 1=<1320
17
+ *
18
+ * Ratings use base model scores — no thinking, default effort.
19
+ */
20
+ export declare const MODEL_MATRIX: Record<string, ModelRatings>;
21
+ /**
22
+ * Get capability ratings for a model by its ID.
23
+ *
24
+ * Uses longest-prefix matching: strips provider prefix (e.g. "anthropic/"),
25
+ * then finds the longest key in MODEL_MATRIX that the model ID starts with.
26
+ * E.g., "claude-sonnet-4-5-20250929" matches "claude-sonnet-4-5".
27
+ *
28
+ * @param modelId - Full model ID (may include date suffixes)
29
+ * @returns Capability ratings, or undefined if model not in matrix
30
+ */
31
+ export declare function getModelRatings(modelId: string): ModelRatings | undefined;
32
+ /**
33
+ * Check if a model supports a given task type at the required complexity level.
34
+ *
35
+ * @param modelId - Full model ID
36
+ * @param type - Required task type
37
+ * @param minRating - Minimum required rating (1-5)
38
+ * @returns true if the model has a rating for the type >= minRating
39
+ */
40
+ export declare function modelSupportsTask(modelId: string, type: TaskType, minRating: number): boolean;
41
+ //# sourceMappingURL=matrix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matrix.d.ts","sourceRoot":"","sources":["../src/matrix.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA6CrD,CAAC;AAKF;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAIzE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAK7F"}
package/dist/matrix.js ADDED
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Multi-dimensional model capability matrix.
3
+ *
4
+ * Maps model ID prefixes to per-task-type capability ratings.
5
+ * Source: Arena leaderboards (arena.ai/leaderboard/*).
6
+ */
7
+ /**
8
+ * Multi-dimensional model capability matrix.
9
+ *
10
+ * Source: Arena leaderboards (arena.ai/leaderboard/*), Feb 2026.
11
+ *
12
+ * ELO → tier mapping per leaderboard (each has different ELO scale):
13
+ * Code: 5=≥1440 4=1370-1439 3=1280-1369 2=1180-1279 1=<1180
14
+ * Vision: 5=≥1250 4=1200-1249 3=1150-1199 2=1100-1149 1=<1100
15
+ * Text: 5=≥1460 4=1410-1459 3=1370-1409 2=1320-1369 1=<1320
16
+ *
17
+ * Ratings use base model scores — no thinking, default effort.
18
+ */
19
+ export const MODEL_MATRIX = {
20
+ // Anthropic
21
+ "claude-opus-4-6": { code: 5, vision: 3, text: 5 },
22
+ "claude-opus-4-5": { code: 5, vision: 3, text: 5 },
23
+ "claude-opus-4-1": { code: 4, vision: 3, text: 4 },
24
+ "claude-sonnet-4-5": { code: 4, vision: 3, text: 4 },
25
+ "claude-haiku-4-5": { code: 3, vision: 2, text: 3 },
26
+ // OpenAI
27
+ "gpt-5.2": { code: 4, vision: 4, text: 4 },
28
+ "gpt-5": { code: 4, vision: 4, text: 4 },
29
+ "gpt-5.1": { code: 3, vision: 4, text: 4 },
30
+ "gpt-5.3-codex": { code: 4, text: 4 },
31
+ "gpt-5.3-codex-spark": { code: 2, text: 2 },
32
+ "gpt-5.2-codex": { code: 3, text: 3 },
33
+ "gpt-5.1-codex-max": { code: 4, text: 4 },
34
+ "gpt-5.1-codex": { code: 3, text: 3 },
35
+ "gpt-5.1-codex-mini": { code: 2, text: 2 },
36
+ // Google
37
+ "gemini-3-pro": { code: 5, vision: 5, text: 5 },
38
+ "gemini-3-flash": { code: 5, vision: 5, text: 5 },
39
+ "gemini-2.5-pro": { code: 2, vision: 4, text: 4 },
40
+ "gemini-2.5-flash": { vision: 4, text: 4 },
41
+ // Z.ai (Zhipu)
42
+ "glm-5": { code: 5, text: 5 },
43
+ "glm-4.7": { code: 5, text: 4 },
44
+ "glm-4.6": { code: 3, vision: 3, text: 4 },
45
+ // DeepSeek
46
+ "deepseek-reasoner": { code: 4, text: 4 },
47
+ "deepseek-chat": { code: 3, text: 4 },
48
+ // MiniMax
49
+ "minimax-m2.1": { code: 4, text: 3 },
50
+ "minimax-m2": { code: 3, text: 2 },
51
+ // Moonshot (Kimi)
52
+ "kimi-k2.5": { code: 4, text: 4 },
53
+ "kimi-k2": { code: 3, text: 4 },
54
+ // Qwen (Alibaba)
55
+ "qwen3-coder": { code: 3, text: 3 },
56
+ "qwen3-max": { text: 4 },
57
+ // xAI
58
+ "grok-4.1": { code: 2, text: 5 },
59
+ "grok-4": { code: 1, text: 4 },
60
+ // Mistral
61
+ "mistral-large-3": { code: 2, text: 4 },
62
+ "devstral-2": { code: 2 },
63
+ "devstral-medium": { code: 1 },
64
+ };
65
+ /** Sorted keys longest-first for prefix matching. */
66
+ const SORTED_KEYS = Object.keys(MODEL_MATRIX).sort((a, b) => b.length - a.length);
67
+ /**
68
+ * Get capability ratings for a model by its ID.
69
+ *
70
+ * Uses longest-prefix matching: strips provider prefix (e.g. "anthropic/"),
71
+ * then finds the longest key in MODEL_MATRIX that the model ID starts with.
72
+ * E.g., "claude-sonnet-4-5-20250929" matches "claude-sonnet-4-5".
73
+ *
74
+ * @param modelId - Full model ID (may include date suffixes)
75
+ * @returns Capability ratings, or undefined if model not in matrix
76
+ */
77
+ export function getModelRatings(modelId) {
78
+ const bare = modelId.includes("/") ? modelId.slice(modelId.indexOf("/") + 1) : modelId;
79
+ const key = SORTED_KEYS.find((k) => bare.startsWith(k));
80
+ return key ? MODEL_MATRIX[key] : undefined;
81
+ }
82
+ /**
83
+ * Check if a model supports a given task type at the required complexity level.
84
+ *
85
+ * @param modelId - Full model ID
86
+ * @param type - Required task type
87
+ * @param minRating - Minimum required rating (1-5)
88
+ * @returns true if the model has a rating for the type >= minRating
89
+ */
90
+ export function modelSupportsTask(modelId, type, minRating) {
91
+ const ratings = getModelRatings(modelId);
92
+ if (!ratings)
93
+ return false;
94
+ const rating = ratings[type];
95
+ return rating !== undefined && rating >= minRating;
96
+ }
97
+ //# sourceMappingURL=matrix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matrix.js","sourceRoot":"","sources":["../src/matrix.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Fuzzy model name resolution against the pi-ai model registry.
3
+ *
4
+ * Resolves human-friendly names like "opus", "sonnet 4.5" to exact
5
+ * provider/model-id pairs using a 6-tier resolution cascade.
6
+ */
7
+ import type { ModelSource, ResolvedModel } from "./types.js";
8
+ /**
9
+ * Resolves a human-friendly model name to a single exact provider/model-id.
10
+ *
11
+ * Finds all tied candidates via the resolution cascade, then picks the
12
+ * best one using capability score → shortest ID → lexicographic ordering.
13
+ *
14
+ * @param query - Human-friendly model name (e.g. "opus", "sonnet 4.5", "claude-opus-4-5")
15
+ * @param modelSource - Optional model-fetching function (defaults to pi-ai registry)
16
+ * @returns Resolved model, or undefined if no match found
17
+ */
18
+ export declare function resolveModelFuzzy(query: string, modelSource?: ModelSource): ResolvedModel | undefined;
19
+ /**
20
+ * Resolves a human-friendly model name to ALL tied candidates.
21
+ *
22
+ * Same resolution cascade as `resolveModelFuzzy`, but returns every model
23
+ * that ties at the best score instead of picking one. Used by the model
24
+ * router for scoped auto-routing: "codex" → all codex models → classify
25
+ * task → pick the right one for the job.
26
+ *
27
+ * @param query - Human-friendly model name (e.g. "codex", "opus", "gemini flash")
28
+ * @param modelSource - Optional model-fetching function (defaults to pi-ai registry)
29
+ * @returns Array of resolved models (may be empty if no match)
30
+ */
31
+ export declare function resolveModelCandidates(query: string, modelSource?: ModelSource): ResolvedModel[];
32
+ /**
33
+ * Lists all available models from the registry for error messages.
34
+ *
35
+ * @param modelSource - Optional model-fetching function (defaults to pi-ai registry)
36
+ * @returns Array of model display strings ("provider/id")
37
+ */
38
+ export declare function listAvailableModels(modelSource?: ModelSource): string[];
39
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAkB,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAsL7E;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,WAAW,GACvB,aAAa,GAAG,SAAS,CAI3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,aAAa,EAAE,CAEhG;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,MAAM,EAAE,CAGvE"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Fuzzy model name resolution against the pi-ai model registry.
3
+ *
4
+ * Resolves human-friendly names like "opus", "sonnet 4.5" to exact
5
+ * provider/model-id pairs using a 6-tier resolution cascade.
6
+ */
7
+ import { getModels, getProviders } from "@mariozechner/pi-ai";
8
+ import { getModelRatings } from "./matrix.js";
9
+ /**
10
+ * Collects all models from every registered provider.
11
+ *
12
+ * @returns Flat array of candidate models with provider, id, and name
13
+ */
14
+ function getAllModels() {
15
+ const result = [];
16
+ for (const provider of getProviders()) {
17
+ for (const m of getModels(provider)) {
18
+ result.push({ provider: m.provider, id: m.id, name: m.name });
19
+ }
20
+ }
21
+ return result;
22
+ }
23
+ /**
24
+ * Builds a ResolvedModel from a candidate.
25
+ *
26
+ * @param m - Candidate model
27
+ * @returns ResolvedModel with display name
28
+ */
29
+ function toResolved(m) {
30
+ return { provider: m.provider, id: m.id, displayName: `${m.provider}/${m.id}` };
31
+ }
32
+ /**
33
+ * Sum of all capability ratings for a model from the matrix.
34
+ * Higher = more capable. Returns 0 if not in matrix.
35
+ *
36
+ * @param id - Model ID
37
+ * @returns Total capability score
38
+ */
39
+ function capabilityScore(id) {
40
+ const ratings = getModelRatings(id);
41
+ if (!ratings)
42
+ return 0;
43
+ return Object.values(ratings).reduce((sum, v) => sum + (v ?? 0), 0);
44
+ }
45
+ /**
46
+ * Picks the best candidate from a list of fuzzy-match ties.
47
+ *
48
+ * Tiebreak order:
49
+ * 1. Highest capability score (from model matrix) — picks the most capable model
50
+ * 2. Shortest model ID — prefers concise canonical names over variants
51
+ * 3. Lexicographically last — higher version numbers win ("5.3" > "5.2")
52
+ *
53
+ * @param models - Array of candidates to pick from
54
+ * @returns The best candidate by capability-then-shortest-then-latest
55
+ */
56
+ function pickBest(models) {
57
+ return models.reduce((a, b) => {
58
+ const aCap = capabilityScore(a.id);
59
+ const bCap = capabilityScore(b.id);
60
+ if (aCap !== bCap)
61
+ return aCap > bCap ? a : b;
62
+ if (a.id.length !== b.id.length)
63
+ return a.id.length < b.id.length ? a : b;
64
+ return a.id >= b.id ? a : b;
65
+ });
66
+ }
67
+ /**
68
+ * Splits a string into lowercase tokens on spaces, hyphens, underscores, dots,
69
+ * and word↔digit boundaries (e.g. "codex5" → ["codex", "5"]).
70
+ *
71
+ * @param s - Input string
72
+ * @returns Array of non-empty lowercase tokens
73
+ */
74
+ function tokenize(s) {
75
+ return s
76
+ .toLowerCase()
77
+ .replace(/([a-z])(\d)/g, "$1 $2")
78
+ .replace(/(\d)([a-z])/g, "$1 $2")
79
+ .split(/[\s\-_.]+/)
80
+ .filter((t) => t.length > 0);
81
+ }
82
+ /**
83
+ * Strips all common separators and lowercases for normalized comparison.
84
+ * "glm-5" → "glm5", "claude-sonnet-4-5" → "claudesonnet45"
85
+ *
86
+ * @param s - Input string
87
+ * @returns Normalized string with separators removed
88
+ */
89
+ function normalize(s) {
90
+ return s.toLowerCase().replace(/[\s\-_.]+/g, "");
91
+ }
92
+ /**
93
+ * Finds all fuzzy-matched candidates for a query (before tiebreaking).
94
+ *
95
+ * Returns all models that tie at the best score for whichever resolution
96
+ * tier first produces a match. Used by `resolveModelFuzzy` (picks one)
97
+ * and `resolveModelCandidates` (returns all for scoped routing).
98
+ *
99
+ * Resolution cascade:
100
+ * 1. Exact ID match across all providers
101
+ * 2. Case-insensitive ID match
102
+ * 2.5. Normalized match — strips separators ("glm5" → "glm-5")
103
+ * 3. Provider/ID format (e.g. "anthropic/claude-sonnet-4-5")
104
+ * 4. Token overlap — split query into tokens, score models by weighted
105
+ * token matches. ID/name matches score 2, provider-only matches score 1.
106
+ * Best score wins.
107
+ * 5. Substring match — query appears as substring in model ID or name
108
+ * 6. Normalized substring — strips separators before substring comparison
109
+ *
110
+ * @param query - Human-friendly model name (e.g. "opus", "sonnet 4.5", "codex")
111
+ * @param modelSource - Optional model-fetching function (defaults to pi-ai registry)
112
+ * @returns Array of tied candidates from the first matching tier, or empty
113
+ */
114
+ function findCandidates(query, modelSource) {
115
+ const models = modelSource ? modelSource() : getAllModels();
116
+ if (models.length === 0)
117
+ return [];
118
+ const q = query.trim();
119
+ if (q.length === 0)
120
+ return [];
121
+ const qLower = q.toLowerCase();
122
+ // 1. Exact ID match
123
+ const exact = models.filter((m) => m.id === q);
124
+ if (exact.length > 0)
125
+ return exact;
126
+ // 2. Case-insensitive ID match
127
+ const ciMatch = models.filter((m) => m.id.toLowerCase() === qLower);
128
+ if (ciMatch.length > 0)
129
+ return ciMatch;
130
+ // 2.5. Normalized match — strips separators ("glm5" matches "glm-5")
131
+ const qNorm = normalize(q);
132
+ const normMatch = models.filter((m) => normalize(m.id) === qNorm);
133
+ if (normMatch.length > 0)
134
+ return normMatch;
135
+ // 3. Provider/ID format
136
+ if (q.includes("/")) {
137
+ const slashIdx = q.indexOf("/");
138
+ const provider = q.slice(0, slashIdx).toLowerCase();
139
+ const id = q.slice(slashIdx + 1).toLowerCase();
140
+ const providerMatch = models.filter((m) => m.provider.toLowerCase() === provider && m.id.toLowerCase() === id);
141
+ if (providerMatch.length > 0)
142
+ return providerMatch;
143
+ }
144
+ // 4. Token overlap scoring (ID/name matches weighted 2×, provider-only 1×)
145
+ const queryTokens = tokenize(q);
146
+ if (queryTokens.length > 0) {
147
+ let bestScore = 0;
148
+ let bestMatches = [];
149
+ for (const m of models) {
150
+ const idName = `${m.id} ${m.name}`.toLowerCase();
151
+ const providerStr = m.provider.toLowerCase();
152
+ let score = 0;
153
+ for (const t of queryTokens) {
154
+ if (idName.includes(t))
155
+ score += 2;
156
+ else if (providerStr.includes(t))
157
+ score += 1;
158
+ }
159
+ if (score > bestScore) {
160
+ bestScore = score;
161
+ bestMatches = [m];
162
+ }
163
+ else if (score === bestScore && score > 0) {
164
+ bestMatches.push(m);
165
+ }
166
+ }
167
+ if (bestScore > 0 && bestMatches.length > 0)
168
+ return bestMatches;
169
+ }
170
+ // 5. Substring match (raw)
171
+ const subMatches = models.filter((m) => m.id.toLowerCase().includes(qLower) || m.name.toLowerCase().includes(qLower));
172
+ if (subMatches.length > 0)
173
+ return subMatches;
174
+ // 6. Substring match (normalized — strips separators before comparing)
175
+ const normSubMatches = models.filter((m) => normalize(m.id).includes(qNorm) || normalize(m.name).includes(qNorm));
176
+ if (normSubMatches.length > 0)
177
+ return normSubMatches;
178
+ return [];
179
+ }
180
+ /**
181
+ * Resolves a human-friendly model name to a single exact provider/model-id.
182
+ *
183
+ * Finds all tied candidates via the resolution cascade, then picks the
184
+ * best one using capability score → shortest ID → lexicographic ordering.
185
+ *
186
+ * @param query - Human-friendly model name (e.g. "opus", "sonnet 4.5", "claude-opus-4-5")
187
+ * @param modelSource - Optional model-fetching function (defaults to pi-ai registry)
188
+ * @returns Resolved model, or undefined if no match found
189
+ */
190
+ export function resolveModelFuzzy(query, modelSource) {
191
+ const candidates = findCandidates(query, modelSource);
192
+ if (candidates.length === 0)
193
+ return undefined;
194
+ return toResolved(pickBest(candidates));
195
+ }
196
+ /**
197
+ * Resolves a human-friendly model name to ALL tied candidates.
198
+ *
199
+ * Same resolution cascade as `resolveModelFuzzy`, but returns every model
200
+ * that ties at the best score instead of picking one. Used by the model
201
+ * router for scoped auto-routing: "codex" → all codex models → classify
202
+ * task → pick the right one for the job.
203
+ *
204
+ * @param query - Human-friendly model name (e.g. "codex", "opus", "gemini flash")
205
+ * @param modelSource - Optional model-fetching function (defaults to pi-ai registry)
206
+ * @returns Array of resolved models (may be empty if no match)
207
+ */
208
+ export function resolveModelCandidates(query, modelSource) {
209
+ return findCandidates(query, modelSource).map(toResolved);
210
+ }
211
+ /**
212
+ * Lists all available models from the registry for error messages.
213
+ *
214
+ * @param modelSource - Optional model-fetching function (defaults to pi-ai registry)
215
+ * @returns Array of model display strings ("provider/id")
216
+ */
217
+ export function listAvailableModels(modelSource) {
218
+ const models = modelSource ? modelSource() : getAllModels();
219
+ return models.map((m) => `${m.provider}/${m.id}`);
220
+ }
221
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Model selection algorithm.
3
+ *
4
+ * Given a classified task and cost preference, ranks available models
5
+ * by suitability. Consumers use the first result and fall back to the rest.
6
+ */
7
+ import type { ClassificationResult, CostPreference, ResolvedModel } from "./types.js";
8
+ /**
9
+ * Selects models for a classified task, ranked by preference.
10
+ *
11
+ * Algorithm:
12
+ * 1. Enumerate all models from registry that have matrix ratings
13
+ * 2. Filter: model has rating for classification.type
14
+ * 3. Filter: rating[type] >= classification.complexity
15
+ * 4. Sort by cost preference:
16
+ * - "eco": ascending by effective cost
17
+ * - "premium": descending by effective cost
18
+ * - "balanced": exact rating match first, then ascending cost
19
+ * 5. Return ranked list (caller uses first, falls back to rest)
20
+ *
21
+ * @param classification - Task classification result
22
+ * @param costPreference - Cost preference for sorting
23
+ * @param pool - Optional pre-resolved model pool (for scoped routing)
24
+ * @returns Ranked list of suitable models (may be empty)
25
+ */
26
+ export declare function selectModels(classification: ClassificationResult, costPreference: CostPreference, pool?: ResolvedModel[]): ResolvedModel[];
27
+ //# sourceMappingURL=selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../src/selector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAEX,oBAAoB,EACpB,cAAc,EAGd,aAAa,EACb,MAAM,YAAY,CAAC;AAuFpB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAC3B,cAAc,EAAE,oBAAoB,EACpC,cAAc,EAAE,cAAc,EAC9B,IAAI,CAAC,EAAE,aAAa,EAAE,GACpB,aAAa,EAAE,CAyBjB"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Model selection algorithm.
3
+ *
4
+ * Given a classified task and cost preference, ranks available models
5
+ * by suitability. Consumers use the first result and fall back to the rest.
6
+ */
7
+ import { getModels, getProviders } from "@mariozechner/pi-ai";
8
+ import { getModelRatings } from "./matrix.js";
9
+ /**
10
+ * Collects all models from every registered provider.
11
+ *
12
+ * @returns Flat array of candidate models
13
+ */
14
+ function getAllModels() {
15
+ const result = [];
16
+ for (const provider of getProviders()) {
17
+ for (const m of getModels(provider)) {
18
+ result.push({ provider: m.provider, id: m.id, name: m.name });
19
+ }
20
+ }
21
+ return result;
22
+ }
23
+ /**
24
+ * Enumerates all models from the registry with their ratings and costs.
25
+ *
26
+ * @param modelSource - Optional model-fetching function (defaults to pi-ai registry)
27
+ * @returns Array of candidates that exist in the capability matrix
28
+ */
29
+ function enumerateCandidates(modelSource) {
30
+ const candidates = [];
31
+ const models = modelSource ? modelSource() : getAllModels();
32
+ for (const provider of getProviders()) {
33
+ for (const regModel of getModels(provider)) {
34
+ // Only include models that are in our source list (or all if no source)
35
+ if (modelSource &&
36
+ !models.some((m) => m.id === regModel.id && m.provider === regModel.provider)) {
37
+ continue;
38
+ }
39
+ const ratings = getModelRatings(regModel.id);
40
+ if (!ratings)
41
+ continue;
42
+ candidates.push({
43
+ resolved: {
44
+ provider: regModel.provider,
45
+ id: regModel.id,
46
+ displayName: `${regModel.provider}/${regModel.id}`,
47
+ },
48
+ ratings,
49
+ effectiveCost: (regModel.cost.input + regModel.cost.output) / 2,
50
+ });
51
+ }
52
+ }
53
+ return candidates;
54
+ }
55
+ /**
56
+ * Converts a set of pre-resolved models into scored candidates.
57
+ *
58
+ * Used for scoped routing: takes fuzzy-matched models and enriches them
59
+ * with ratings and cost data so they can be filtered/sorted by selectModels.
60
+ *
61
+ * @param pool - Pre-resolved models to convert
62
+ * @returns Scored candidates (only those with matrix ratings)
63
+ */
64
+ function candidatesFromPool(pool) {
65
+ const candidates = [];
66
+ for (const resolved of pool) {
67
+ const ratings = getModelRatings(resolved.id);
68
+ if (!ratings)
69
+ continue;
70
+ // Look up cost from registry
71
+ let effectiveCost = 0;
72
+ for (const provider of getProviders()) {
73
+ for (const model of getModels(provider)) {
74
+ if (model.id === resolved.id && model.provider === resolved.provider) {
75
+ effectiveCost = (model.cost.input + model.cost.output) / 2;
76
+ }
77
+ }
78
+ }
79
+ candidates.push({ resolved, ratings, effectiveCost });
80
+ }
81
+ return candidates;
82
+ }
83
+ /**
84
+ * Selects models for a classified task, ranked by preference.
85
+ *
86
+ * Algorithm:
87
+ * 1. Enumerate all models from registry that have matrix ratings
88
+ * 2. Filter: model has rating for classification.type
89
+ * 3. Filter: rating[type] >= classification.complexity
90
+ * 4. Sort by cost preference:
91
+ * - "eco": ascending by effective cost
92
+ * - "premium": descending by effective cost
93
+ * - "balanced": exact rating match first, then ascending cost
94
+ * 5. Return ranked list (caller uses first, falls back to rest)
95
+ *
96
+ * @param classification - Task classification result
97
+ * @param costPreference - Cost preference for sorting
98
+ * @param pool - Optional pre-resolved model pool (for scoped routing)
99
+ * @returns Ranked list of suitable models (may be empty)
100
+ */
101
+ export function selectModels(classification, costPreference, pool) {
102
+ const { type, complexity } = classification;
103
+ const allCandidates = pool ? candidatesFromPool(pool) : enumerateCandidates();
104
+ const candidates = allCandidates.filter((c) => {
105
+ const rating = c.ratings[type];
106
+ return rating !== undefined && rating >= complexity;
107
+ });
108
+ if (candidates.length === 0)
109
+ return [];
110
+ candidates.sort((a, b) => {
111
+ if (costPreference === "eco") {
112
+ return a.effectiveCost - b.effectiveCost;
113
+ }
114
+ if (costPreference === "premium") {
115
+ return b.effectiveCost - a.effectiveCost;
116
+ }
117
+ // "balanced": exact-match rating sorts first, then ascending cost
118
+ const aExact = a.ratings[type] === complexity ? 0 : 1;
119
+ const bExact = b.ratings[type] === complexity ? 0 : 1;
120
+ if (aExact !== bExact)
121
+ return aExact - bExact;
122
+ return a.effectiveCost - b.effectiveCost;
123
+ });
124
+ return candidates.map((c) => c.resolved);
125
+ }
126
+ //# sourceMappingURL=selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.js","sourceRoot":"","sources":["../src/selector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /** LLM task types for routing. */
2
+ export type TaskType = "code" | "vision" | "text";
3
+ /**
4
+ * Per-type capability ratings. Scale: 1 (basic) to 5 (frontier).
5
+ * Missing key = model doesn't support that type.
6
+ */
7
+ export type ModelRatings = Partial<Record<TaskType, number>>;
8
+ /** User's cost preference for model routing. */
9
+ export type CostPreference = "eco" | "balanced" | "premium";
10
+ /** Task complexity level (1-5). */
11
+ export type TaskComplexity = 1 | 2 | 3 | 4 | 5;
12
+ /** Result of task classification. */
13
+ export interface ClassificationResult {
14
+ type: TaskType;
15
+ complexity: TaskComplexity;
16
+ reasoning: string;
17
+ }
18
+ /** A resolved model with provider and display name. */
19
+ export interface ResolvedModel {
20
+ provider: string;
21
+ id: string;
22
+ displayName: string;
23
+ }
24
+ /** Model-fetching function signature for dependency injection. */
25
+ export interface CandidateModel {
26
+ provider: string;
27
+ id: string;
28
+ name: string;
29
+ }
30
+ /** Model source function for dependency injection in resolver/selector. */
31
+ export type ModelSource = () => CandidateModel[];
32
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAElD;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAE7D,gDAAgD;AAChD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;AAE5D,mCAAmC;AACnC,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAE/C,qCAAqC;AACrC,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,cAAc,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,uDAAuD;AACvD,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACb;AAED,2EAA2E;AAC3E,MAAM,MAAM,WAAW,GAAG,MAAM,cAAc,EAAE,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@dungle-scrubs/synapse",
3
+ "version": "0.1.0",
4
+ "description": "Model capability matrix, fuzzy resolver, task classifier, and selection algorithm for pi-ai models",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": ["dist", "README.md", "CHANGELOG.md"],
15
+ "peerDependencies": {
16
+ "@mariozechner/pi-ai": ">=0.50.0"
17
+ },
18
+ "devDependencies": {
19
+ "@biomejs/biome": "^1.9.4",
20
+ "@mariozechner/pi-ai": "0.52.12",
21
+ "@types/bun": "^1.2.5",
22
+ "typescript": "^5.7.3"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "typecheck": "tsc --noEmit",
27
+ "test": "bun test",
28
+ "lint": "biome check src/ __tests__/",
29
+ "lint:fix": "biome check --write src/ __tests__/"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/dungle-scrubs/synapse.git"
34
+ },
35
+ "author": "Kevin Frilot",
36
+ "license": "MIT"
37
+ }