|
1 | 1 | import { type EdgeEnv } from '../dispatch.js'; |
2 | 2 | import { getAllMemoryForReflection } from '../memory-adapter.js'; |
| 3 | +import { askWorkersAiOrGroq } from './dreaming/llm.js'; |
3 | 4 | // Standalone emails removed — all content routes through daily digest |
4 | 5 |
|
5 | 6 | // ─── Memory Reflection (#introspection) ─────────────────────── |
@@ -84,67 +85,34 @@ export async function runMemoryReflectionCycle(env: EdgeEnv): Promise<void> { |
84 | 85 |
|
85 | 86 | const userPrompt = `Here is my complete active memory — ${entries.length} entries across ${byTopic.size} topics:\n\n${topicSections}\n\nReflect.`; |
86 | 87 |
|
87 | | - // Call Claude directly (no tools needed, pure generation) |
88 | | - const anthropicBase = env.anthropicBaseUrl || 'https://api.anthropic.com'; |
89 | | - |
90 | | - try { |
91 | | - const response = await fetch(`${anthropicBase}/v1/messages`, { |
92 | | - method: 'POST', |
93 | | - headers: { |
94 | | - 'Content-Type': 'application/json', |
95 | | - 'x-api-key': env.anthropicApiKey, |
96 | | - 'anthropic-version': '2023-06-01', |
97 | | - }, |
98 | | - body: JSON.stringify({ |
99 | | - model: env.claudeModel, |
100 | | - max_tokens: 2048, |
101 | | - system: REFLECTION_SYSTEM, |
102 | | - messages: [{ role: 'user', content: userPrompt }], |
103 | | - }), |
104 | | - }); |
105 | | - |
106 | | - if (!response.ok) { |
107 | | - const errText = await response.text(); |
108 | | - throw new Error(`Anthropic API error ${response.status}: ${errText}`); |
109 | | - } |
110 | | - |
111 | | - const data = await response.json<{ |
112 | | - content: Array<{ type: string; text?: string }>; |
113 | | - usage: { input_tokens: number; output_tokens: number }; |
114 | | - }>(); |
115 | | - |
116 | | - const reflection = data.content.filter(b => b.type === 'text').map(b => b.text ?? '').join(''); |
117 | | - if (!reflection) throw new Error('Empty reflection response'); |
118 | | - |
119 | | - // Cost calculation (Sonnet rates) |
120 | | - const cost = (data.usage.input_tokens * 3 + data.usage.output_tokens * 15) / 1_000_000; |
121 | | - const topics = [...byTopic.keys()]; |
122 | | - |
123 | | - // Store in D1 |
124 | | - await env.db.prepare( |
125 | | - 'INSERT INTO reflections (content, memory_count, topics_covered, cost) VALUES (?, ?, ?, ?)' |
126 | | - ).bind(reflection, entries.length, JSON.stringify(topics), cost).run(); |
127 | | - |
128 | | - // Queue reflection for daily digest instead of standalone email |
129 | | - const reflectionPayload = JSON.stringify({ |
130 | | - reflection: reflection.slice(0, 2000), |
131 | | - memoryCount: entries.length, |
132 | | - topics, |
133 | | - timestamp: new Date().toISOString(), |
134 | | - }); |
135 | | - await env.db.prepare( |
136 | | - "INSERT INTO digest_sections (section, payload) VALUES ('memory_reflection', ?)" |
137 | | - ).bind(reflectionPayload).run(); |
138 | | - |
139 | | - // Record timestamp |
140 | | - await env.db.prepare( |
141 | | - "INSERT OR REPLACE INTO web_events (event_id, received_at) VALUES ('last_memory_reflection', datetime('now'))" |
142 | | - ).run(); |
143 | | - |
144 | | - console.log(`[reflection] Weekly memory reflection complete — ${entries.length} memories, ${byTopic.size} topics, $${cost.toFixed(4)}`); |
145 | | - } catch (err) { |
146 | | - console.error('[reflection] Memory reflection failed:', err instanceof Error ? err.message : String(err)); |
147 | | - } |
| 88 | + // Route through Workers AI (free) ��� Groq fallback — no raw Anthropic calls (#412) |
| 89 | + const reflection = await askWorkersAiOrGroq(env, REFLECTION_SYSTEM, userPrompt); |
| 90 | + if (!reflection) throw new Error('Empty reflection response'); |
| 91 | + |
| 92 | + const topics = [...byTopic.keys()]; |
| 93 | + |
| 94 | + // Store in D1 (cost ≈ 0 for Workers AI, minimal for Groq fallback) |
| 95 | + await env.db.prepare( |
| 96 | + 'INSERT INTO reflections (content, memory_count, topics_covered, cost) VALUES (?, ?, ?, ?)' |
| 97 | + ).bind(reflection, entries.length, JSON.stringify(topics), 0).run(); |
| 98 | + |
| 99 | + // Queue reflection for daily digest instead of standalone email |
| 100 | + const reflectionPayload = JSON.stringify({ |
| 101 | + reflection: reflection.slice(0, 2000), |
| 102 | + memoryCount: entries.length, |
| 103 | + topics, |
| 104 | + timestamp: new Date().toISOString(), |
| 105 | + }); |
| 106 | + await env.db.prepare( |
| 107 | + "INSERT INTO digest_sections (section, payload) VALUES ('memory_reflection', ?)" |
| 108 | + ).bind(reflectionPayload).run(); |
| 109 | + |
| 110 | + // Record timestamp |
| 111 | + await env.db.prepare( |
| 112 | + "INSERT OR REPLACE INTO web_events (event_id, received_at) VALUES ('last_memory_reflection', datetime('now'))" |
| 113 | + ).run(); |
| 114 | + |
| 115 | + console.log(`[reflection] Weekly memory reflection complete — ${entries.length} memories, ${byTopic.size} topics`); |
148 | 116 | } |
149 | 117 |
|
150 | 118 | // ─── Operator's Log (#introspection) ────────────────────────── |
@@ -384,54 +352,22 @@ ${goalSummary} |
384 | 352 |
|
385 | 353 | Write your worklog entry.`; |
386 | 354 |
|
387 | | - const anthropicBase = env.anthropicBaseUrl || 'https://api.anthropic.com'; |
388 | | - |
389 | | - try { |
390 | | - const response = await fetch(`${anthropicBase}/v1/messages`, { |
391 | | - method: 'POST', |
392 | | - headers: { |
393 | | - 'Content-Type': 'application/json', |
394 | | - 'x-api-key': env.anthropicApiKey, |
395 | | - 'anthropic-version': '2023-06-01', |
396 | | - }, |
397 | | - body: JSON.stringify({ |
398 | | - model: env.claudeModel, |
399 | | - max_tokens: 1024, |
400 | | - system: OPERATOR_LOG_SYSTEM, |
401 | | - messages: [{ role: 'user', content: userPrompt }], |
402 | | - }), |
403 | | - }); |
404 | | - |
405 | | - if (!response.ok) { |
406 | | - const errText = await response.text(); |
407 | | - throw new Error(`Anthropic API error ${response.status}: ${errText}`); |
408 | | - } |
409 | | - |
410 | | - const data = await response.json<{ |
411 | | - content: Array<{ type: string; text?: string }>; |
412 | | - usage: { input_tokens: number; output_tokens: number }; |
413 | | - }>(); |
414 | | - |
415 | | - const logEntry = data.content.filter(b => b.type === 'text').map(b => b.text ?? '').join(''); |
416 | | - if (!logEntry) throw new Error('Empty log response'); |
417 | | - |
418 | | - const cost = (data.usage.input_tokens * 3 + data.usage.output_tokens * 15) / 1_000_000; |
419 | | - |
420 | | - // Store in D1 |
421 | | - await env.db.prepare( |
422 | | - 'INSERT INTO operator_log (content, episodes_count, goals_run, tasks_completed, tasks_failed, prs_created, total_cost, cost) VALUES (?, ?, ?, ?, ?, ?, ?, ?)' |
423 | | - ).bind(logEntry, activity.episodes.length, activity.goalActions.length, activity.tasksCompleted.length, activity.tasksFailed.length, prsCreated, activity.totalCost, cost).run(); |
424 | | - |
425 | | - // Operator log is consumed by the daily digest (reads operator_log table). |
426 | | - // No standalone email — consolidated into the single daily digest. |
427 | | - |
428 | | - // Record timestamp |
429 | | - await env.db.prepare( |
430 | | - "INSERT OR REPLACE INTO web_events (event_id, received_at) VALUES ('last_operator_log', datetime('now'))" |
431 | | - ).run(); |
432 | | - |
433 | | - console.log(`[operator-log] Nightly log complete — ${activity.episodes.length} episodes, ${activity.goalActions.length} goals, $${cost.toFixed(4)}`); |
434 | | - } catch (err) { |
435 | | - console.error('[operator-log] Failed:', err instanceof Error ? err.message : String(err)); |
436 | | - } |
| 355 | + // Route through Workers AI (free) → Groq fallback — no raw Anthropic calls (#412) |
| 356 | + const logEntry = await askWorkersAiOrGroq(env, OPERATOR_LOG_SYSTEM, userPrompt); |
| 357 | + if (!logEntry) throw new Error('Empty log response'); |
| 358 | + |
| 359 | + // Store in D1 (cost ≈ 0 for Workers AI, minimal for Groq fallback) |
| 360 | + await env.db.prepare( |
| 361 | + 'INSERT INTO operator_log (content, episodes_count, goals_run, tasks_completed, tasks_failed, prs_created, total_cost, cost) VALUES (?, ?, ?, ?, ?, ?, ?, ?)' |
| 362 | + ).bind(logEntry, activity.episodes.length, activity.goalActions.length, activity.tasksCompleted.length, activity.tasksFailed.length, prsCreated, activity.totalCost, 0).run(); |
| 363 | + |
| 364 | + // Operator log is consumed by the daily digest (reads operator_log table). |
| 365 | + // No standalone email — consolidated into the single daily digest. |
| 366 | + |
| 367 | + // Record timestamp |
| 368 | + await env.db.prepare( |
| 369 | + "INSERT OR REPLACE INTO web_events (event_id, received_at) VALUES ('last_operator_log', datetime('now'))" |
| 370 | + ).run(); |
| 371 | + |
| 372 | + console.log(`[operator-log] Nightly log complete — ${activity.episodes.length} episodes, ${activity.goalActions.length} goals`); |
437 | 373 | } |
0 commit comments