quay-ai-factory 0.3.0 β†’ 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # Quay - Autonomous AI Software Factory
2
2
 
3
- > Self-hosted AI agents with MCP integration, customizable pipelines, real-time Mission Control dashboard, cost transparency, and full audit trails.
3
+ > Self-hosted AI agents with MCP integration, A3M routing, customizable pipelines, real-time Mission Control dashboard, cost transparency, and full audit trails.
4
4
 
5
5
  ## πŸš€ Features
6
6
 
7
7
  ### 5 AI-Powered Features (Built on A3M Router)
8
8
 
9
- | Feature | Description |
10
- |---------|-------------|
11
- | **Human-in-Loop Verification** | Require approval before critical actions (deploy, delete) |
12
- | **Determinism & Replay** | Black-box recorder captures every step for reproducibility |
13
- | **Visual Pipeline Builder** | Drag-drop YAML pipeline generation with built-in templates |
14
- | **Agent RAG** | Knowledge grounding to reduce hallucinations |
15
- | **Self-Healing Agents** | Auto-detect and recover from failures |
9
+ | Feature | Description | Agent Integration |
10
+ |---------|-------------|-------------------|
11
+ | **Human-in-Loop Verification** | Require approval before critical actions | Auto-triggers on deploy, delete, rm, etc. |
12
+ | **Determinism & Replay** | Black-box recorder captures every step | Snapshots after each LLM call |
13
+ | **Visual Pipeline Builder** | Drag-drop YAML pipeline generation | Built-in templates for common workflows |
14
+ | **Agent RAG** | Knowledge grounding to reduce hallucinations | Applied before first LLM call |
15
+ | **Self-Healing Agents** | Auto-detect and recover from failures | Detects 7 failure types, 5 recovery strategies |
16
16
 
17
17
  ### Core Capabilities
18
18
 
@@ -25,7 +25,7 @@
25
25
  ## πŸ“¦ Installation
26
26
 
27
27
  ```bash
28
- npm install
28
+ npm install quay-ai-factory
29
29
  ```
30
30
 
31
31
  ## πŸƒβ€β™‚οΈ Running
@@ -42,6 +42,88 @@ node build
42
42
  bun test
43
43
  ```
44
44
 
45
+ ## πŸ€– Agent Runner
46
+
47
+ The `AgentRunner` orchestrates the observe→plan→act loop with all 5 AI features:
48
+
49
+ ```typescript
50
+ import { agentRunner } from './server/agents/agentRunner.js';
51
+
52
+ // Run an agent with all features enabled
53
+ const result = await agentRunner.run(
54
+ { id: 'task-1', title: 'Deploy to production', description: '...' },
55
+ { id: 'agent-1', name: 'DeployBot', type: 'coder', provider: 'openai', model: 'gpt-4o', config: '{}' },
56
+ 'run-123',
57
+ 'You are a deployment agent...',
58
+ ['bash', 'deploy'],
59
+ {
60
+ knowledgeBaseId: 'kb-1', // Enable RAG grounding
61
+ verificationConfig: { // Custom verification rules
62
+ criticalActions: ['deploy', 'delete'],
63
+ autoApproveBelow: 0.10,
64
+ },
65
+ maxSteps: 30, // Custom step limit
66
+ }
67
+ );
68
+ ```
69
+
70
+ ### How Features Integrate
71
+
72
+ ```
73
+ User Request
74
+ ↓
75
+ AgentRunner.run()
76
+ ↓
77
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
78
+ β”‚ 1. RAG Grounding β”‚
79
+ β”‚ buildGroundedPrompt() β”‚
80
+ β”‚ β†’ Adds relevant knowledge to prompt β”‚
81
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
82
+ ↓
83
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
84
+ β”‚ 2. LLM Call β”‚
85
+ β”‚ callLLM() with A3M routing β”‚
86
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
87
+ ↓
88
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
89
+ β”‚ 3. Self-Healing Check β”‚
90
+ β”‚ detectFailure() β†’ getRecoveryStrategyβ”‚
91
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
92
+ ↓
93
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
94
+ β”‚ 4. Determinism Snapshot β”‚
95
+ β”‚ createSnapshot() with state hash β”‚
96
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
97
+ ↓
98
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
99
+ β”‚ 5. Health Update β”‚
100
+ β”‚ updateHealthStatus() β”‚
101
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
102
+ ↓
103
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
104
+ β”‚ 6. Critical Action Verification β”‚
105
+ β”‚ createVerificationRequest() β”‚
106
+ β”‚ β†’ Pauses if approval needed β”‚
107
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
108
+ ↓
109
+ Tool Execution β†’ Loop
110
+ ```
111
+
112
+ ## πŸ”§ Configuration
113
+
114
+ ```bash
115
+ # Environment variables
116
+ OPENAI_API_KEY=sk-... # OpenAI API key
117
+ ANTHROPIC_API_KEY=sk-ant-... # Anthropic API key
118
+ QUAY_DEFAULT_LLM_URL=https://api.openai.com/v1 # Default LLM endpoint
119
+
120
+ EMBEDDING_PROVIDER=openai # openai, local, or mock
121
+ EMBEDDING_ENDPOINT=http://localhost:11434/api/embeddings
122
+ QUAY_DB_PATH=./quay.db # Database path
123
+ SSE_ENABLED=true # Enable SSE
124
+ NODE_ENV=production # production, development, test
125
+ ```
126
+
45
127
  ## πŸ§ͺ Features
46
128
 
47
129
  ### Verification Dashboard (`/verification`)
@@ -64,6 +146,8 @@ const request = await createVerificationRequest(
64
146
  approveRequest(request.id, 'admin');
65
147
  ```
