@aiping.cn/model_router 1.2.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.
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalAdapterError = exports.LocalAdapter = void 0;
4
+ /**
5
+ * LocalAdapter forwards requests to an Ollama-compatible local server
6
+ * using the OpenAI-compatible /v1/chat/completions endpoint.
7
+ */
8
+ class LocalAdapter {
9
+ config;
10
+ constructor(config) {
11
+ this.config = config;
12
+ }
13
+ get baseUrl() {
14
+ return this.config.localProxyUrl.replace(/\/$/, '');
15
+ }
16
+ buildHeaders() {
17
+ const headers = {
18
+ 'Content-Type': 'application/json',
19
+ };
20
+ if (this.config.localProxyKey) {
21
+ headers['Authorization'] = `Bearer ${this.config.localProxyKey}`;
22
+ }
23
+ return headers;
24
+ }
25
+ async chat(request) {
26
+ const body = JSON.stringify({
27
+ ...request,
28
+ model: this.config.localModel,
29
+ stream: false,
30
+ });
31
+ const controller = new AbortController();
32
+ const timeoutId = setTimeout(() => controller.abort(), this.config.localTimeoutMs);
33
+ try {
34
+ const res = await fetch(`${this.baseUrl}/v1/chat/completions`, {
35
+ method: 'POST',
36
+ headers: this.buildHeaders(),
37
+ body,
38
+ signal: controller.signal,
39
+ });
40
+ if (!res.ok) {
41
+ const text = await res.text().catch(() => '');
42
+ throw new LocalAdapterError(`Local model returned ${res.status}: ${text}`, res.status);
43
+ }
44
+ const data = (await res.json());
45
+ // Normalise the model field so the caller always sees the virtual model name
46
+ return { ...data, model: request.model };
47
+ }
48
+ catch (err) {
49
+ if (err.name === 'AbortError') {
50
+ throw new LocalAdapterError(`Local model timed out after ${this.config.localTimeoutMs}ms`, 408);
51
+ }
52
+ throw err;
53
+ }
54
+ finally {
55
+ clearTimeout(timeoutId);
56
+ }
57
+ }
58
+ async *chatStream(request) {
59
+ const body = JSON.stringify({
60
+ ...request,
61
+ model: this.config.localModel,
62
+ stream: true,
63
+ });
64
+ const controller = new AbortController();
65
+ const timeoutId = setTimeout(() => controller.abort(), this.config.localTimeoutMs);
66
+ try {
67
+ const res = await fetch(`${this.baseUrl}/v1/chat/completions`, {
68
+ method: 'POST',
69
+ headers: this.buildHeaders(),
70
+ body,
71
+ signal: controller.signal,
72
+ });
73
+ if (!res.ok) {
74
+ const text = await res.text().catch(() => '');
75
+ throw new LocalAdapterError(`Local model returned ${res.status}: ${text}`, res.status);
76
+ }
77
+ if (!res.body)
78
+ throw new LocalAdapterError('Empty response body', 500);
79
+ const reader = res.body.getReader();
80
+ const decoder = new TextDecoder();
81
+ while (true) {
82
+ const { done, value } = await reader.read();
83
+ if (done)
84
+ break;
85
+ yield decoder.decode(value, { stream: true });
86
+ }
87
+ }
88
+ catch (err) {
89
+ if (err.name === 'AbortError') {
90
+ throw new LocalAdapterError(`Local model stream timed out after ${this.config.localTimeoutMs}ms`, 408);
91
+ }
92
+ throw err;
93
+ }
94
+ finally {
95
+ clearTimeout(timeoutId);
96
+ }
97
+ }
98
+ async ping() {
99
+ const start = Date.now();
100
+ try {
101
+ const res = await fetch(`${this.baseUrl}/api/tags`, {
102
+ method: 'GET',
103
+ headers: this.buildHeaders(),
104
+ signal: AbortSignal.timeout(5000),
105
+ });
106
+ const latencyMs = Date.now() - start;
107
+ if (!res.ok) {
108
+ // Fallback: also try OpenAI-compatible /v1/models
109
+ const res2 = await fetch(`${this.baseUrl}/v1/models`, {
110
+ method: 'GET',
111
+ headers: this.buildHeaders(),
112
+ signal: AbortSignal.timeout(5000),
113
+ });
114
+ if (!res2.ok) {
115
+ return { ok: false, latencyMs, error: `HTTP ${res.status}` };
116
+ }
117
+ return { ok: true, latencyMs: Date.now() - start };
118
+ }
119
+ return { ok: true, latencyMs };
120
+ }
121
+ catch (err) {
122
+ return {
123
+ ok: false,
124
+ latencyMs: Date.now() - start,
125
+ error: err.message,
126
+ };
127
+ }
128
+ }
129
+ /** Returns list of model names available on the local server. */
130
+ async listModels() {
131
+ try {
132
+ // Try Ollama native endpoint first
133
+ const res = await fetch(`${this.baseUrl}/api/tags`, {
134
+ headers: this.buildHeaders(),
135
+ signal: AbortSignal.timeout(5000),
136
+ });
137
+ if (res.ok) {
138
+ const data = (await res.json());
139
+ return (data.models ?? []).map((m) => m.name);
140
+ }
141
+ // Fallback: OpenAI-compatible /v1/models
142
+ const res2 = await fetch(`${this.baseUrl}/v1/models`, {
143
+ headers: this.buildHeaders(),
144
+ signal: AbortSignal.timeout(5000),
145
+ });
146
+ if (res2.ok) {
147
+ const data2 = (await res2.json());
148
+ return (data2.data ?? []).map((m) => m.id);
149
+ }
150
+ }
151
+ catch {
152
+ // ignored
153
+ }
154
+ return [];
155
+ }
156
+ }
157
+ exports.LocalAdapter = LocalAdapter;
158
+ class LocalAdapterError extends Error {
159
+ statusCode;
160
+ constructor(message, statusCode) {
161
+ super(message);
162
+ this.statusCode = statusCode;
163
+ this.name = 'LocalAdapterError';
164
+ }
165
+ }
166
+ exports.LocalAdapterError = LocalAdapterError;
167
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/providers/local.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { ChatRequest, RoutingDecision, PluginConfig } from '../types.js';
2
+ import { Scorer } from './scorer.js';
3
+ export declare class Router {
4
+ private readonly scorer;
5
+ private readonly config;
6
+ constructor(config: PluginConfig, scorer?: Scorer);
7
+ decide(request: ChatRequest): RoutingDecision;
8
+ private log;
9
+ }
10
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,YAAY,EACb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM;IAKjD,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,eAAe;IA+B7C,OAAO,CAAC,GAAG;CAQZ"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Router = void 0;
4
+ const scorer_js_1 = require("./scorer.js");
5
+ class Router {
6
+ scorer;
7
+ config;
8
+ constructor(config, scorer) {
9
+ this.config = config;
10
+ this.scorer = scorer ?? new scorer_js_1.Scorer();
11
+ }
12
+ decide(request) {
13
+ const { totalScore, dimensionScores, forced } = this.scorer.score(request);
14
+ if (forced) {
15
+ const decision = {
16
+ target: forced,
17
+ score: totalScore,
18
+ forced: true,
19
+ reasons: dimensionScores.map((d) => `[${d.name}] ${d.reason}`),
20
+ };
21
+ this.log(decision);
22
+ return decision;
23
+ }
24
+ const target = totalScore >= this.config.routingThreshold ? 'cloud' : 'local';
25
+ const decision = {
26
+ target,
27
+ score: totalScore,
28
+ forced: false,
29
+ reasons: [
30
+ `Score ${totalScore} vs threshold ${this.config.routingThreshold} → ${target}`,
31
+ ...dimensionScores
32
+ .filter((d) => d.score > 0)
33
+ .map((d) => `[${d.name}] ${d.reason} (+${d.score})`),
34
+ ],
35
+ };
36
+ this.log(decision);
37
+ return decision;
38
+ }
39
+ log(decision) {
40
+ if (!this.config.debugRouting)
41
+ return;
42
+ console.log(`[aiping:router] → ${decision.target.toUpperCase()} ` +
43
+ `(score=${decision.score}, forced=${decision.forced})\n` +
44
+ decision.reasons.map((r) => ` • ${r}`).join('\n'));
45
+ }
46
+ }
47
+ exports.Router = Router;
48
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":";;;AAKA,2CAAqC;AAErC,MAAa,MAAM;IACA,MAAM,CAAS;IACf,MAAM,CAAe;IAEtC,YAAY,MAAoB,EAAE,MAAe;QAC/C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,IAAI,kBAAM,EAAE,CAAC;IACvC,CAAC;IAED,MAAM,CAAC,OAAoB;QACzB,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAE3E,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,QAAQ,GAAoB;gBAChC,MAAM,EAAE,MAAM;gBACd,KAAK,EAAE,UAAU;gBACjB,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;aAC/D,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACnB,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC9E,MAAM,QAAQ,GAAoB;YAChC,MAAM;YACN,KAAK,EAAE,UAAU;YACjB,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACP,SAAS,UAAU,iBAAiB,IAAI,CAAC,MAAM,CAAC,gBAAgB,MAAM,MAAM,EAAE;gBAC9E,GAAG,eAAe;qBACf,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;qBAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,KAAK,GAAG,CAAC;aACvD;SACF,CAAC;QAEF,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACnB,OAAO,QAAQ,CAAC;IAClB,CAAC;IAEO,GAAG,CAAC,QAAyB;QACnC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY;YAAE,OAAO;QACtC,OAAO,CAAC,GAAG,CACT,qBAAqB,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,GAAG;YACnD,UAAU,QAAQ,CAAC,KAAK,YAAY,QAAQ,CAAC,MAAM,KAAK;YACxD,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACrD,CAAC;IACJ,CAAC;CACF;AAhDD,wBAgDC"}
@@ -0,0 +1,61 @@
1
+ import type { ChatRequest, DimensionScore, RuleScorer } from '../types.js';
2
+ /**
3
+ * Token Count Scorer
4
+ * Long inputs are harder for small local models (limited context window).
5
+ * > 4000 estimated tokens → full 30 points (relaxed to keep ~90% local)
6
+ * Scales linearly from 0 at 1500 tokens to 30 at 4000 tokens.
7
+ */
8
+ export declare class TokenCountScorer implements RuleScorer {
9
+ readonly name = "token_count";
10
+ readonly maxScore = 30;
11
+ private readonly LOW_THRESHOLD;
12
+ private readonly HIGH_THRESHOLD;
13
+ score(request: ChatRequest): DimensionScore;
14
+ }
15
+ /**
16
+ * Code Complexity Scorer
17
+ * Large code blocks require strong reasoning capabilities.
18
+ * Code fences with > 80 lines → full 20 points (relaxed to keep ~90% local).
19
+ */
20
+ export declare class CodeComplexityScorer implements RuleScorer {
21
+ readonly name = "code_complexity";
22
+ readonly maxScore = 20;
23
+ private readonly LINE_THRESHOLD;
24
+ score(request: ChatRequest): DimensionScore;
25
+ }
26
+ /**
27
+ * Reasoning Depth Scorer
28
+ * Only triggers for clearly heavy analytical tasks (≥2 strong keywords).
29
+ * Single keyword matches no longer score to reduce cloud routing rate.
30
+ */
31
+ export declare class ReasoningDepthScorer implements RuleScorer {
32
+ readonly name = "reasoning_depth";
33
+ readonly maxScore = 15;
34
+ private readonly STRONG_KEYWORDS;
35
+ score(request: ChatRequest): DimensionScore;
36
+ }
37
+ /**
38
+ * Multi-turn Context Scorer
39
+ * Long conversation histories require the model to synthesize and track many facts.
40
+ * > 16 messages → full 20 points (relaxed to keep ~90% local).
41
+ */
42
+ export declare class MultiTurnContextScorer implements RuleScorer {
43
+ readonly name = "multi_turn_context";
44
+ readonly maxScore = 20;
45
+ private readonly TURN_THRESHOLD;
46
+ score(request: ChatRequest): DimensionScore;
47
+ }
48
+ /**
49
+ * Override Scorer
50
+ * Detects explicit @local or @cloud directives in the last user message.
51
+ * These force routing and override all other scores.
52
+ */
53
+ export declare class OverrideScorer implements RuleScorer {
54
+ readonly name = "override_directive";
55
+ readonly maxScore = 0;
56
+ score(request: ChatRequest): DimensionScore & {
57
+ forced?: 'local' | 'cloud';
58
+ };
59
+ }
60
+ export declare const DEFAULT_SCORERS: RuleScorer[];
61
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/router/rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAa3E;;;;;GAKG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IACjD,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,QAAQ,CAAC,QAAQ,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IAEvC,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CAoB5C;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,UAAU;IACrD,QAAQ,CAAC,IAAI,qBAAqB;IAClC,QAAQ,CAAC,QAAQ,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAM;IAErC,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CAuB5C;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,UAAU;IACrD,QAAQ,CAAC,IAAI,qBAAqB;IAClC,QAAQ,CAAC,QAAQ,MAAM;IAGvB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAQ9B;IAEF,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CAe5C;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,UAAU;IACvD,QAAQ,CAAC,IAAI,wBAAwB;IACrC,QAAQ,CAAC,QAAQ,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAM;IAErC,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CAc5C;AAED;;;;GAIG;AACH,qBAAa,cAAe,YAAW,UAAU;IAC/C,QAAQ,CAAC,IAAI,wBAAwB;IACrC,QAAQ,CAAC,QAAQ,KAAK;IAEtB,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc,GAAG;QAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;KAAE;CAoC7E;AAGD,eAAO,MAAM,eAAe,EAAE,UAAU,EAMvC,CAAC"}
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_SCORERS = exports.OverrideScorer = exports.MultiTurnContextScorer = exports.ReasoningDepthScorer = exports.CodeComplexityScorer = exports.TokenCountScorer = void 0;
4
+ // Estimates token count from a string using a simple heuristic (~4 chars per token)
5
+ function estimateTokens(text) {
6
+ return Math.ceil(text.length / 4);
7
+ }
8
+ function extractAllText(request) {
9
+ return request.messages
10
+ .map((m) => (typeof m.content === 'string' ? m.content : ''))
11
+ .join('\n');
12
+ }
13
+ /**
14
+ * Token Count Scorer
15
+ * Long inputs are harder for small local models (limited context window).
16
+ * > 4000 estimated tokens → full 30 points (relaxed to keep ~90% local)
17
+ * Scales linearly from 0 at 1500 tokens to 30 at 4000 tokens.
18
+ */
19
+ class TokenCountScorer {
20
+ name = 'token_count';
21
+ maxScore = 30;
22
+ LOW_THRESHOLD = 1500;
23
+ HIGH_THRESHOLD = 4000;
24
+ score(request) {
25
+ const text = extractAllText(request);
26
+ const tokens = estimateTokens(text);
27
+ let points = 0;
28
+ let reason = `~${tokens} estimated tokens`;
29
+ if (tokens >= this.HIGH_THRESHOLD) {
30
+ points = this.maxScore;
31
+ reason += ` (≥${this.HIGH_THRESHOLD} → cloud favored)`;
32
+ }
33
+ else if (tokens > this.LOW_THRESHOLD) {
34
+ points = Math.round(((tokens - this.LOW_THRESHOLD) / (this.HIGH_THRESHOLD - this.LOW_THRESHOLD)) *
35
+ this.maxScore);
36
+ reason += ` (scaling ${this.LOW_THRESHOLD}-${this.HIGH_THRESHOLD})`;
37
+ }
38
+ return { name: this.name, score: points, maxScore: this.maxScore, reason };
39
+ }
40
+ }
41
+ exports.TokenCountScorer = TokenCountScorer;
42
+ /**
43
+ * Code Complexity Scorer
44
+ * Large code blocks require strong reasoning capabilities.
45
+ * Code fences with > 80 lines → full 20 points (relaxed to keep ~90% local).
46
+ */
47
+ class CodeComplexityScorer {
48
+ name = 'code_complexity';
49
+ maxScore = 20;
50
+ LINE_THRESHOLD = 80;
51
+ score(request) {
52
+ const text = extractAllText(request);
53
+ const codeBlockRegex = /```[\s\S]*?```/g;
54
+ const blocks = text.match(codeBlockRegex) ?? [];
55
+ let maxBlockLines = 0;
56
+ for (const block of blocks) {
57
+ const lines = block.split('\n').length;
58
+ if (lines > maxBlockLines)
59
+ maxBlockLines = lines;
60
+ }
61
+ let points = 0;
62
+ let reason = `${blocks.length} code block(s), max ${maxBlockLines} lines`;
63
+ if (maxBlockLines >= this.LINE_THRESHOLD) {
64
+ points = this.maxScore;
65
+ reason += ` (≥${this.LINE_THRESHOLD} lines → cloud favored)`;
66
+ }
67
+ else if (blocks.length > 0) {
68
+ points = Math.round((maxBlockLines / this.LINE_THRESHOLD) * this.maxScore);
69
+ }
70
+ return { name: this.name, score: points, maxScore: this.maxScore, reason };
71
+ }
72
+ }
73
+ exports.CodeComplexityScorer = CodeComplexityScorer;
74
+ /**
75
+ * Reasoning Depth Scorer
76
+ * Only triggers for clearly heavy analytical tasks (≥2 strong keywords).
77
+ * Single keyword matches no longer score to reduce cloud routing rate.
78
+ */
79
+ class ReasoningDepthScorer {
80
+ name = 'reasoning_depth';
81
+ maxScore = 15;
82
+ // Only "strong" multi-word or unambiguously complex phrases trigger scoring
83
+ STRONG_KEYWORDS = [
84
+ // English – multi-word / unambiguous
85
+ 'explain in detail', 'step by step', 'step-by-step',
86
+ 'reason through', 'pros and cons', 'trade-offs', 'trade offs',
87
+ 'critically evaluate', 'comprehensive analysis',
88
+ // Chinese – unambiguously heavy
89
+ '详细分析', '逐步分析', '全面对比', '深度分析', '系统性分析',
90
+ '优缺点对比', '深入推理',
91
+ ];
92
+ score(request) {
93
+ const text = extractAllText(request).toLowerCase();
94
+ const matched = this.STRONG_KEYWORDS.filter((kw) => text.includes(kw.toLowerCase()));
95
+ // Require ≥1 strong multi-word phrase to score
96
+ const points = matched.length >= 1 ? this.maxScore : 0;
97
+ const reason = matched.length >= 1
98
+ ? `强推理关键词: ${matched.slice(0, 3).join(', ')}`
99
+ : '无强推理关键词';
100
+ return { name: this.name, score: points, maxScore: this.maxScore, reason };
101
+ }
102
+ }
103
+ exports.ReasoningDepthScorer = ReasoningDepthScorer;
104
+ /**
105
+ * Multi-turn Context Scorer
106
+ * Long conversation histories require the model to synthesize and track many facts.
107
+ * > 16 messages → full 20 points (relaxed to keep ~90% local).
108
+ */
109
+ class MultiTurnContextScorer {
110
+ name = 'multi_turn_context';
111
+ maxScore = 20;
112
+ TURN_THRESHOLD = 16;
113
+ score(request) {
114
+ const turns = request.messages.length;
115
+ let points = 0;
116
+ let reason = `${turns} message(s) in context`;
117
+ if (turns >= this.TURN_THRESHOLD) {
118
+ points = this.maxScore;
119
+ reason += ` (≥${this.TURN_THRESHOLD} turns → cloud favored)`;
120
+ }
121
+ else if (turns > 1) {
122
+ points = Math.round((turns / this.TURN_THRESHOLD) * this.maxScore);
123
+ }
124
+ return { name: this.name, score: points, maxScore: this.maxScore, reason };
125
+ }
126
+ }
127
+ exports.MultiTurnContextScorer = MultiTurnContextScorer;
128
+ /**
129
+ * Override Scorer
130
+ * Detects explicit @local or @cloud directives in the last user message.
131
+ * These force routing and override all other scores.
132
+ */
133
+ class OverrideScorer {
134
+ name = 'override_directive';
135
+ maxScore = 0; // Does not contribute to score; uses forced field
136
+ score(request) {
137
+ const lastUserMessage = [...request.messages]
138
+ .reverse()
139
+ .find((m) => m.role === 'user');
140
+ const content = typeof lastUserMessage?.content === 'string'
141
+ ? lastUserMessage.content
142
+ : '';
143
+ if (/@local\b/i.test(content)) {
144
+ return {
145
+ name: this.name,
146
+ score: 0,
147
+ maxScore: this.maxScore,
148
+ reason: '@local directive detected — forcing local routing',
149
+ forced: 'local',
150
+ };
151
+ }
152
+ if (/@cloud\b/i.test(content)) {
153
+ return {
154
+ name: this.name,
155
+ score: 0,
156
+ maxScore: this.maxScore,
157
+ reason: '@cloud directive detected — forcing cloud routing',
158
+ forced: 'cloud',
159
+ };
160
+ }
161
+ return {
162
+ name: this.name,
163
+ score: 0,
164
+ maxScore: this.maxScore,
165
+ reason: 'No override directive',
166
+ };
167
+ }
168
+ }
169
+ exports.OverrideScorer = OverrideScorer;
170
+ // Default set of rule scorers (ordered by processing speed, cheapest first)
171
+ exports.DEFAULT_SCORERS = [
172
+ new OverrideScorer(),
173
+ new MultiTurnContextScorer(),
174
+ new TokenCountScorer(),
175
+ new CodeComplexityScorer(),
176
+ new ReasoningDepthScorer(),
177
+ ];
178
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/router/rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { ChatRequest, ScoringResult, RuleScorer } from '../types.js';
2
+ export declare class Scorer {
3
+ private readonly scorers;
4
+ constructor(scorers?: RuleScorer[]);
5
+ score(request: ChatRequest): ScoringResult;
6
+ }
7
+ //# sourceMappingURL=scorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scorer.d.ts","sourceRoot":"","sources":["../../src/router/scorer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG1E,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;gBAE3B,OAAO,GAAE,UAAU,EAAoB;IAInD,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa;CA2B3C"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scorer = void 0;
4
+ const rules_js_1 = require("./rules.js");
5
+ class Scorer {
6
+ scorers;
7
+ constructor(scorers = rules_js_1.DEFAULT_SCORERS) {
8
+ this.scorers = scorers;
9
+ }
10
+ score(request) {
11
+ let totalScore = 0;
12
+ const dimensionScores = [];
13
+ let forced;
14
+ for (const scorer of this.scorers) {
15
+ // OverrideScorer may return a `forced` field via duck-typing
16
+ const result = scorer.score(request);
17
+ dimensionScores.push({
18
+ name: result.name,
19
+ score: result.score,
20
+ maxScore: result.maxScore,
21
+ reason: result.reason,
22
+ });
23
+ totalScore += result.score;
24
+ if (result.forced) {
25
+ forced = result.forced;
26
+ // Short-circuit: override directives take immediate effect
27
+ break;
28
+ }
29
+ }
30
+ return { totalScore, dimensionScores, forced };
31
+ }
32
+ }
33
+ exports.Scorer = Scorer;
34
+ //# sourceMappingURL=scorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scorer.js","sourceRoot":"","sources":["../../src/router/scorer.ts"],"names":[],"mappings":";;;AACA,yCAA6D;AAE7D,MAAa,MAAM;IACA,OAAO,CAAe;IAEvC,YAAY,UAAwB,0BAAe;QACjD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,OAAoB;QACxB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,MAAM,eAAe,GAAG,EAAE,CAAC;QAC3B,IAAI,MAAqC,CAAC;QAE1C,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,6DAA6D;YAC7D,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAwC,CAAC;YAE5E,eAAe,CAAC,IAAI,CAAC;gBACnB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAC,CAAC;YAEH,UAAU,IAAI,MAAM,CAAC,KAAK,CAAC;YAE3B,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;gBACvB,2DAA2D;gBAC3D,MAAM;YACR,CAAC;QACH,CAAC;QAED,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC;IACjD,CAAC;CACF;AAlCD,wBAkCC"}
@@ -0,0 +1,28 @@
1
+ export interface OllamaModel {
2
+ name: string;
3
+ size?: string;
4
+ modifiedAt?: string;
5
+ }
6
+ export interface OllamaStatus {
7
+ binaryFound: boolean;
8
+ serviceRunning: boolean;
9
+ models: OllamaModel[];
10
+ latencyMs?: number;
11
+ error?: string;
12
+ }
13
+ export interface AipingStatus {
14
+ reachable: boolean;
15
+ keyValid: boolean;
16
+ model: string;
17
+ latencyMs?: number;
18
+ error?: string;
19
+ errorCode?: number;
20
+ }
21
+ export declare function detectOllama(baseUrl?: string): Promise<OllamaStatus>;
22
+ export declare function detectAiping(apiKey: string, model?: string): Promise<AipingStatus>;
23
+ export declare const RECOMMENDED_MODELS: Array<{
24
+ name: string;
25
+ size: string;
26
+ desc: string;
27
+ }>;
28
+ //# sourceMappingURL=detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/setup/detector.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,wBAAsB,YAAY,CAAC,OAAO,SAA2B,GAAG,OAAO,CAAC,YAAY,CAAC,CA4C5F;AA2CD,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA2E5F;AAMD,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAMlF,CAAC"}