@agentplugged/claw 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.
Files changed (149) hide show
  1. package/dist/cli.d.ts +11 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +111 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config.d.ts +29 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +94 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/index.d.ts +17 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +54 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/memory/capture.d.ts +13 -0
  14. package/dist/memory/capture.d.ts.map +1 -0
  15. package/dist/memory/capture.js +102 -0
  16. package/dist/memory/capture.js.map +1 -0
  17. package/dist/memory/db.d.ts +56 -0
  18. package/dist/memory/db.d.ts.map +1 -0
  19. package/dist/memory/db.js +206 -0
  20. package/dist/memory/db.js.map +1 -0
  21. package/dist/memory/index.d.ts +8 -0
  22. package/dist/memory/index.d.ts.map +1 -0
  23. package/dist/memory/index.js +42 -0
  24. package/dist/memory/index.js.map +1 -0
  25. package/dist/memory/llm-extract.d.ts +13 -0
  26. package/dist/memory/llm-extract.d.ts.map +1 -0
  27. package/dist/memory/llm-extract.js +135 -0
  28. package/dist/memory/llm-extract.js.map +1 -0
  29. package/dist/memory/recall.d.ts +16 -0
  30. package/dist/memory/recall.d.ts.map +1 -0
  31. package/dist/memory/recall.js +131 -0
  32. package/dist/memory/recall.js.map +1 -0
  33. package/dist/observability/collector.d.ts +40 -0
  34. package/dist/observability/collector.d.ts.map +1 -0
  35. package/dist/observability/collector.js +119 -0
  36. package/dist/observability/collector.js.map +1 -0
  37. package/dist/observability/db.d.ts +60 -0
  38. package/dist/observability/db.d.ts.map +1 -0
  39. package/dist/observability/db.js +189 -0
  40. package/dist/observability/db.js.map +1 -0
  41. package/dist/observability/index.d.ts +7 -0
  42. package/dist/observability/index.d.ts.map +1 -0
  43. package/dist/observability/index.js +35 -0
  44. package/dist/observability/index.js.map +1 -0
  45. package/dist/observability/queries.d.ts +54 -0
  46. package/dist/observability/queries.d.ts.map +1 -0
  47. package/dist/observability/queries.js +181 -0
  48. package/dist/observability/queries.js.map +1 -0
  49. package/dist/router/classifier.d.ts +8 -0
  50. package/dist/router/classifier.d.ts.map +1 -0
  51. package/dist/router/classifier.js +90 -0
  52. package/dist/router/classifier.js.map +1 -0
  53. package/dist/router/fallback.d.ts +18 -0
  54. package/dist/router/fallback.d.ts.map +1 -0
  55. package/dist/router/fallback.js +102 -0
  56. package/dist/router/fallback.js.map +1 -0
  57. package/dist/router/index.d.ts +5 -0
  58. package/dist/router/index.d.ts.map +1 -0
  59. package/dist/router/index.js +214 -0
  60. package/dist/router/index.js.map +1 -0
  61. package/dist/router/leak-detector.d.ts +33 -0
  62. package/dist/router/leak-detector.d.ts.map +1 -0
  63. package/dist/router/leak-detector.js +320 -0
  64. package/dist/router/leak-detector.js.map +1 -0
  65. package/dist/router/logger.d.ts +18 -0
  66. package/dist/router/logger.d.ts.map +1 -0
  67. package/dist/router/logger.js +130 -0
  68. package/dist/router/logger.js.map +1 -0
  69. package/dist/router/models.d.ts +5 -0
  70. package/dist/router/models.d.ts.map +1 -0
  71. package/dist/router/models.js +96 -0
  72. package/dist/router/models.js.map +1 -0
  73. package/dist/router/providers.d.ts +4 -0
  74. package/dist/router/providers.d.ts.map +1 -0
  75. package/dist/router/providers.js +323 -0
  76. package/dist/router/providers.js.map +1 -0
  77. package/dist/router/strategy.d.ts +29 -0
  78. package/dist/router/strategy.d.ts.map +1 -0
  79. package/dist/router/strategy.js +169 -0
  80. package/dist/router/strategy.js.map +1 -0
  81. package/dist/router/types.d.ts +63 -0
  82. package/dist/router/types.d.ts.map +1 -0
  83. package/dist/router/types.js +3 -0
  84. package/dist/router/types.js.map +1 -0
  85. package/dist/sidecar/auth.d.ts +7 -0
  86. package/dist/sidecar/auth.d.ts.map +1 -0
  87. package/dist/sidecar/auth.js +36 -0
  88. package/dist/sidecar/auth.js.map +1 -0
  89. package/dist/sidecar/index.d.ts +2 -0
  90. package/dist/sidecar/index.d.ts.map +1 -0
  91. package/dist/sidecar/index.js +336 -0
  92. package/dist/sidecar/index.js.map +1 -0
  93. package/dist/sidecar/routes/addons.d.ts +6 -0
  94. package/dist/sidecar/routes/addons.d.ts.map +1 -0
  95. package/dist/sidecar/routes/addons.js +332 -0
  96. package/dist/sidecar/routes/addons.js.map +1 -0
  97. package/dist/sidecar/routes/backup.d.ts +7 -0
  98. package/dist/sidecar/routes/backup.d.ts.map +1 -0
  99. package/dist/sidecar/routes/backup.js +204 -0
  100. package/dist/sidecar/routes/backup.js.map +1 -0
  101. package/dist/sidecar/routes/channels.d.ts +4 -0
  102. package/dist/sidecar/routes/channels.d.ts.map +1 -0
  103. package/dist/sidecar/routes/channels.js +120 -0
  104. package/dist/sidecar/routes/channels.js.map +1 -0
  105. package/dist/sidecar/routes/health.d.ts +5 -0
  106. package/dist/sidecar/routes/health.d.ts.map +1 -0
  107. package/dist/sidecar/routes/health.js +28 -0
  108. package/dist/sidecar/routes/health.js.map +1 -0
  109. package/dist/sidecar/routes/memory.d.ts +7 -0
  110. package/dist/sidecar/routes/memory.d.ts.map +1 -0
  111. package/dist/sidecar/routes/memory.js +234 -0
  112. package/dist/sidecar/routes/memory.js.map +1 -0
  113. package/dist/sidecar/routes/metrics.d.ts +5 -0
  114. package/dist/sidecar/routes/metrics.d.ts.map +1 -0
  115. package/dist/sidecar/routes/metrics.js +273 -0
  116. package/dist/sidecar/routes/metrics.js.map +1 -0
  117. package/dist/sidecar/routes/restart.d.ts +4 -0
  118. package/dist/sidecar/routes/restart.d.ts.map +1 -0
  119. package/dist/sidecar/routes/restart.js +81 -0
  120. package/dist/sidecar/routes/restart.js.map +1 -0
  121. package/dist/sidecar/routes/router-config.d.ts +5 -0
  122. package/dist/sidecar/routes/router-config.d.ts.map +1 -0
  123. package/dist/sidecar/routes/router-config.js +150 -0
  124. package/dist/sidecar/routes/router-config.js.map +1 -0
  125. package/dist/sidecar/routes/security.d.ts +8 -0
  126. package/dist/sidecar/routes/security.d.ts.map +1 -0
  127. package/dist/sidecar/routes/security.js +308 -0
  128. package/dist/sidecar/routes/security.js.map +1 -0
  129. package/dist/sidecar/routes/skills.d.ts +5 -0
  130. package/dist/sidecar/routes/skills.d.ts.map +1 -0
  131. package/dist/sidecar/routes/skills.js +146 -0
  132. package/dist/sidecar/routes/skills.js.map +1 -0
  133. package/dist/sidecar/routes/soul.d.ts +4 -0
  134. package/dist/sidecar/routes/soul.d.ts.map +1 -0
  135. package/dist/sidecar/routes/soul.js +115 -0
  136. package/dist/sidecar/routes/soul.js.map +1 -0
  137. package/dist/sidecar/routes/team.d.ts +4 -0
  138. package/dist/sidecar/routes/team.d.ts.map +1 -0
  139. package/dist/sidecar/routes/team.js +139 -0
  140. package/dist/sidecar/routes/team.js.map +1 -0
  141. package/dist/sidecar/routes/update.d.ts +4 -0
  142. package/dist/sidecar/routes/update.d.ts.map +1 -0
  143. package/dist/sidecar/routes/update.js +96 -0
  144. package/dist/sidecar/routes/update.js.map +1 -0
  145. package/dist/sidecar/utils.d.ts +9 -0
  146. package/dist/sidecar/utils.d.ts.map +1 -0
  147. package/dist/sidecar/utils.js +57 -0
  148. package/dist/sidecar/utils.js.map +1 -0
  149. package/package.json +45 -0
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTodayStats = getTodayStats;
4
+ exports.getWeeklyStats = getWeeklyStats;
5
+ exports.getMonthlyStats = getMonthlyStats;
6
+ exports.getActiveSessions = getActiveSessions;
7
+ exports.getRecentSessions = getRecentSessions;
8
+ exports.getSessionEvents = getSessionEvents;
9
+ exports.getTopSkillsByCost = getTopSkillsByCost;
10
+ exports.getModelDistribution = getModelDistribution;
11
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
12
+ function emptyDayStats(date) {
13
+ return {
14
+ date,
15
+ messages: 0,
16
+ tokens_in: 0,
17
+ tokens_out: 0,
18
+ cost: 0,
19
+ tool_calls: 0,
20
+ sessions: 0,
21
+ errors: 0,
22
+ };
23
+ }
24
+ /**
25
+ * Converts flat daily_stats rows into typed DayStats objects.
26
+ */
27
+ function rowsToDayStats(rows) {
28
+ const byDate = new Map();
29
+ const NUMERIC_METRICS = new Set([
30
+ "messages",
31
+ "tokens_in",
32
+ "tokens_out",
33
+ "cost",
34
+ "tool_calls",
35
+ "sessions",
36
+ "errors",
37
+ ]);
38
+ for (const row of rows) {
39
+ if (!byDate.has(row.date)) {
40
+ byDate.set(row.date, emptyDayStats(row.date));
41
+ }
42
+ const entry = byDate.get(row.date);
43
+ if (NUMERIC_METRICS.has(row.metric)) {
44
+ const metric = row.metric;
45
+ entry[metric] += row.value;
46
+ }
47
+ }
48
+ return byDate;
49
+ }
50
+ function todayString() {
51
+ return new Date().toISOString().slice(0, 10);
52
+ }
53
+ // ─── Queries ──────────────────────────────────────────────────────────────────
54
+ /**
55
+ * Returns aggregated stats for today.
56
+ */
57
+ function getTodayStats(db) {
58
+ const today = todayString();
59
+ const rows = db
60
+ .prepare("SELECT date, metric, value FROM daily_stats WHERE date = ?")
61
+ .all(today);
62
+ const map = rowsToDayStats(rows);
63
+ return map.get(today) ?? emptyDayStats(today);
64
+ }
65
+ /**
66
+ * Returns aggregated stats for each of the last 7 days.
67
+ */
68
+ function getWeeklyStats(db) {
69
+ const rows = db
70
+ .prepare(`SELECT date, metric, value FROM daily_stats
71
+ WHERE date >= date('now', '-6 days')
72
+ ORDER BY date ASC`)
73
+ .all();
74
+ const map = rowsToDayStats(rows);
75
+ // Ensure all 7 days are present (fill gaps with zeros)
76
+ const result = [];
77
+ for (let i = 6; i >= 0; i--) {
78
+ const d = new Date();
79
+ d.setDate(d.getDate() - i);
80
+ const dateStr = d.toISOString().slice(0, 10);
81
+ result.push(map.get(dateStr) ?? emptyDayStats(dateStr));
82
+ }
83
+ return result;
84
+ }
85
+ /**
86
+ * Returns aggregated stats for each of the last 30 days.
87
+ */
88
+ function getMonthlyStats(db) {
89
+ const rows = db
90
+ .prepare(`SELECT date, metric, value FROM daily_stats
91
+ WHERE date >= date('now', '-29 days')
92
+ ORDER BY date ASC`)
93
+ .all();
94
+ const map = rowsToDayStats(rows);
95
+ const result = [];
96
+ for (let i = 29; i >= 0; i--) {
97
+ const d = new Date();
98
+ d.setDate(d.getDate() - i);
99
+ const dateStr = d.toISOString().slice(0, 10);
100
+ result.push(map.get(dateStr) ?? emptyDayStats(dateStr));
101
+ }
102
+ return result;
103
+ }
104
+ /**
105
+ * Returns sessions currently in 'active' status.
106
+ */
107
+ function getActiveSessions(db) {
108
+ return db
109
+ .prepare(`SELECT * FROM sessions WHERE status = 'active' ORDER BY started_at DESC`)
110
+ .all();
111
+ }
112
+ /**
113
+ * Returns the most recent sessions (default: 20).
114
+ */
115
+ function getRecentSessions(db, limit = 20) {
116
+ return db
117
+ .prepare(`SELECT * FROM sessions ORDER BY started_at DESC LIMIT ?`)
118
+ .all(limit);
119
+ }
120
+ /**
121
+ * Returns all events for a given session, ordered chronologically.
122
+ */
123
+ function getSessionEvents(db, sessionId) {
124
+ return db
125
+ .prepare(`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`)
126
+ .all(sessionId);
127
+ }
128
+ /**
129
+ * Returns the top skills ranked by cost over the last N days.
130
+ * Skill name is extracted from the tool_call event's data.tool field.
131
+ */
132
+ function getTopSkillsByCost(db, days = 30) {
133
+ // We join events (tool_calls) with their parent session cost.
134
+ // For simplicity in V1 we use the tool name from events and
135
+ // the session total_cost proportionally — a direct approach is cleaner:
136
+ // extract cost from llm_call events that follow tool_call events.
137
+ // Here we aggregate llm_call events grouped by tool calls within same session.
138
+ const cutoff = new Date();
139
+ cutoff.setDate(cutoff.getDate() - days);
140
+ const cutoffStr = cutoff.toISOString().slice(0, 19);
141
+ const rows = db
142
+ .prepare(`SELECT
143
+ json_extract(data, '$.tool') AS tool_name,
144
+ COUNT(*) AS calls
145
+ FROM events
146
+ WHERE type = 'tool_call'
147
+ AND timestamp >= ?
148
+ AND json_extract(data, '$.tool') IS NOT NULL
149
+ GROUP BY tool_name
150
+ ORDER BY calls DESC
151
+ LIMIT 20`)
152
+ .all(cutoffStr);
153
+ // For V1 cost is estimated as 0 since we don't track per-skill cost precisely.
154
+ // This will be improved in V2 with skill attribution.
155
+ return rows.map((r) => ({
156
+ skill: r.tool_name,
157
+ cost: 0,
158
+ calls: r.calls,
159
+ }));
160
+ }
161
+ /**
162
+ * Returns model distribution (call count and total cost) over the last N days.
163
+ */
164
+ function getModelDistribution(db, days = 30) {
165
+ const cutoff = new Date();
166
+ cutoff.setDate(cutoff.getDate() - days);
167
+ const cutoffStr = cutoff.toISOString().slice(0, 19);
168
+ return db
169
+ .prepare(`SELECT
170
+ json_extract(data, '$.model') AS model,
171
+ COUNT(*) AS count,
172
+ SUM(CAST(json_extract(data, '$.cost') AS REAL)) AS cost
173
+ FROM events
174
+ WHERE type = 'llm_call'
175
+ AND timestamp >= ?
176
+ AND json_extract(data, '$.model') IS NOT NULL
177
+ GROUP BY model
178
+ ORDER BY count DESC`)
179
+ .all(cutoffStr);
180
+ }
181
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/observability/queries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { ChatMessage } from "./types";
2
+ export type Complexity = "low" | "medium" | "high";
3
+ /**
4
+ * Classifies request complexity in <5ms using pure heuristics.
5
+ * No LLM call is made — this runs synchronously.
6
+ */
7
+ export declare function classifyComplexity(messages: ChatMessage[]): Complexity;
8
+ //# sourceMappingURL=classifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.d.ts","sourceRoot":"","sources":["../../src/router/classifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAoDnD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,UAAU,CA0CtE"}
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.classifyComplexity = classifyComplexity;
4
+ const HIGH_COMPLEXITY_KEYWORDS = [
5
+ "analyze",
6
+ "analyse",
7
+ "compare",
8
+ "summarize long",
9
+ "summarise long",
10
+ "write essay",
11
+ "detailed report",
12
+ "comprehensive",
13
+ "in-depth",
14
+ "thorough",
15
+ "critique",
16
+ "evaluate",
17
+ ];
18
+ const MEDIUM_COMPLEXITY_KEYWORDS = [
19
+ "explain",
20
+ "describe",
21
+ "summarize",
22
+ "summarise",
23
+ "list",
24
+ "outline",
25
+ "translate",
26
+ ];
27
+ function totalContentLength(messages) {
28
+ return messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0);
29
+ }
30
+ function hasToolCalls(messages) {
31
+ return messages.some((msg) => msg.tool_calls && msg.tool_calls.length > 0);
32
+ }
33
+ function systemPromptLength(messages) {
34
+ const sys = messages.find((m) => m.role === "system");
35
+ return sys?.content?.length ?? 0;
36
+ }
37
+ function matchesKeywords(text, keywords) {
38
+ const lower = text.toLowerCase();
39
+ return keywords.some((kw) => lower.includes(kw));
40
+ }
41
+ function lastUserMessage(messages) {
42
+ const userMessages = messages.filter((m) => m.role === "user");
43
+ return userMessages[userMessages.length - 1]?.content ?? "";
44
+ }
45
+ /**
46
+ * Classifies request complexity in <5ms using pure heuristics.
47
+ * No LLM call is made — this runs synchronously.
48
+ */
49
+ function classifyComplexity(messages) {
50
+ let score = 0;
51
+ const total = totalContentLength(messages);
52
+ const conversationLength = messages.length;
53
+ const sysLen = systemPromptLength(messages);
54
+ const lastUser = lastUserMessage(messages);
55
+ // Content length scoring
56
+ if (total > 1000) {
57
+ score += 2;
58
+ }
59
+ else if (total > 200) {
60
+ score += 1;
61
+ }
62
+ // Tool calls bump complexity
63
+ if (hasToolCalls(messages)) {
64
+ score += 1;
65
+ }
66
+ // System prompt length factor
67
+ if (sysLen > 500) {
68
+ score += 1;
69
+ }
70
+ // Conversation depth factor
71
+ if (conversationLength > 10) {
72
+ score += 1;
73
+ }
74
+ else if (conversationLength > 5) {
75
+ score += 0;
76
+ }
77
+ // High complexity keywords in last user message
78
+ if (matchesKeywords(lastUser, HIGH_COMPLEXITY_KEYWORDS)) {
79
+ score += 2;
80
+ }
81
+ else if (matchesKeywords(lastUser, MEDIUM_COMPLEXITY_KEYWORDS)) {
82
+ score += 1;
83
+ }
84
+ if (score >= 3)
85
+ return "high";
86
+ if (score >= 1)
87
+ return "medium";
88
+ return "low";
89
+ }
90
+ //# sourceMappingURL=classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.js","sourceRoot":"","sources":["../../src/router/classifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { ModelConfig, ProviderConfig } from "../config";
2
+ import { ChatCompletionRequest, ChatCompletionResponse } from "./types";
3
+ interface ProviderAndModel {
4
+ provider: ProviderConfig;
5
+ model: ModelConfig;
6
+ }
7
+ /**
8
+ * Forwards a request to the primary provider/model with automatic fallback
9
+ * to equivalent-tier models on other providers if the primary fails.
10
+ *
11
+ * Returns the response and a flag indicating whether a fallback was used.
12
+ */
13
+ export declare function forwardWithFallback(request: ChatCompletionRequest, primary: ProviderAndModel, providers: ProviderConfig[]): Promise<{
14
+ response: ChatCompletionResponse;
15
+ usedFallback: boolean;
16
+ }>;
17
+ export {};
18
+ //# sourceMappingURL=fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../../src/router/fallback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGxD,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAKxE,UAAU,gBAAgB;IACxB,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC;CACpB;AA+ED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,gBAAgB,EACzB,SAAS,EAAE,cAAc,EAAE,GAC1B,OAAO,CAAC;IAAE,QAAQ,EAAE,sBAAsB,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAAC,CA8CtE"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.forwardWithFallback = forwardWithFallback;
4
+ const models_1 = require("./models");
5
+ const providers_1 = require("./providers");
6
+ const RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504]);
7
+ const REQUEST_TIMEOUT_MS = 30_000;
8
+ /**
9
+ * Determines if the error message suggests a retryable condition
10
+ * (rate limit, timeout, 5xx server error).
11
+ */
12
+ function isRetryableError(err) {
13
+ if (!(err instanceof Error))
14
+ return false;
15
+ const msg = err.message.toLowerCase();
16
+ // Check for status code patterns embedded in error messages
17
+ for (const code of RETRYABLE_STATUS_CODES) {
18
+ if (msg.includes(`${code}`))
19
+ return true;
20
+ }
21
+ return (msg.includes("timeout") ||
22
+ msg.includes("rate limit") ||
23
+ msg.includes("overloaded") ||
24
+ msg.includes("econnreset") ||
25
+ msg.includes("econnrefused") ||
26
+ msg.includes("network"));
27
+ }
28
+ /**
29
+ * Wraps a fetch call with a timeout.
30
+ */
31
+ async function withTimeout(promise, ms) {
32
+ let timeoutId;
33
+ const timeout = new Promise((_, reject) => {
34
+ timeoutId = setTimeout(() => {
35
+ reject(new Error(`Request timed out after ${ms}ms`));
36
+ }, ms);
37
+ });
38
+ try {
39
+ const result = await Promise.race([promise, timeout]);
40
+ clearTimeout(timeoutId);
41
+ return result;
42
+ }
43
+ catch (err) {
44
+ clearTimeout(timeoutId);
45
+ throw err;
46
+ }
47
+ }
48
+ /**
49
+ * Builds a list of fallback candidates with the same tier as the primary model,
50
+ * excluding the primary provider itself.
51
+ */
52
+ function buildFallbackCandidates(primary, providers) {
53
+ const candidates = [];
54
+ const targetTier = primary.model.tier;
55
+ for (const provider of providers) {
56
+ if (!provider.enabled)
57
+ continue;
58
+ if (provider.name === primary.provider.name)
59
+ continue;
60
+ const registryModels = models_1.MODELS_BY_PROVIDER.get(provider.name) ?? [];
61
+ const availableIds = new Set(provider.models.map((m) => m.id));
62
+ const eligible = registryModels.filter((m) => availableIds.has(m.id) && m.tier === targetTier);
63
+ if (eligible.length > 0) {
64
+ candidates.push({ provider, model: eligible[0] });
65
+ }
66
+ }
67
+ return candidates;
68
+ }
69
+ /**
70
+ * Forwards a request to the primary provider/model with automatic fallback
71
+ * to equivalent-tier models on other providers if the primary fails.
72
+ *
73
+ * Returns the response and a flag indicating whether a fallback was used.
74
+ */
75
+ async function forwardWithFallback(request, primary, providers) {
76
+ // Attempt primary
77
+ try {
78
+ const response = await withTimeout((0, providers_1.forwardToProvider)(primary.provider, primary.model, request), REQUEST_TIMEOUT_MS);
79
+ return { response, usedFallback: false };
80
+ }
81
+ catch (primaryErr) {
82
+ if (!isRetryableError(primaryErr)) {
83
+ // Non-retryable error — surface immediately
84
+ throw primaryErr;
85
+ }
86
+ console.warn(`[router/fallback] Primary provider "${primary.provider.name}/${primary.model.id}" failed: ${primaryErr.message}. Attempting fallbacks.`);
87
+ }
88
+ // Attempt fallbacks in order
89
+ const fallbacks = buildFallbackCandidates(primary, providers);
90
+ for (const candidate of fallbacks) {
91
+ try {
92
+ console.info(`[router/fallback] Trying ${candidate.provider.name}/${candidate.model.id}`);
93
+ const response = await withTimeout((0, providers_1.forwardToProvider)(candidate.provider, candidate.model, request), REQUEST_TIMEOUT_MS);
94
+ return { response, usedFallback: true };
95
+ }
96
+ catch (fallbackErr) {
97
+ console.warn(`[router/fallback] Fallback "${candidate.provider.name}/${candidate.model.id}" also failed: ${fallbackErr.message}`);
98
+ }
99
+ }
100
+ throw new Error(`All providers failed for tier "${primary.model.tier}". No fallback succeeded.`);
101
+ }
102
+ //# sourceMappingURL=fallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback.js","sourceRoot":"","sources":["../../src/router/fallback.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import * as http from "http";
2
+ import Database from "better-sqlite3";
3
+ import { ClawConfig } from "../config";
4
+ export declare function createRouterServer(config: ClawConfig, db: Database.Database): http.Server;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAc,UAAU,EAAE,MAAM,WAAW,CAAC;AAwKnD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,EAAE,EAAE,QAAQ,CAAC,QAAQ,GACpB,IAAI,CAAC,MAAM,CA6Bb"}
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createRouterServer = createRouterServer;
37
+ const http = __importStar(require("http"));
38
+ const config_1 = require("../config");
39
+ const classifier_1 = require("./classifier");
40
+ const strategy_1 = require("./strategy");
41
+ const fallback_1 = require("./fallback");
42
+ const logger_1 = require("./logger");
43
+ const leak_detector_1 = require("./leak-detector");
44
+ // ---------------------------------------------------------------------------
45
+ // Helpers
46
+ // ---------------------------------------------------------------------------
47
+ function readBody(req) {
48
+ return new Promise((resolve, reject) => {
49
+ const chunks = [];
50
+ req.on("data", (chunk) => chunks.push(chunk));
51
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
52
+ req.on("error", reject);
53
+ });
54
+ }
55
+ function sendJson(res, status, body) {
56
+ const json = JSON.stringify(body);
57
+ res.writeHead(status, {
58
+ "Content-Type": "application/json",
59
+ "Content-Length": Buffer.byteLength(json),
60
+ });
61
+ res.end(json);
62
+ }
63
+ function sendError(res, status, message) {
64
+ sendJson(res, status, {
65
+ error: { message, type: "router_error", code: status },
66
+ });
67
+ }
68
+ // ---------------------------------------------------------------------------
69
+ // Request handler
70
+ // ---------------------------------------------------------------------------
71
+ async function handleChatCompletions(req, res, config, db) {
72
+ const startMs = Date.now();
73
+ let body;
74
+ try {
75
+ body = await readBody(req);
76
+ }
77
+ catch {
78
+ return sendError(res, 400, "Failed to read request body");
79
+ }
80
+ let request;
81
+ try {
82
+ request = JSON.parse(body);
83
+ }
84
+ catch {
85
+ return sendError(res, 400, "Invalid JSON body");
86
+ }
87
+ if (!request.messages || !Array.isArray(request.messages)) {
88
+ return sendError(res, 400, "Missing or invalid 'messages' field");
89
+ }
90
+ // Classify complexity (synchronous, <5ms)
91
+ const complexity = (0, classifier_1.classifyComplexity)(request.messages);
92
+ // Select the best model for this request
93
+ let selected;
94
+ try {
95
+ selected = (0, strategy_1.selectModel)(complexity, config.strategy, config.providers);
96
+ }
97
+ catch (err) {
98
+ console.error("[router] Model selection failed:", err);
99
+ return sendError(res, 503, err.message);
100
+ }
101
+ // Forward with fallback
102
+ let chatResponse;
103
+ try {
104
+ chatResponse = await (0, fallback_1.forwardWithFallback)(request, selected, config.providers);
105
+ }
106
+ catch (err) {
107
+ const latencyMs = Date.now() - startMs;
108
+ (0, logger_1.logRouterRequest)(db, {
109
+ timestamp: new Date().toISOString(),
110
+ model: selected.model.id,
111
+ provider: selected.provider.name,
112
+ inputTokens: 0,
113
+ outputTokens: 0,
114
+ cost: 0,
115
+ latencyMs,
116
+ complexity,
117
+ strategy: config.strategy,
118
+ fallback: false,
119
+ success: false,
120
+ error: err.message,
121
+ });
122
+ console.error("[router] All providers failed:", err);
123
+ return sendError(res, 502, err.message);
124
+ }
125
+ const latencyMs = Date.now() - startMs;
126
+ const { response, usedFallback } = chatResponse;
127
+ const inputTokens = response.usage?.prompt_tokens ?? 0;
128
+ const outputTokens = response.usage?.completion_tokens ?? 0;
129
+ const cost = (0, logger_1.calculateCost)(inputTokens, outputTokens, selected.model.inputPrice, selected.model.outputPrice);
130
+ // Leak detection — scan response content before sending to client
131
+ let leakDetected = false;
132
+ if (response.choices) {
133
+ for (const choice of response.choices) {
134
+ const content = choice.message?.content;
135
+ if (typeof content === "string") {
136
+ const result = (0, leak_detector_1.detectLeaks)(content);
137
+ if (result.leaked) {
138
+ leakDetected = true;
139
+ console.warn(`[router] Leak detected in response (model=${selected.model.id}): ${result.findings.length} finding(s)`, result.findings.map((f) => `${f.type}: ${f.match}`));
140
+ choice.message.content = (0, leak_detector_1.sanitizeResponse)(content);
141
+ }
142
+ }
143
+ }
144
+ }
145
+ // Persist log (synchronous — non-blocking on the happy path since it's fast)
146
+ (0, logger_1.logRouterRequest)(db, {
147
+ timestamp: new Date().toISOString(),
148
+ model: selected.model.id,
149
+ provider: selected.provider.name,
150
+ inputTokens,
151
+ outputTokens,
152
+ cost,
153
+ latencyMs,
154
+ complexity,
155
+ strategy: config.strategy,
156
+ fallback: usedFallback,
157
+ success: true,
158
+ leakDetected,
159
+ });
160
+ sendJson(res, 200, response);
161
+ }
162
+ // ---------------------------------------------------------------------------
163
+ // Server factory
164
+ // ---------------------------------------------------------------------------
165
+ function createRouterServer(config, db) {
166
+ const server = http.createServer(async (req, res) => {
167
+ const method = req.method?.toUpperCase();
168
+ const url = req.url ?? "/";
169
+ // Health check
170
+ if (method === "GET" && url === "/health") {
171
+ return sendJson(res, 200, {
172
+ status: "ok",
173
+ version: "0.1.0",
174
+ strategy: config.strategy,
175
+ providers: config.providers
176
+ .filter((p) => p.enabled)
177
+ .map((p) => p.name),
178
+ });
179
+ }
180
+ // Chat completions — OpenAI-compatible endpoint
181
+ if (method === "POST" && url === "/v1/chat/completions") {
182
+ return handleChatCompletions(req, res, config, db);
183
+ }
184
+ // 404 for everything else
185
+ return sendError(res, 404, `Route not found: ${method} ${url}`);
186
+ });
187
+ return server;
188
+ }
189
+ // ---------------------------------------------------------------------------
190
+ // Entry point (when run directly)
191
+ // ---------------------------------------------------------------------------
192
+ if (require.main === module) {
193
+ const config = (0, config_1.loadConfig)();
194
+ const db = (0, logger_1.openDatabase)(config.dataDir);
195
+ const server = createRouterServer(config, db);
196
+ server.listen(config.routerPort, () => {
197
+ console.log(`[router] Claw Router Proxy listening on port ${config.routerPort}`);
198
+ console.log(`[router] Strategy: ${config.strategy} | Providers: ${config.providers
199
+ .filter((p) => p.enabled)
200
+ .map((p) => p.name)
201
+ .join(", ") || "none"}`);
202
+ });
203
+ process.on("SIGTERM", () => {
204
+ console.log("[router] Shutting down...");
205
+ db.close();
206
+ server.close(() => process.exit(0));
207
+ });
208
+ process.on("SIGINT", () => {
209
+ console.log("[router] Interrupted, shutting down...");
210
+ db.close();
211
+ server.close(() => process.exit(0));
212
+ });
213
+ }
214
+ //# sourceMappingURL=index.js.map