66
148
 
149
+ **Critical Actions** (auto-detected): `deploy`, `delete`, `rm`, `drop`, `truncate`, `shutdown`, `restart`
150
+
67
151
  ### Health Monitor (`/health`)
68
152
  Real-time agent health status with self-healing.
69
153
 
@@ -76,8 +160,13 @@ updateHealthStatus('agent-1', { latencyMs: 150, success: true });
76
160
  // Detect failure and get recovery strategy
77
161
  const failure = detectFailure(new Error('timeout'));
78
162
  const strategy = getRecoveryStrategy(failure.type);
163
+ // β†’ { action: 'retry', maxRetries: 3, fallbackModel: 'gpt-4o-mini' }
79
164
  ```
80
165
 
166
+ **Failure Types**: `timeout`, `rate_limit`, `api_error`, `context_overflow`, `tool_error`, `unknown`
167
+
168
+ **Recovery Strategies**: `retry`, `fallback_model`, `fallback_agent`, `abort`, `skip_step`
169
+
81
170
  ### Knowledge Base (`/knowledge`)
82
171
  RAG-powered knowledge grounding.
83
172
 
@@ -103,6 +192,8 @@ const templates = getTemplates('code-review');
103
192
  const yaml = generateYAML(nodes, edges);
104
193
  ```
105
194
 
195
+ **Built-in Templates**: `code-review`, `security-scan`, `data-processing`, `research-agent`
196
+
106
197
  ### Replay (`/replay`)
107
198
  Deterministic replay of agent runs.
108
199
 
@@ -113,43 +204,34 @@ await createSnapshot(runId, stepIndex, state);
113
204
  const session = await startReplay(runId);
114
205
  ```
115
206
 
116
- ## πŸ”§ Configuration
117
-
118
- ```bash
119
- # Environment variables
120
- OPENAI_API_KEY=sk-... # OpenAI API key
121
- EMBEDDING_PROVIDER=openai # openai, local, or mock
122
- EMBEDDING_ENDPOINT=http://localhost:11434/api/embeddings
123
- QUAY_DB_PATH=./quay.db # Database path
124
- SSE_ENABLED=true # Enable SSE
125
- NODE_ENV=production # production, development, test
126
- ```
127
-
128
207
  ## πŸ“ Project Structure
129
208
 
130
209
  ```
131
210
  quay/
132
211
  β”œβ”€β”€ src/
133
212
  β”‚ β”œβ”€β”€ lib/
