konsilio 0.3.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 (80) hide show
  1. package/LICENSE.md +24 -0
  2. package/README.md +170 -0
  3. package/build/cli/history.d.ts +2 -0
  4. package/build/cli/history.js +179 -0
  5. package/build/cli/history.js.map +1 -0
  6. package/build/config.d.ts +38 -0
  7. package/build/config.js +118 -0
  8. package/build/config.js.map +1 -0
  9. package/build/container.d.ts +32 -0
  10. package/build/container.js +102 -0
  11. package/build/container.js.map +1 -0
  12. package/build/db/schema.d.ts +1 -0
  13. package/build/db/schema.js +67 -0
  14. package/build/db/schema.js.map +1 -0
  15. package/build/index.d.ts +2 -0
  16. package/build/index.js +140 -0
  17. package/build/index.js.map +1 -0
  18. package/build/konsilio.json +25 -0
  19. package/build/konsilio.schema.json +140 -0
  20. package/build/logger.d.ts +84 -0
  21. package/build/logger.js +121 -0
  22. package/build/logger.js.map +1 -0
  23. package/build/personas/expert.d.ts +33 -0
  24. package/build/personas/expert.js +46 -0
  25. package/build/personas/expert.js.map +1 -0
  26. package/build/personas/index.d.ts +9 -0
  27. package/build/personas/index.js +11 -0
  28. package/build/personas/index.js.map +1 -0
  29. package/build/personas/lead.d.ts +33 -0
  30. package/build/personas/lead.js +51 -0
  31. package/build/personas/lead.js.map +1 -0
  32. package/build/personas/schemas.d.ts +313 -0
  33. package/build/personas/schemas.js +97 -0
  34. package/build/personas/schemas.js.map +1 -0
  35. package/build/prompts/consolidation/critique.md +59 -0
  36. package/build/prompts/consolidation/decision.md +51 -0
  37. package/build/prompts/consolidation/extraction.md +46 -0
  38. package/build/prompts/consolidation/synthesis.md +42 -0
  39. package/build/prompts/expert-rules.md +50 -0
  40. package/build/prompts/personas/dev-tooling.md +27 -0
  41. package/build/prompts/personas/devops.md +26 -0
  42. package/build/prompts/personas/distributed-systems.md +28 -0
  43. package/build/prompts/personas/graph-dba.md +27 -0
  44. package/build/prompts/personas/node-fullstack.md +27 -0
  45. package/build/prompts/personas/performance.md +26 -0
  46. package/build/prompts/personas/security.md +26 -0
  47. package/build/prompts/personas/test-architect.md +29 -0
  48. package/build/prompts/personas/test-quoted.md +14 -0
  49. package/build/prompts/personas/typescript.md +26 -0
  50. package/build/prompts/personas/ux-dx.md +29 -0
  51. package/build/prompts/workflow-rules.md +3 -0
  52. package/build/server.d.ts +24 -0
  53. package/build/server.js +181 -0
  54. package/build/server.js.map +1 -0
  55. package/build/services/cache.service.d.ts +49 -0
  56. package/build/services/cache.service.js +85 -0
  57. package/build/services/cache.service.js.map +1 -0
  58. package/build/services/council.service.d.ts +111 -0
  59. package/build/services/council.service.js +361 -0
  60. package/build/services/council.service.js.map +1 -0
  61. package/build/services/database.service.d.ts +70 -0
  62. package/build/services/database.service.js +221 -0
  63. package/build/services/database.service.js.map +1 -0
  64. package/build/services/formatter.service.d.ts +52 -0
  65. package/build/services/formatter.service.js +133 -0
  66. package/build/services/formatter.service.js.map +1 -0
  67. package/build/services/index.d.ts +18 -0
  68. package/build/services/index.js +13 -0
  69. package/build/services/index.js.map +1 -0
  70. package/build/services/openrouter.service.d.ts +58 -0
  71. package/build/services/openrouter.service.js +128 -0
  72. package/build/services/openrouter.service.js.map +1 -0
  73. package/build/services/persona.service.d.ts +43 -0
  74. package/build/services/persona.service.js +93 -0
  75. package/build/services/persona.service.js.map +1 -0
  76. package/build/services/prompt.service.d.ts +59 -0
  77. package/build/services/prompt.service.js +209 -0
  78. package/build/services/prompt.service.js.map +1 -0
  79. package/konsilio.schema.json +140 -0
  80. package/package.json +68 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Database Service
