@relayplane/proxy 1.9.18 → 1.9.21

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,268 @@
1
+ "use strict";
2
+ /**
3
+ * Policy Analyzer
4
+ *
5
+ * Reads routing-log.jsonl directly (not the in-memory buffer) and joins with
6
+ * agents.json to produce per-agent traffic summaries used by the suggestion engine
7
+ * and CLI display.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.MODEL_COST_PER_1M = void 0;
44
+ exports.estimateDailyCost = estimateDailyCost;
45
+ exports.inferAgentName = inferAgentName;
46
+ exports.analyzeTraffic = analyzeTraffic;
47
+ const fs = __importStar(require("node:fs"));
48
+ const routing_log_js_1 = require("./routing-log.js");
49
+ const agent_tracker_js_1 = require("./agent-tracker.js");
50
+ // ─── Model cost table ─────────────────────────────────────────────────────────
51
+ // Costs in USD per 1M tokens. Input/output separately.
52
+ // Update when provider pricing changes.
53
+ exports.MODEL_COST_PER_1M = {
54
+ 'anthropic/claude-opus-4-5': { input: 15.00, output: 75.00 },
55
+ 'anthropic/claude-opus-4': { input: 15.00, output: 75.00 },
56
+ 'anthropic/claude-sonnet-4-5': { input: 3.00, output: 15.00 },
57
+ 'anthropic/claude-sonnet-4': { input: 3.00, output: 15.00 },
58
+ 'anthropic/claude-haiku-4-5': { input: 0.80, output: 4.00 },
59
+ 'anthropic/claude-haiku-4': { input: 0.80, output: 4.00 },
60
+ 'openai/gpt-4o': { input: 2.50, output: 10.00 },
61
+ 'openai/gpt-4o-mini': { input: 0.15, output: 0.60 },
62
+ 'google/gemini-2.0-flash': { input: 0.10, output: 0.40 },
63
+ 'google/gemini-1.5-flash': { input: 0.075, output: 0.30 },
64
+ 'groq/llama-3.3-70b': { input: 0.59, output: 0.79 },
65
+ 'groq/llama-3.1-8b-instant': { input: 0.05, output: 0.08 },
66
+ 'openrouter/auto': { input: 1.00, output: 5.00 }, // rough estimate
67
+ };
68
+ function estimateDailyCost(avgInputTokens, avgOutputTokens, requestsPerDay, model) {
69
+ const costs = exports.MODEL_COST_PER_1M[model];
70
+ if (!costs)
71
+ return 0;
72
+ const perRequest = (avgInputTokens / 1_000_000) * costs.input +
73
+ (avgOutputTokens / 1_000_000) * costs.output;
74
+ return perRequest * requestsPerDay;
75
+ }
76
+ // ─── Name inference ───────────────────────────────────────────────────────────
77
+ function slugify(text) {
78
+ return text
79
+ .toLowerCase()
80
+ .replace(/\s+/g, '-')
81
+ .replace(/[^a-z0-9-]/g, '')
82
+ .slice(0, 24);
83
+ }
84
+ /**
85
+ * Infer a human-readable agent name from its system prompt and task distribution.
86
+ */
87
+ function inferAgentName(systemPromptPreview, taskDistribution) {
88
+ // Priority 1: "You are a/an [role]"
89
+ const youAreMatch = systemPromptPreview.match(/you are (?:a |an )?([A-Za-z][\w\s]{1,24})/i);
90
+ if (youAreMatch && youAreMatch[1]) {
91
+ return slugify(youAreMatch[1].trim());
92
+ }
93
+ // Priority 2: "Your job/role/task/purpose is to [verb]"
94
+ const jobMatch = systemPromptPreview.match(/your (?:job|role|task|purpose) is to (\w+)/i);
95
+ if (jobMatch && jobMatch[1]) {
96
+ return slugify(jobMatch[1].trim()) + '-agent';
97
+ }
98
+ // Priority 3: "As a/an [role] assistant/agent/bot"
99
+ const asAMatch = systemPromptPreview.match(/as (?:a |an )?(\w+(?:\s+\w+)?) (?:assistant|agent|bot)/i);
100
+ if (asAMatch && asAMatch[1]) {
101
+ return slugify(asAMatch[1].trim());
102
+ }
103
+ // Priority 4: fallback to dominant task
104
+ const dominant = Object.entries(taskDistribution).sort((a, b) => b[1] - a[1])[0];
105
+ if (dominant) {
106
+ return dominant[0] + '-agent';
107
+ }
108
+ return 'unknown-agent';
109
+ }
110
+ // ─── Main analysis function ───────────────────────────────────────────────────
111
+ /**
112
+ * Read routing-log.jsonl and agents.json to produce per-agent traffic summaries.
113
+ * Default lookbackDays = 7.
114
+ */
115
+ async function analyzeTraffic(opts) {
116
+ const lookbackDays = opts?.lookbackDays ?? 7;
117
+ const cutoff = Date.now() - lookbackDays * 86_400_000;
118
+ // 1. Read JSONL file
119
+ if (!fs.existsSync(routing_log_js_1.LOG_FILE)) {
120
+ return [];
121
+ }
122
+ let lines;
123
+ try {
124
+ const content = fs.readFileSync(routing_log_js_1.LOG_FILE, 'utf-8');
125
+ lines = content.split('\n').filter(l => l.trim().length > 0);
126
+ }
127
+ catch {
128
+ return [];
129
+ }
130
+ // Parse and filter entries within lookback window
131
+ const entries = [];
132
+ for (const line of lines) {
133
+ try {
134
+ const entry = JSON.parse(line);
135
+ const ts = Date.parse(entry.ts);
136
+ if (!isNaN(ts) && ts >= cutoff) {
137
+ entries.push(entry);
138
+ }
139
+ }
140
+ catch {
141
+ // Skip corrupt lines
142
+ }
143
+ }
144
+ if (entries.length === 0) {
145
+ return [];
146
+ }
147
+ // 2. Group by agentFingerprint (skip null fingerprints)
148
+ const groups = new Map();
149
+ for (const entry of entries) {
150
+ if (!entry.agentFingerprint)
151
+ continue;
152
+ const existing = groups.get(entry.agentFingerprint);
153
+ if (existing) {
154
+ existing.push(entry);
155
+ }
156
+ else {
157
+ groups.set(entry.agentFingerprint, [entry]);
158
+ }
159
+ }
160
+ if (groups.size === 0) {
161
+ return [];
162
+ }
163
+ // 3. Load agent registry
164
+ const registry = (0, agent_tracker_js_1.getAgentRegistry)();
165
+ // 4. Build analysis for each fingerprint group
166
+ const analyses = [];
167
+ for (const [fingerprint, groupEntries] of groups) {
168
+ const total = groupEntries.length;
169
+ // a. Task distribution
170
+ const taskCounts = {};
171
+ for (const e of groupEntries) {
172
+ taskCounts[e.taskType] = (taskCounts[e.taskType] ?? 0) + 1;
173
+ }
174
+ const taskDistribution = {};
175
+ for (const [task, count] of Object.entries(taskCounts)) {
176
+ taskDistribution[task] = count / total;
177
+ }
178
+ // b. Dominant task
179
+ const dominantTask = Object.entries(taskDistribution).sort((a, b) => b[1] - a[1])[0]?.[0] ?? 'unknown';
180
+ // c. Token averages
181
+ const entriesWithTokens = groupEntries.filter(e => e.inputTokens !== undefined || e.outputTokens !== undefined);
182
+ let avgInputTokens;
183
+ let avgOutputTokens;
184
+ let tokensAreEstimated;
185
+ if (entriesWithTokens.length > 0) {
186
+ avgInputTokens = entriesWithTokens.reduce((sum, e) => sum + (e.inputTokens ?? 0), 0) / entriesWithTokens.length;
187
+ avgOutputTokens = entriesWithTokens.reduce((sum, e) => sum + (e.outputTokens ?? 0), 0) / entriesWithTokens.length;
188
+ tokensAreEstimated = false;
189
+ }
190
+ else {
191
+ // Estimate from cost: assume 80/20 input/output split
192
+ // We'll estimate based on the model pricing
193
+ let totalEstimatedTokens = 0;
194
+ let validEntries = 0;
195
+ for (const e of groupEntries) {
196
+ const costs = exports.MODEL_COST_PER_1M[e.resolvedModel];
197
+ if (costs && costs.input > 0) {
198
+ // Rough estimate: use a small baseline if cost not available
199
+ const estimatedTokens = 2000; // baseline
200
+ totalEstimatedTokens += estimatedTokens;
201
+ validEntries++;
202
+ }
203
+ }
204
+ const avgTotal = validEntries > 0 ? totalEstimatedTokens / validEntries : 2000;
205
+ avgInputTokens = avgTotal * 0.8;
206
+ avgOutputTokens = avgTotal * 0.2;
207
+ tokensAreEstimated = true;
208
+ }
209
+ const avgTotalTokens = avgInputTokens + avgOutputTokens;
210
+ // d. Current model (most recent entry by ts)
211
+ const sortedByTs = [...groupEntries].sort((a, b) => Date.parse(b.ts) - Date.parse(a.ts));
212
+ const currentModel = sortedByTs[0]?.resolvedModel ?? 'unknown';
213
+ // e. Join with agents.json
214
+ const registryEntry = registry[fingerprint];
215
+ let daysObserved = 1;
216
+ let costPerDay = 0;
217
+ let requestsPerDay = total;
218
+ let systemPromptPreview = '';
219
+ let totalRequests = total;
220
+ let name;
221
+ let nameIsInferred;
222
+ if (registryEntry) {
223
+ const firstSeen = Date.parse(registryEntry.firstSeen);
224
+ const lastSeen = Date.parse(registryEntry.lastSeen);
225
+ daysObserved = Math.max(1, (lastSeen - firstSeen) / 86_400_000);
226
+ costPerDay = registryEntry.totalCost / daysObserved;
227
+ requestsPerDay = registryEntry.totalRequests / daysObserved;
228
+ systemPromptPreview = (registryEntry.systemPromptPreview ?? '').slice(0, 80);
229
+ totalRequests = registryEntry.totalRequests;
230
+ // Use registry name if user-renamed (not "Agent N" pattern)
231
+ const isDefaultName = /^Agent \d+$/.test(registryEntry.name);
232
+ if (!isDefaultName) {
233
+ name = registryEntry.name;
234
+ nameIsInferred = false;
235
+ }
236
+ else {
237
+ name = inferAgentName(systemPromptPreview, taskDistribution);
238
+ nameIsInferred = true;
239
+ }
240
+ }
241
+ else {
242
+ // Fingerprint in log but not in agents.json — use log data
243
+ name = inferAgentName(systemPromptPreview, taskDistribution);
244
+ nameIsInferred = true;
245
+ }
246
+ analyses.push({
247
+ fingerprint,
248
+ name,
249
+ nameIsInferred,
250
+ taskDistribution,
251
+ dominantTask,
252
+ avgInputTokens,
253
+ avgOutputTokens,
254
+ avgTotalTokens,
255
+ tokensAreEstimated,
256
+ requestsPerDay,
257
+ costPerDay,
258
+ currentModel,
259
+ daysObserved,
260
+ totalRequests,
261
+ systemPromptPreview,
262
+ });
263
+ }
264
+ // 5. Sort by costPerDay descending
265
+ analyses.sort((a, b) => b.costPerDay - a.costPerDay);
266
+ return analyses;
267
+ }
268
+ //# sourceMappingURL=policy-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-analyzer.js","sourceRoot":"","sources":["../src/policy-analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Policy Suggestions
3
+ *
4
+ * Takes AgentAnalysis[] and available providers to produce PolicySuggestion[]
5
+ * with model recommendations and savings estimates.
6
+ */
7
+ import type { AgentAnalysis } from './policy-analyzer.js';
8
+ export interface PolicySuggestion {
9
+ fingerprint: string;
10
+ agentName: string;
11
+ currentModel: string;
12
+ suggestedModel: string;
13
+ escalateTo?: string;
14
+ escalateOn?: Array<'complexity_high' | 'rate_limit' | 'error'>;
15
+ neverDowngrade: boolean;
16
+ reason: string;
17
+ estimatedDailySavings: number;
18
+ estimatedMonthlySavings: number;
19
+ noSuggestion?: boolean;
20
+ noSuggestionReason?: string;
21
+ }
22
+ /**
23
+ * Returns list of provider names (lowercase) where a key is available.
24
+ * Checks both env vars and config file. Deduplicates.
25
+ */
26
+ export declare function detectAvailableProviders(): string[];
27
+ /**
28
+ * Produce suggestions for all agents.
29
+ */
30
+ export declare function suggestPolicies(analyses: AgentAnalysis[], availableProviders: string[]): PolicySuggestion[];
31
+ //# sourceMappingURL=policy-suggestions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-suggestions.d.ts","sourceRoot":"","sources":["../src/policy-suggestions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,KAAK,CAAC,iBAAiB,GAAG,YAAY,GAAG,OAAO,CAAC,CAAC;IAC/D,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB,EAAE,MAAM,CAAC;IAC9B,uBAAuB,EAAE,MAAM,CAAC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAID;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CAwBnD;AAuID;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,GAAG,gBAAgB,EAAE,CAE3G"}
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ /**
3
+ * Policy Suggestions
4
+ *
5
+ * Takes AgentAnalysis[] and available providers to produce PolicySuggestion[]
6
+ * with model recommendations and savings estimates.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.detectAvailableProviders = detectAvailableProviders;
10
+ exports.suggestPolicies = suggestPolicies;
11
+ const config_js_1 = require("./config.js");
12
+ const policy_analyzer_js_1 = require("./policy-analyzer.js");
13
+ // ─── Provider detection ────────────────────────────────────────────────────────
14
+ /**
15
+ * Returns list of provider names (lowercase) where a key is available.
16
+ * Checks both env vars and config file. Deduplicates.
17
+ */
18
+ function detectAvailableProviders() {
19
+ const providers = new Set();
20
+ // Check environment variables
21
+ if (process.env['ANTHROPIC_API_KEY'])
22
+ providers.add('anthropic');
23
+ if (process.env['OPENAI_API_KEY'])
24
+ providers.add('openai');
25
+ if (process.env['GOOGLE_API_KEY'])
26
+ providers.add('google');
27
+ if (process.env['GEMINI_API_KEY'])
28
+ providers.add('google'); // alias
29
+ if (process.env['GROQ_API_KEY'])
30
+ providers.add('groq');
31
+ if (process.env['OPENROUTER_API_KEY'])
32
+ providers.add('openrouter');
33
+ // Check config file
34
+ try {
35
+ const providerConfigs = (0, config_js_1.getProviderConfigs)();
36
+ for (const [providerName, config] of Object.entries(providerConfigs)) {
37
+ if (config.accounts && config.accounts.length > 0 && config.accounts[0]?.apiKey) {
38
+ providers.add(providerName.toLowerCase());
39
+ }
40
+ }
41
+ }
42
+ catch {
43
+ // Config may not exist — that's fine
44
+ }
45
+ return [...providers].sort();
46
+ }
47
+ // ─── Suggestion engine ────────────────────────────────────────────────────────
48
+ /**
49
+ * Returns first candidate model whose provider prefix is in availableProviders.
50
+ * Returns null if none are available.
51
+ */
52
+ function bestAvailable(candidates, providers) {
53
+ for (const candidate of candidates) {
54
+ const prefix = candidate.split('/')[0] ?? '';
55
+ if (providers.includes(prefix)) {
56
+ return candidate;
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * Produce a policy suggestion for a single agent.
63
+ */
64
+ function suggestForAgent(analysis, availableProviders) {
65
+ const { fingerprint, name: agentName, currentModel, taskDistribution, avgTotalTokens, costPerDay, requestsPerDay, avgInputTokens, avgOutputTokens } = analysis;
66
+ let suggestedModel = null;
67
+ let escalateTo;
68
+ let escalateOn;
69
+ let neverDowngrade = false;
70
+ let reason = '';
71
+ // RULE 1 — Long-context: avgTotalTokens > 50_000
72
+ if (avgTotalTokens > 50_000) {
73
+ suggestedModel = bestAvailable([
74
+ 'anthropic/claude-opus-4-5', 'anthropic/claude-opus-4',
75
+ 'openai/gpt-4o',
76
+ ], availableProviders);
77
+ neverDowngrade = true;
78
+ reason = `Long-context patterns (avg ${Math.round(avgTotalTokens / 1000)}K tokens) — keep on full-context model`;
79
+ }
80
+ // RULE 2 — Security/review: review + security >= 0.8
81
+ else if ((taskDistribution['review'] ?? 0) + (taskDistribution['security'] ?? 0) >= 0.8) {
82
+ const pct = Math.round(((taskDistribution['review'] ?? 0) + (taskDistribution['security'] ?? 0)) * 100);
83
+ suggestedModel = bestAvailable([
84
+ 'anthropic/claude-opus-4-5', 'anthropic/claude-opus-4',
85
+ ], availableProviders) ?? currentModel;
86
+ neverDowngrade = true;
87
+ reason = `High review/security share (${pct}%) — never downgrade for accuracy`;
88
+ }
89
+ // RULE 3 — Code-heavy: code >= 0.8
90
+ else if ((taskDistribution['code'] ?? 0) >= 0.8) {
91
+ const pct = Math.round((taskDistribution['code'] ?? 0) * 100);
92
+ suggestedModel = bestAvailable([
93
+ 'anthropic/claude-sonnet-4-5', 'anthropic/claude-sonnet-4', 'openai/gpt-4o',
94
+ ], availableProviders);
95
+ escalateTo = bestAvailable([
96
+ 'anthropic/claude-opus-4-5', 'anthropic/claude-opus-4',
97
+ ], availableProviders) ?? undefined;
98
+ escalateOn = ['complexity_high'];
99
+ neverDowngrade = false;
100
+ reason = `Code-heavy (${pct}%) — sonnet for speed, escalate to opus on complexity`;
101
+ }
102
+ // RULE 4 — Summarization: summarization >= 0.8
103
+ else if ((taskDistribution['summarization'] ?? 0) >= 0.8) {
104
+ const pct = Math.round((taskDistribution['summarization'] ?? 0) * 100);
105
+ suggestedModel = bestAvailable([
106
+ 'google/gemini-2.0-flash', 'google/gemini-1.5-flash',
107
+ 'anthropic/claude-haiku-4-5', 'openai/gpt-4o-mini',
108
+ ], availableProviders);
109
+ neverDowngrade = false;
110
+ reason = `Summarization-heavy (${pct}%) — fast/cheap model`;
111
+ }
112
+ // RULE 5 — Simple/utility: (simple + utility) >= 0.8 AND avgTotalTokens < 5_000
113
+ else if (((taskDistribution['simple'] ?? 0) + (taskDistribution['utility'] ?? 0)) >= 0.8 &&
114
+ avgTotalTokens < 5_000) {
115
+ const pct = Math.round(((taskDistribution['simple'] ?? 0) + (taskDistribution['utility'] ?? 0)) * 100);
116
+ suggestedModel = bestAvailable([
117
+ 'groq/llama-3.1-8b-instant', 'groq/llama-3.3-70b',
118
+ 'google/gemini-2.0-flash', 'anthropic/claude-haiku-4-5', 'openai/gpt-4o-mini',
119
+ ], availableProviders);
120
+ neverDowngrade = false;
121
+ reason = `Simple tasks with low token volume (avg ${Math.round(avgTotalTokens)} tokens) — cheapest capable model`;
122
+ }
123
+ // DEFAULT — no dominant pattern
124
+ else {
125
+ suggestedModel = bestAvailable([
126
+ 'anthropic/claude-sonnet-4-5', 'openai/gpt-4o', 'google/gemini-2.0-flash',
127
+ ], availableProviders);
128
+ neverDowngrade = false;
129
+ reason = 'Mixed patterns — balanced capability model';
130
+ }
131
+ // Post-rule: handle null or same model
132
+ let noSuggestion;
133
+ let noSuggestionReason;
134
+ if (suggestedModel === null) {
135
+ noSuggestion = true;
136
+ noSuggestionReason = "No available provider for this agent's task profile";
137
+ suggestedModel = currentModel;
138
+ }
139
+ else if (suggestedModel === currentModel) {
140
+ noSuggestion = true;
141
+ noSuggestionReason = 'Already on the recommended model';
142
+ }
143
+ // Compute savings
144
+ const projectedDailyCost = (0, policy_analyzer_js_1.estimateDailyCost)(avgInputTokens, avgOutputTokens, requestsPerDay, suggestedModel);
145
+ const estimatedDailySavings = Math.max(0, costPerDay - projectedDailyCost);
146
+ const estimatedMonthlySavings = estimatedDailySavings * 30;
147
+ const result = {
148
+ fingerprint,
149
+ agentName,
150
+ currentModel,
151
+ suggestedModel,
152
+ neverDowngrade,
153
+ reason,
154
+ estimatedDailySavings,
155
+ estimatedMonthlySavings,
156
+ };
157
+ if (escalateTo)
158
+ result.escalateTo = escalateTo;
159
+ if (escalateOn)
160
+ result.escalateOn = escalateOn;
161
+ if (noSuggestion !== undefined)
162
+ result.noSuggestion = noSuggestion;
163
+ if (noSuggestionReason !== undefined)
164
+ result.noSuggestionReason = noSuggestionReason;
165
+ return result;
166
+ }
167
+ /**
168
+ * Produce suggestions for all agents.
169
+ */
170
+ function suggestPolicies(analyses, availableProviders) {
171
+ return analyses.map(a => suggestForAgent(a, availableProviders));
172
+ }
173
+ //# sourceMappingURL=policy-suggestions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-suggestions.js","sourceRoot":"","sources":["../src/policy-suggestions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Routing Log
3
+ *
4
+ * In-memory ring buffer (1000 entries) with JSONL persistence at
5
+ * ~/.relayplane/routing-log.jsonl. Tracks all routing decisions for
6
+ * observability and debugging.
7
+ */
8
+ import type { ResolvedBy } from './agent-policy.js';
9
+ export interface RoutingLogEntry {
10
+ ts: string;
11
+ requestId: string;
12
+ agentFingerprint: string | null;
13
+ agentName: string | null;
14
+ taskType: string;
15
+ complexity: string;
16
+ resolvedModel: string;
17
+ resolvedBy: ResolvedBy;
18
+ candidateModel: string | null;
19
+ reason: string;
20
+ inputTokens?: number;
21
+ outputTokens?: number;
22
+ }
23
+ export declare const LOG_FILE: string;
24
+ /** Reset for testing */
25
+ export declare function _resetRoutingLog(): void;
26
+ /**
27
+ * Initialize routing log by loading the last 1000 lines from file.
28
+ * Called once on proxy startup.
29
+ */
30
+ export declare function initRoutingLog(): void;
31
+ /**
32
+ * Append a routing decision to the in-memory buffer and JSONL file.
33
+ */
34
+ export declare function appendRoutingLog(entry: RoutingLogEntry): void;
35
+ /**
36
+ * Get routing log entries from the in-memory buffer with optional filters.
37
+ */
38
+ export declare function getRoutingLog(opts?: {
39
+ limit?: number;
40
+ agentFingerprint?: string;
41
+ taskType?: string;
42
+ }): RoutingLogEntry[];
43
+ /**
44
+ * No-op flush — writes are synchronous. Called for symmetry on shutdown.
45
+ */
46
+ export declare function flushRoutingLog(): void;
47
+ //# sourceMappingURL=routing-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-log.d.ts","sourceRoot":"","sources":["../src/routing-log.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIpD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IAEf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAKD,eAAO,MAAM,QAAQ,QAA0C,CAAC;AAShE,wBAAwB;AACxB,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAID;;;GAGG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAkBrC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAyB7D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,eAAe,EAAE,CAcpB;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}