134
- β”‚ β”‚ β”œβ”€β”€ features/ # 5 AI features
135
- β”‚ β”‚ β”‚ β”œβ”€β”€ verification.ts
136
- β”‚ β”‚ β”‚ β”œβ”€β”€ determinism.ts
137
- β”‚ β”‚ β”‚ β”œβ”€β”€ visualBuilder.ts
138
- β”‚ β”‚ β”‚ β”œβ”€β”€ agentRAG.ts
139
- β”‚ β”‚ β”‚ └── selfHealing.ts
140
- β”‚ β”‚ β”œβ”€β”€ components/ # Svelte UI components
141
- β”‚ β”‚ β”œβ”€β”€ sse/ # SSE broadcaster
142
- β”‚ β”‚ β”œβ”€β”€ a3m.ts # A3M router integration
143
- β”‚ β”‚ β”œβ”€β”€ db.ts # Database layer
144
- β”‚ β”‚ └── embedding.ts # Embedding service
213
+ β”‚ β”‚ β”œβ”€β”€ features/ # 5 AI features
214
+ β”‚ β”‚ β”‚ β”œβ”€β”€ verification.ts # Human-in-loop
215
+ β”‚ β”‚ β”‚ β”œβ”€β”€ determinism.ts # Snapshots & replay
216
+ β”‚ β”‚ β”‚ β”œβ”€β”€ visualBuilder.ts # Pipeline builder
217
+ β”‚ β”‚ β”‚ β”œβ”€β”€ agentRAG.ts # Knowledge grounding
218
+ β”‚ β”‚ β”‚ └── selfHealing.ts # Auto-recovery
219
+ β”‚ β”‚ β”œβ”€β”€ components/ # Svelte UI components
220
+ β”‚ β”‚ β”œβ”€β”€ sse/ # SSE broadcaster
221
+ β”‚ β”‚ β”œβ”€β”€ a3m.ts # A3M router integration
222
+ β”‚ β”‚ β”œβ”€β”€ db.ts # Database layer
223
+ β”‚ β”‚ └── embedding.ts # Embedding service
145
224
  β”‚ β”œβ”€β”€ routes/
146
- β”‚ β”‚ β”œβ”€β”€ api/ # API endpoints
147
- β”‚ β”‚ └── [page].svelte # UI pages
225
+ β”‚ β”‚ β”œβ”€β”€ api/ # API endpoints
226
+ β”‚ β”‚ └── [page].svelte # UI pages
148
227
  β”‚ └── server/
149
- β”‚ β”œβ”€β”€ db/ # Database schema
150
- β”‚ └── a3m/ # A3M config
228
+ β”‚ β”œβ”€β”€ agents/
229
+ β”‚ β”‚ └── agentRunner.ts # Main agent loop (integrates all features)
230
+ β”‚ β”œβ”€β”€ db/ # Database schema
231
+ β”‚ β”œβ”€β”€ features/ # Feature implementations
232
+ β”‚ └── ...
151
233
  └── tests/