3
+ *
4
+ * Provides SQLite database operations for session persistence using sql.js (WASM).
5
+ * Designed for dependency injection to enable testing.
6
+ */
7
+ import initSqlJs from 'sql.js';
8
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
9
+ import { dirname } from 'node:path';
10
+ /**
11
+ * DatabaseService - Handles SQLite persistence via sql.js (WASM)
12
+ *
13
+ * Uses an in-memory database that is loaded from / saved to disk.
14
+ * All queries run against the in-memory copy; changes are persisted
15
+ * explicitly after writes.
16
+ */
17
+ export class DatabaseService {
18
+ db = null;
19
+ dbPath;
20
+ maxHistorySessions;
21
+ logger;
22
+ constructor(config, logger) {
23
+ this.dbPath = config.dbPath;
24
+ this.maxHistorySessions = config.maxHistorySessions ?? 10;
25
+ this.logger = logger;
26
+ }
27
+ /**
28
+ * Factory method to create and initialize the DatabaseService.
29
+ * Async because sql.js WASM loading and DB file reading are async.
30
+ */
31
+ static async create(config, logger, schema) {
32
+ const service = new DatabaseService(config, logger);
33
+ await service.initialize(schema);
34
+ return service;
35
+ }
36
+ /**
37
+ * Initialize database: load WASM, read existing DB file (or create new), apply schema
38
+ */
39
+ async initialize(schema) {
40
+ try {
41
+ // In Node.js, sql.js auto-locates the WASM file from node_modules.
42
+ // No locateFile needed - per sql.js docs: "You can omit locateFile completely when running in node"
43
+ const SQL = await initSqlJs();
44
+ mkdirSync(dirname(this.dbPath), { recursive: true });
45
+ if (existsSync(this.dbPath)) {
46
+ const buffer = readFileSync(this.dbPath);
47
+ this.db = new SQL.Database(buffer);
48
+ }
49
+ else {
50
+ this.db = new SQL.Database();
51
+ }
52
+ // Apply schema (idempotent — uses IF NOT EXISTS)
53
+ this.db.run(schema);
54
+ this.save();
55
+ this.logger.info('Database service initialized', { path: this.dbPath });
56
+ }
57
+ catch (err) {
58
+ this.logger.error('Failed to initialize database service', {
59
+ path: this.dbPath,
60
+ error: err instanceof Error ? err.message : String(err)
61
+ });
62
+ throw err;
63
+ }
64
+ }
65
+ /**
66
+ * Persist the in-memory database to disk
67
+ */
68
+ save() {
69
+ if (!this.db)
70
+ return;
71
+ const data = this.db.export();
72
+ const buffer = Buffer.from(data);
73
+ writeFileSync(this.dbPath, buffer);
74
+ }
75
+ /**
76
+ * Check database health
77
+ */
78
+ checkHealth() {
79
+ const start = Date.now();
80
+ if (!this.db) {
81
+ return { status: 'unhealthy', latencyMs: 0, error: 'Database not initialized' };
82
+ }
83
+ try {
84
+ this.db.exec('SELECT 1');
85
+ return { status: 'healthy', latencyMs: Date.now() - start };
86
+ }
87
+ catch (err) {
88
+ return { status: 'unhealthy', latencyMs: Date.now() - start, error: err instanceof Error ? err.message : String(err) };
89
+ }
90
+ }
91
+ /**
92
+ * Save a council session result
93
+ */
94
+ saveCouncilResult(result, draftPlan, techStack, constraints, correlationId) {
95
+ if (!this.db) {
96
+ this.logger.warn('Database not initialized, skipping save', {}, correlationId);
97
+ return;
98
+ }
99
+ try {
100
+ // Save session
101
+ this.db.run(`INSERT INTO sessions (id, draft_plan_summary, tech_stack, constraints) VALUES (?, ?, ?, ?)`, [result.sessionId, draftPlan.slice(0, 500), techStack ?? null, constraints ?? null]);
102
+ // Save user message
103
+ this.db.run(`INSERT INTO messages (session_id, role, content) VALUES (?, 'user', ?)`, [result.sessionId, draftPlan]);
104
+ // Save expert findings
105
+ for (const report of result.expertReports) {
106
+ for (const finding of report.structuredOutput.findings) {
107
+ const decision = result.decisionOutput.decisions.find(d => d.findingId === finding.id);
108
+ const accepted = decision?.action === 'ACCEPT' ? 1 : 0;
109
+ const rejectionReason = decision?.action === 'REJECT' ? decision.reasoning : null;
110
+ this.db.run(`INSERT INTO expert_findings (
111
+ id, session_id, persona_id, persona_name, persona_emoji,
112
+ severity, component, issue, mitigation, accepted, rejection_reason,
113
+ duration_ms, model_used
114
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
115
+ finding.id,
116
+ result.sessionId,
117
+ report.personaId,
118
+ report.personaName,
119
+ report.personaEmoji,
120
+ finding.severity,
121
+ finding.component,
122
+ finding.issue,
123
+ finding.mitigation,
124
+ accepted,
125
+ rejectionReason,
126
+ report.durationMs,
127
+ report.modelUsed
128
+ ]);
129
+ }
130
+ // Save expert risks
131
+ for (const risk of report.structuredOutput.risks) {
132
+ this.db.run(`INSERT INTO expert_risks (
133
+ id, session_id, persona_id, category, probability, impact, description
134
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`, [
135
+ risk.id,
136
+ result.sessionId,
137
+ report.personaId,
138
+ risk.category,
139
+ risk.probability,
140
+ risk.impact,
141
+ risk.description
142
+ ]);
143
+ }
144
+ }
145
+ // Save consolidation phase outputs
146
+ this.db.run(`INSERT INTO consolidation_phases (session_id, phase_name, phase_output) VALUES (?, 'extraction', ?)`, [result.sessionId, JSON.stringify(result.extractionOutput)]);
147
+ this.db.run(`INSERT INTO consolidation_phases (session_id, phase_name, phase_output) VALUES (?, 'critique', ?)`, [result.sessionId, JSON.stringify(result.critiqueOutput)]);
148
+ this.db.run(`INSERT INTO consolidation_phases (session_id, phase_name, phase_output) VALUES (?, 'decision', ?)`, [result.sessionId, JSON.stringify(result.decisionOutput)]);
149
+ this.db.run(`INSERT INTO consolidation_phases (session_id, phase_name, phase_output) VALUES (?, 'synthesis', ?)`, [result.sessionId, JSON.stringify(result.synthesisOutput)]);
150
+ // Save final blueprint as assistant message
151
+ this.db.run(`INSERT INTO messages (session_id, role, persona_id, content) VALUES (?, 'assistant', 'consolidation', ?)`, [result.sessionId, result.finalBlueprint]);
152
+ this.save();
153
+ this.pruneOldSessions();
154
+ this.logger.debug('Council result saved', { sessionId: result.sessionId }, correlationId);
155
+ }
156
+ catch (err) {
157
+ this.logger.error('Failed to save council result', {
158
+ sessionId: result.sessionId,
159
+ error: err instanceof Error ? err.message : String(err),
160
+ }, correlationId);
161
+ }
162
+ }
163
+ /**
164
+ * Get recent sessions
165
+ */
166
+ getRecentSessions(limit = 10) {
167
+ if (!this.db)
168
+ return [];
169
+ const result = this.db.exec(`
170
+ SELECT id, created_at, draft_plan_summary, tech_stack
171
+ FROM sessions ORDER BY created_at DESC LIMIT ${limit}
172
+ `);
173
+ if (result.length === 0)
174
+ return [];
175
+ const columns = result[0].columns;
176
+ const rows = [];
177
+ for (const values of result[0].values) {
178
+ const row = {};
179
+ for (let i = 0; i < columns.length; i++) {
180
+ row[columns[i]] = values[i];
181
+ }
182
+ rows.push(row);
183
+ }
184
+ return rows;
185
+ }
186
+ /**
187
+ * Get a session's blueprint
188
+ */
189
+ getSessionBlueprint(sessionId) {
190
+ if (!this.db)
191
+ return null;
192
+ const result = this.db.exec(`
193
+ SELECT content FROM messages
194
+ WHERE session_id = '${sessionId.replace(/'/g, "''")}' AND role = 'assistant' AND persona_id = 'consolidation'
195
+ ORDER BY created_at DESC LIMIT 1
196
+ `);
197
+ if (result.length === 0 || result[0].values.length === 0)
198
+ return null;
199
+ return result[0].values[0][0];
200
+ }
201
+ /**
202
+ * Close database (no-op for sql.js, but we clear the reference)
203
+ */
204
+ close() {
205
+ if (this.db) {
206
+ this.save();
207
+ this.db.close();
208
+ this.db = null;
209
+ this.logger.info('Database service closed');
210
+ }
211
+ }
212
+ pruneOldSessions() {
213
+ if (!this.db)
214
+ return;
215
+ this.db.run(`DELETE FROM sessions WHERE id NOT IN (
216
+ SELECT id FROM sessions ORDER BY created_at DESC LIMIT ?
217
+ )`, [this.maxHistorySessions]);
218
+ this.save();
219
+ }
220
+ }
221
+ //# sourceMappingURL=database.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.service.js","sourceRoot":"","sources":["../../src/services/database.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Formatter Service
3
+ *
4
+ * Converts expert prose analysis into structured JSON output.
5
+ * Uses OpenAI's response_format with JSON schema for reliable formatting.
6
+ * Leverages gpt-4o-mini as a dedicated formatter model.
7
+ */
8
+ import type { Logger } from '../logger.js';
9
+ import type { OpenRouterService } from './openrouter.service.js';
10
+ import type { StructuredExpertOutput } from '../personas/schemas.js';
11
+ export interface FormatterConfig {
12
+ model: string;
13
+ timeoutMs: number;
14
+ maxRetries: number;
15
+ }
16
+ export interface FormatResult {
17
+ output: StructuredExpertOutput;
18
+ formattingConfidence: number;
19
+ originalProse: string;
20
+ durationMs: number;
21
+ retries: number;
22
+ }
23
+ /**
24
+ * FormatterService - Converts prose to structured JSON using response_format
25
+ */
26
+ export declare class FormatterService {
27
+ private readonly deps;
28
+ private readonly config;
29
+ private readonly responseFormat;
30
+ constructor(deps: {
31
+ openRouterService: OpenRouterService;
32
+ logger: Logger;
33
+ config: FormatterConfig;
34
+ });
35
+ /**
36
+ * Format expert prose into structured JSON output.
37
+ * Uses OpenAI's response_format feature for reliable structured output.
38
+ */
39
+ formatProse(prose: string, personaId: string, correlationId?: string): Promise<FormatResult>;
40
+ /**
41
+ * Build the system prompt for the formatter
42
+ */
43
+ private buildFormatterSystemPrompt;
44
+ /**
45
+ * Build the user message with the prose to format
46
+ */
47
+ private buildFormatUserMessage;
48
+ /**
49
+ * Calculate formatting confidence based on output quality
50
+ */
51
+ private calculateConfidence;
52
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Formatter Service
3
+ *
4
+ * Converts expert prose analysis into structured JSON output.
5
+ * Uses OpenAI's response_format with JSON schema for reliable formatting.
6
+ * Leverages gpt-4o-mini as a dedicated formatter model.
7
+ */
8
+ import { StructuredExpertOutputSchema, toResponseFormat } from '../personas/schemas.js';
9
+ /**
10
+ * FormatterService - Converts prose to structured JSON using response_format
11
+ */
12
+ export class FormatterService {
13
+ deps;
14
+ config;
15
+ responseFormat;
16
+ constructor(deps) {
17
+ this.deps = deps;
18
+ this.config = deps.config;
19
+ this.responseFormat = toResponseFormat(StructuredExpertOutputSchema, 'expert_output');
20
+ }
21
+ /**
22
+ * Format expert prose into structured JSON output.
23
+ * Uses OpenAI's response_format feature for reliable structured output.
24
+ */
25
+ async formatProse(prose, personaId, correlationId) {
26
+ const start = Date.now();
27
+ let retries = 0;
28
+ for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
29
+ try {
30
+ const messages = [
31
+ {
32
+ role: 'system',
33
+ content: this.buildFormatterSystemPrompt(personaId)
34
+ },
35
+ {
36
+ role: 'user',
37
+ content: this.buildFormatUserMessage(prose)
38
+ }
39
+ ];
40
+ const rawOutput = await this.deps.openRouterService.call({
41
+ model: this.config.model,
42
+ messages,
43
+ maxTokens: 4096,
44
+ temperature: 0.1,
45
+ timeoutMs: this.config.timeoutMs,
46
+ responseFormat: this.responseFormat
47
+ }, correlationId);
48
+ const parsed = StructuredExpertOutputSchema.parse(JSON.parse(rawOutput));
49
+ // Validate personaId matches
50
+ if (parsed.personaId !== personaId) {
51
+ parsed.personaId = personaId;
52
+ }
53
+ const result = {
54
+ output: parsed,
55
+ formattingConfidence: this.calculateConfidence(parsed),
56
+ originalProse: prose,
57
+ durationMs: Date.now() - start,
58
+ retries: attempt
59
+ };
60
+ this.deps.logger.debug('Prose formatted successfully', {
61
+ personaId,
62
+ findingsCount: parsed.findings.length,
63
+ durationMs: result.durationMs,
64
+ retries: attempt
65
+ }, correlationId);
66
+ return result;
67
+ }
68
+ catch (err) {
69
+ retries = attempt + 1;
70
+ if (attempt < this.config.maxRetries - 1) {
71
+ this.deps.logger.warn('Formatting attempt failed, retrying', {
72
+ personaId,
73
+ attempt: attempt + 1,
74
+ error: err instanceof Error ? err.message : String(err)
75
+ }, correlationId);
76
+ continue;
77
+ }
78
+ throw new Error(`Formatter failed after ${this.config.maxRetries} attempts: ${err instanceof Error ? err.message : String(err)}`);
79
+ }
80
+ }
81
+ throw new Error('Formatter exhausted all retries');
82
+ }
83
+ /**
84
+ * Build the system prompt for the formatter
85
+ */
86
+ buildFormatterSystemPrompt(personaId) {
87
+ return `You are a precise JSON formatter. Your job is to convert expert analysis prose into a structured JSON output.
88
+
89
+ Extract the following from the prose:
90
+ - findings: Specific issues with severity, component, issue description, and mitigation steps
91
+ - risks: Potential risks with category, probability, impact, and description
92
+ - missingAssumptions: Assumptions not stated in the analysis
93
+ - dependencies: External dependencies or prerequisites mentioned
94
+
95
+ Rules:
96
+ - Use severity values: CRITICAL, HIGH, MEDIUM, LOW
97
+ - Use category values: security, performance, operational, ux, technical-debt
98
+ - Use probability/impact values: high, medium, low
99
+ - Each finding must have a unique id (use kebab-case like "auth-rate-limit")
100
+ - Each risk must have a unique id
101
+ - Be precise and complete - capture all findings from the prose
102
+ - Do not add findings not supported by the prose
103
+ - Set personaId to: ${personaId}
104
+
105
+ Output ONLY valid JSON. No markdown, no explanations.`;
106
+ }
107
+ /**
108
+ * Build the user message with the prose to format
109
+ */
110
+ buildFormatUserMessage(prose) {
111
+ return `Convert the following expert analysis into structured JSON:\n\n${prose}`;
112
+ }
113
+ /**
114
+ * Calculate formatting confidence based on output quality
115
+ */
116
+ calculateConfidence(output) {
117
+ let score = 10;
118
+ // Penalize empty sections
119
+ if (output.findings.length === 0)
120
+ score -= 3;
121
+ if (output.risks.length === 0)
122
+ score -= 1;
123
+ // Penalize very short findings
124
+ for (const finding of output.findings) {
125
+ if (finding.issue.length < 20)
126
+ score -= 1;
127
+ if (finding.mitigation.length < 20)
128
+ score -= 1;
129
+ }
130
+ return Math.max(1, Math.min(10, score));
131
+ }
132
+ }
133
+ //# sourceMappingURL=formatter.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.service.js","sourceRoot":"","sources":["../../src/services/formatter.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Services Index
3
+ *
4
+ * Exports all service classes for dependency injection.
5
+ */
6
+ export { OpenRouterService } from './openrouter.service.js';
7
+ export type { Message, OpenRouterCallOptions, OpenRouterServiceConfig } from './openrouter.service.js';
8
+ export { DatabaseService } from './database.service.js';
9
+ export type { DatabaseServiceConfig, SessionSummary } from './database.service.js';
10
+ export { CouncilService } from './council.service.js';
11
+ export type { CouncilConfig, CouncilParams, CouncilOptions } from './council.service.js';
12
+ export { CacheService } from './cache.service.js';
13
+ export type { CacheEntry } from './cache.service.js';
14
+ export { PromptService } from './prompt.service.js';
15
+ export type { PromptServiceConfig, PersonaPromptData } from './prompt.service.js';
16
+ export { PersonaService } from './persona.service.js';
17
+ export { FormatterService } from './formatter.service.js';
18
+ export type { FormatterConfig, FormatResult } from './formatter.service.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Services Index
3
+ *
4
+ * Exports all service classes for dependency injection.
5
+ */
6
+ export { OpenRouterService } from './openrouter.service.js';
7
+ export { DatabaseService } from './database.service.js';
8
+ export { CouncilService } from './council.service.js';
9
+ export { CacheService } from './cache.service.js';
10
+ export { PromptService } from './prompt.service.js';
11
+ export { PersonaService } from './persona.service.js';
12
+ export { FormatterService } from './formatter.service.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG5D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * OpenRouter Service
3
+ *
4
+ * Provides LLM API calls through OpenRouter with retry logic and error handling.
5
+ * Designed for dependency injection to enable testing.
6
+ */
7
+ import type { Logger } from '../logger.js';
8
+ export interface Message {
9
+ role: 'system' | 'user' | 'assistant';
10
+ content: string;
11
+ }
12
+ export interface ResponseFormat {
13
+ type: 'json_schema';
14
+ json_schema: {
15
+ name: string;
16
+ strict: boolean;
17
+ schema: Record<string, unknown>;
18
+ };
19
+ }
20
+ export interface OpenRouterCallOptions {
21
+ model: string;
22
+ messages: Message[];
23
+ maxTokens?: number;
24
+ temperature?: number;
25
+ timeoutMs?: number;
26
+ responseFormat?: ResponseFormat;
27
+ }
28
+ export interface OpenRouterServiceConfig {
29
+ apiKey: string;
30
+ baseUrl: string;
31
+ maxRetries?: number;
32
+ baseDelayMs?: number;
33
+ }
34
+ /**
35
+ * OpenRouterService - Handles LLM API calls
36
+ */
37
+ export declare class OpenRouterService {
38
+ private readonly apiKey;
39
+ private readonly baseUrl;
40
+ private readonly maxRetries;
41
+ private readonly baseDelayMs;
42
+ private readonly logger;
43
+ constructor(config: OpenRouterServiceConfig, logger: Logger);
44
+ /**
45
+ * Call the OpenRouter API
46
+ */
47
+ call(opts: OpenRouterCallOptions, correlationId?: string): Promise<string>;
48
+ /**
49
+ * Check API health by calling the models endpoint
50
+ */
51
+ checkHealth(): Promise<{
52
+ status: 'healthy' | 'unhealthy';
53
+ latencyMs: number;
54
+ error?: string;
55
+ }>;
56
+ private isRetryable;
57
+ private delay;
58
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * OpenRouter Service
3
+ *
4
+ * Provides LLM API calls through OpenRouter with retry logic and error handling.
5
+ * Designed for dependency injection to enable testing.
6
+ */
7
+ /**
8
+ * OpenRouterService - Handles LLM API calls
9
+ */
10
+ export class OpenRouterService {
11
+ apiKey;
12
+ baseUrl;
13
+ maxRetries;
14
+ baseDelayMs;
15
+ logger;
16
+ constructor(config, logger) {
17
+ this.apiKey = config.apiKey;
18
+ this.baseUrl = config.baseUrl;
19
+ this.maxRetries = config.maxRetries ?? 3;
20
+ this.baseDelayMs = config.baseDelayMs ?? 1000;
21
+ this.logger = logger;
22
+ }
23
+ /**
24
+ * Call the OpenRouter API
25
+ */
26
+ async call(opts, correlationId) {
27
+ if (!this.apiKey) {
28
+ throw new Error('Missing OPENROUTER_API_KEY. Set it in your .env file.\n' +
29
+ 'Get your key at https://openrouter.ai/keys');
30
+ }
31
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
32
+ const controller = new AbortController();
33
+ const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 90_000);
34
+ try {
35
+ const res = await fetch(`${this.baseUrl}/chat/completions`, {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Authorization': `Bearer ${this.apiKey}`,
39
+ 'Content-Type': 'application/json',
40
+ 'HTTP-Referer': 'https://github.com/konsilio',
41
+ 'X-Title': 'Konsilio Council',
42
+ },
43
+ body: JSON.stringify({
44
+ model: opts.model,
45
+ messages: opts.messages,
46
+ max_tokens: opts.maxTokens ?? 4096,
47
+ temperature: opts.temperature ?? 0.3,
48
+ ...(opts.responseFormat && { response_format: opts.responseFormat }),
49
+ }),
50
+ signal: controller.signal,
51
+ });
52
+ if (res.status === 401)
53
+ throw new Error('Unauthorized: Invalid OpenRouter API key.');
54
+ if (res.status === 402)
55
+ throw new Error('No credits remaining on OpenRouter.');
56
+ if (res.status === 429) {
57
+ if (attempt < this.maxRetries - 1) {
58
+ await this.delay(this.baseDelayMs * Math.pow(2, attempt));
59
+ continue;
60
+ }
61
+ throw new Error('Rate limited by OpenRouter.');
62
+ }
63
+ const data = await res.json();
64
+ if (data.error)
65
+ throw new Error(`OpenRouter error: ${data.error.message}`);
66
+ const content = data.choices?.[0]?.message?.content;
67
+ if (!content)
68
+ throw new Error('Empty response from OpenRouter.');
69
+ this.logger.debug('OpenRouter call successful', { model: opts.model }, correlationId);
70
+ return content;
71
+ }
72
+ catch (err) {
73
+ if (err instanceof Error && err.name === 'AbortError') {
74
+ throw new Error(`Request timeout (>${(opts.timeoutMs ?? 90_000) / 1000}s).`);
75
+ }
76
+ if (this.isRetryable(err) && attempt < this.maxRetries - 1) {
77
+ this.logger.warn('Retrying OpenRouter call', { attempt: attempt + 1 }, correlationId);
78
+ await this.delay(this.baseDelayMs * Math.pow(2, attempt));
79
+ continue;
80
+ }
81
+ throw err;
82
+ }
83
+ finally {
84
+ clearTimeout(timeout);
85
+ }
86
+ }
87
+ throw new Error('Max retries exceeded.');
88
+ }
89
+ /**
90
+ * Check API health by calling the models endpoint
91
+ */
92
+ async checkHealth() {
93
+ const start = Date.now();
94
+ if (!this.apiKey) {
95
+ return { status: 'unhealthy', latencyMs: 0, error: 'OPENROUTER_API_KEY not configured' };
96
+ }
97
+ try {
98
+ const controller = new AbortController();
99
+ const timeout = setTimeout(() => controller.abort(), 5000);
100
+ const res = await fetch(`${this.baseUrl}/models`, {
101
+ method: 'GET',
102
+ headers: { 'Authorization': `Bearer ${this.apiKey}` },
103
+ signal: controller.signal,
104
+ });
105
+ clearTimeout(timeout);
106
+ if (res.ok) {
107
+ return { status: 'healthy', latencyMs: Date.now() - start };
108
+ }
109
+ else {
110
+ return { status: 'unhealthy', latencyMs: Date.now() - start, error: `HTTP ${res.status}: ${res.statusText}` };
111
+ }
112
+ }
113
+ catch (err) {
114
+ return { status: 'unhealthy', latencyMs: Date.now() - start, error: err instanceof Error ? err.message : String(err) };
115
+ }
116
+ }
117
+ isRetryable(error) {
118
+ if (error instanceof Error) {
119
+ const msg = error.message.toLowerCase();
120
+ return msg.includes('429') || msg.includes('503') || msg.includes('rate');
121
+ }
122
+ return false;
123
+ }
124
+ delay(ms) {
125
+ return new Promise(resolve => setTimeout(resolve, ms));
126
+ }
127
+ }
128
+ //# sourceMappingURL=openrouter.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openrouter.service.js","sourceRoot":"","sources":["../../src/services/openrouter.service.ts"],"names":[],"mappings":"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"}