152
- └── __tests__/ # Integration tests
234
+ └── __tests__/ # Integration tests
153
235
  ```
154
236
 
155
237
  ## πŸ§ͺ Testing
@@ -172,7 +254,6 @@ Total: 56 tests passing
172
254
  | Endpoint | Description |
173
255
  |----------|-------------|
174
256
  | `GET /api/verification` | List verification requests |
175
- | `POST /api/verification` | Create verification request |
176
257
  | `POST /api/verification/[id]/approve` | Approve request |
177
258
  | `POST /api/verification/[id]/reject` | Reject request |
178
259
  | `GET /api/agent-health` | Get all agent health status |
@@ -188,7 +269,7 @@ Total: 56 tests passing
188
269
 
189
270
  | Route | Description |
190
271
  |-------|-------------|
191
- | `/` | Home |
272
+ | `/` | Home / Mission Control |
192
273
  | `/features` | Features dashboard |
193
274
  | `/verification` | Verification dashboard |
194
275
  | `/health` | Agent health monitor |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quay-ai-factory",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Quay β€” Autonomous AI Software Factory. Self-hosted AI agents, MCP integration, customizable pipelines, real-time Mission Control dashboard, cost transparency, and full audit trails.",
5
5
  "keywords": [
6
6
  "ai-software-factory",
@@ -0,0 +1,388 @@
1
+ // ============================================================
2
+ // Multi-Round Dialog Optimizer
3
+ // Tracks conversation context over multiple turns and optimizes
4
+ // routing decisions based on accumulated dialogue history.
5
+ //
6
+ // Based on A3M/TMLPD multi-round dialog module
7
+ // ============================================================
8
+
9
+ export interface DialogTurn {
10
+ turn: number;
11
+ role: 'user' | 'assistant' | 'system';
12
+ content: string;
13
+ model?: string;
14
+ routingDecision?: RoutingDecision;
15
+ timestamp: number;
16
+ }
17
+
18
+ export interface DialogState {
19
+ conversationId: string;
20
+ turns: DialogTurn[];
21
+ topics: Set<string>;
22
+ topicHistory: Map<string, number>;
23
+ modelPerformance: Map<string, ModelStats>;
24
+ currentTopic: string;
25
+ sentiment: 'positive' | 'negative' | 'neutral';
26
+ complexity: number;
27
+ startedAt: number;
28
+ lastTurnAt: number;
29
+ }
30
+
31
+ export interface ModelStats {
32
+ totalTurns: number;
33
+ successfulTurns: number;
34
+ avgLatency: number;
35
+ avgConfidence: number;
36
+ topicAffinity: Map<string, number>; // topic -> success rate
37
+ }
38
+
39
+ export interface RoutingDecision {
40
+ selectedModel: string;
41
+ reasoning: string;
42
+ predictedQuality: number;
43
+ estimatedCost: number;
44
+ }
45
+
46
+ export interface DialogConfig {
47
+ maxContextTurns?: number; // Default 20
48
+ topicThreshold?: number; // Default 0.7
49
+ performanceWindow?: number; // Default 10
50
+ trackComplexity?: boolean; // Default true
51
+ }
52
+
53
+ const DEFAULT_CONFIG: Required<DialogConfig> = {
54
+ maxContextTurns: 20,
55
+ topicThreshold: 0.7,
56
+ performanceWindow: 10,
57
+ trackComplexity: true,
58
+ };
59
+
60
+ // ============================================================
61
+ // Topic Keywords for classification
62
+ // ============================================================
63
+
64
+ const TOPIC_KEYWORDS: Record<string, string[]> = {
65
+ 'coding': ['code', 'function', 'class', 'api', 'debug', 'error', 'bug', 'implement', 'refactor'],
66
+ 'data': ['data', 'database', 'sql', 'query', 'analytics', 'table', 'schema', 'migration'],
67
+ 'web': ['html', 'css', 'javascript', 'frontend', 'backend', 'http', 'api', 'rest', 'graphql'],
68
+ 'ml': ['machine learning', 'model', 'training', 'inference', 'neural', 'ai', 'deep learning', 'classifier'],
69
+ 'devops': ['deploy', 'docker', 'kubernetes', 'ci/cd', 'pipeline', 'infrastructure', 'cloud'],
70
+ 'security': ['auth', 'security', 'encryption', 'jwt', 'oauth', 'permission', 'vulnerability'],
71
+ 'general': [], // Default
72
+ };
73
+
74
+ // ============================================================
75
+ // Multi-Round Dialog Optimizer
76
+ // ============================================================
77
+
78
+ export class MultiRoundDialogOptimizer {
79
+ private dialogStates: Map<string, DialogState> = new Map();
80
+ private config: Required<DialogConfig>;
81
+
82
+ constructor(config: DialogConfig = {}) {
83
+ this.config = { ...DEFAULT_CONFIG, ...config };
84
+ }
85
+
86
+ /**
87
+ * Get or create dialog state
88
+ */
89
+ getDialogState(conversationId: string): DialogState {
90
+ if (!this.dialogStates.has(conversationId)) {
91
+ this.dialogStates.set(conversationId, this.createNewState(conversationId));
92
+ }
93
+ return this.dialogStates.get(conversationId)!;
94
+ }
95
+
96
+ /**
97
+ * Create new dialog state
98
+ */
99
+ private createNewState(conversationId: string): DialogState {
100
+ return {
101
+ conversationId,
102
+ turns: [],
103
+ topics: new Set(['general']),
104
+ topicHistory: new Map([['general', 1]]),
105
+ modelPerformance: new Map(),
106
+ currentTopic: 'general',
107
+ sentiment: 'neutral',
108
+ complexity: 0,
109
+ startedAt: Date.now(),
110
+ lastTurnAt: Date.now(),
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Add a turn to the conversation
116
+ */
117
+ addTurn(
118
+ conversationId: string,
119
+ role: 'user' | 'assistant' | 'system',
120
+ content: string,
121
+ model?: string,
122
+ routingDecision?: RoutingDecision
123
+ ): DialogState {
124
+ const state = this.getDialogState(conversationId);
125
+
126
+ const turn: DialogTurn = {
127
+ turn: state.turns.length,
128
+ role,
129
+ content,
130
+ model,
131
+ routingDecision,
132
+ timestamp: Date.now(),
133
+ };
134
+
135
+ state.turns.push(turn);
136
+ state.lastTurnAt = Date.now();
137
+
138
+ // Update topic tracking
139
+ const topics = this.extractTopics(content);
140
+ for (const topic of topics) {
141
+ state.topics.add(topic);
142
+ const count = state.topicHistory.get(topic) || 0;
143
+ state.topicHistory.set(topic, count + 1);
144
+ }
145
+ state.currentTopic = this.getMostFrequentTopic(state.topicHistory);
146
+
147
+ // Update complexity
148
+ if (this.config.trackComplexity) {
149
+ state.complexity = this.calculateComplexity(content, state.turns);
150
+ }
151
+
152
+ // Update model performance
153
+ if (role === 'assistant' && model && routingDecision) {
154
+ this.updateModelPerformance(state, model, content, routingDecision);
155
+ }
156
+
157
+ // Trim old turns
158
+ if (state.turns.length > this.config.maxContextTurns) {
159
+ state.turns = state.turns.slice(-this.config.maxContextTurns);
160
+ }
161
+
162
+ return state;
163
+ }
164
+
165
+ /**
166
+ * Extract topics from content
167
+ */
168
+ extractTopics(content: string): string[] {
169
+ const topics: string[] = [];
170
+ const lower = content.toLowerCase();
171
+
172
+ for (const [topic, keywords] of Object.entries(TOPIC_KEYWORDS)) {
173
+ if (topic === 'general') continue;
174
+ const matches = keywords.filter(kw => lower.includes(kw)).length;
175
+ if (matches > 0) {
176
+ topics.push(topic);
177
+ }
178
+ }
179
+
180
+ return topics.length > 0 ? topics : ['general'];
181
+ }
182
+
183
+ /**
184
+ * Get most frequent topic
185
+ */
186
+ private getMostFrequentTopic(topicHistory: Map<string, number>): string {
187
+ let maxCount = 0;
188
+ let maxTopic = 'general';
189
+
190
+ for (const [topic, count] of topicHistory) {
191
+ if (count > maxCount) {
192
+ maxCount = count;
193
+ maxTopic = topic;
194
+ }
195
+ }
196
+
197
+ return maxTopic;
198
+ }
199
+
200
+ /**
201
+ * Calculate complexity score (0-1)
202
+ */
203
+ calculateComplexity(content: string, turns: DialogTurn[]): number {
204
+ const words = content.split(/\s+/).length;
205
+
206
+ // Base complexity from length
207
+ let complexity = Math.min(1, words / 500);
208
+
209
+ // Indicators of complexity
210
+ const complexIndicators = ['however', 'therefore', 'consequently', 'specifically', 'although'];
211
+ const simpleIndicators = ['yes', 'no', 'okay', 'sure', 'thanks'];
212
+
213
+ const lower = content.toLowerCase();
214
+ for (const ind of complexIndicators) {
215
+ if (lower.includes(ind)) complexity += 0.1;
216
+ }
217
+ for (const ind of simpleIndicators) {
218
+ if (lower.includes(ind)) complexity -= 0.1;
219
+ }
220
+
221
+ // Turn count adds context complexity
222
+ if (turns.length > 5) complexity += 0.1;
223
+ if (turns.length > 10) complexity += 0.1;
224
+
225
+ return Math.max(0, Math.min(1, complexity));
226
+ }
227
+
228
+ /**
229
+ * Update model performance metrics
230
+ */
231
+ private updateModelPerformance(
232
+ state: DialogState,
233
+ model: string,
234
+ content: string,
235
+ routingDecision: RoutingDecision
236
+ ): void {
237
+ if (!state.modelPerformance.has(model)) {
238
+ state.modelPerformance.set(model, {
239
+ totalTurns: 0,
240
+ successfulTurns: 0,
241
+ avgLatency: 0,
242
+ avgConfidence: 0,
243
+ topicAffinity: new Map(),
244
+ });
245
+ }
246
+
247
+ const stats = state.modelPerformance.get(model)!;
248
+ stats.totalTurns++;
249
+
250
+ // Estimate success by response length and confidence
251
+ const isSuccessful = content.split(/\s+/).length > 10 && routingDecision.predictedQuality > 0.7;
252
+ if (isSuccessful) stats.successfulTurns++;
253
+
254
+ // Update latency (EMA)
255
+ const latency = routingDecision.estimatedCost > 0 ? 1000 / routingDecision.estimatedCost : 500;
256
+ stats.avgLatency = 0.9 * stats.avgLatency + 0.1 * latency;
257
+
258
+ // Update confidence (EMA)
259
+ stats.avgConfidence = 0.9 * stats.avgConfidence + 0.1 * routingDecision.predictedQuality;
260
+
261
+ // Update topic affinity
262
+ const topics = this.extractTopics(content);
263
+ for (const topic of topics) {
264
+ const current = stats.topicAffinity.get(topic) || 0.5;
265
+ stats.topicAffinity.set(topic, 0.9 * current + 0.1 * (isSuccessful ? 1 : 0));
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Recommend best model for current conversation
271
+ */
272
+ recommendModel(state: DialogState): { model: string; reasoning: string } {
273
+ const models = Array.from(state.modelPerformance.keys());
274
+
275
+ if (models.length === 0) {
276
+ return { model: 'openai/gpt-4o-mini', reasoning: 'No history - default to balanced model' };
277
+ }
278
+
279
+ if (models.length === 1) {
280
+ return { model: models[0], reasoning: 'Only one model used in this conversation' };
281
+ }
282
+
283
+ // Score each model for current topic
284
+ const scores: Map<string, number> = new Map();
285
+
286
+ for (const model of models) {
287
+ const stats = state.modelPerformance.get(model)!;
288
+
289
+ // Topic affinity for current topic
290
+ const topicScore = stats.topicAffinity.get(state.currentTopic) || 0.5;
291
+
292
+ // Success rate
293
+ const successRate = stats.successfulTurns / Math.max(1, stats.totalTurns);
294
+
295
+ // Confidence score
296
+ const confidenceScore = stats.avgConfidence;
297
+
298
+ // Combined score
299
+ const combinedScore = topicScore * 0.4 + successRate * 0.3 + confidenceScore * 0.3;
300
+ scores.set(model, combinedScore);
301
+ }
302
+
303
+ // Find best
304
+ let bestModel = models[0];
305
+ let bestScore = scores.get(models[0])!;
306
+
307
+ for (const [model, score] of scores) {
308
+ if (score > bestScore) {
309
+ bestScore = score;
310
+ bestModel = model;
311
+ }
312
+ }
313
+
314
+ return {
315
+ model: bestModel,
316
+ reasoning: `Best for ${state.currentTopic} (score: ${bestScore.toFixed(2)})`,
317
+ };
318
+ }
319
+
320
+ /**
321
+ * Get routing hint for next turn
322
+ */
323
+ getRoutingHint(conversationId: string): {
324
+ suggestedModel?: string;
325
+ topic: string;
326
+ complexity: number;
327
+ turnCount: number;
328
+ } {
329
+ const state = this.getDialogState(conversationId);
330
+ const recommendation = this.recommendModel(state);
331
+
332
+ return {
333
+ suggestedModel: recommendation.model,
334
+ topic: state.currentTopic,
335
+ complexity: state.complexity,
336
+ turnCount: state.turns.length,
337
+ };
338
+ }
339
+
340
+ /**
341
+ * Build enhanced context with history
342
+ */
343
+ buildEnhancedContext(conversationId: string, maxTurns?: number): string {
344
+ const state = this.getDialogState(conversationId);
345
+ const turnsToInclude = maxTurns
346
+ ? state.turns.slice(-maxTurns)
347
+ : state.turns.slice(-this.config.maxContextTurns);
348
+
349
+ const parts: string[] = [];
350
+
351
+ // Add topic context
352
+ parts.push(`[Conversation on: ${state.currentTopic}]`);
353
+
354
+ // Add turns
355
+ for (const turn of turnsToInclude) {
356
+ const role = turn.role === 'user' ? 'User' : turn.model || 'Assistant';
357
+ const truncated = turn.content.slice(0, 500);
358
+ parts.push(`${role}: ${truncated}`);
359
+ }
360
+
361
+ // Add complexity hint
362
+ if (state.complexity > 0.7) {
363
+ parts.push('[Complex conversation - consider using higher quality model]');
364
+ }
365
+
366
+ return parts.join('\n');
367
+ }
368
+
369
+ /**
370
+ * Reset conversation state
371
+ */
372
+ resetConversation(conversationId: string): void {
373
+ this.dialogStates.delete(conversationId);
374
+ }
375
+
376
+ /**
377
+ * Get all active conversations
378
+ */
379
+ getActiveConversations(): string[] {
380
+ return Array.from(this.dialogStates.keys());
381
+ }
382
+ }
383
+
384
+ // ============================================================
385
+ // Convenience export
386
+ // ============================================================
387
+
388
+ export const dialogOptimizer = new MultiRoundDialogOptimizer();