@stackbilt/aegis-core 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.
- package/package.json +96 -0
- package/schema.sql +586 -0
- package/src/adapters/voice/cloudflare-agent.ts +34 -0
- package/src/auth.ts +124 -0
- package/src/bluesky.ts +464 -0
- package/src/claude-tools/content.ts +188 -0
- package/src/claude-tools/email.ts +69 -0
- package/src/claude-tools/github.ts +440 -0
- package/src/claude-tools/goals.ts +116 -0
- package/src/claude-tools/index.ts +353 -0
- package/src/claude-tools/web.ts +59 -0
- package/src/claude.ts +406 -0
- package/src/codebeast.ts +200 -0
- package/src/composite.ts +715 -0
- package/src/content/column.ts +80 -0
- package/src/content/hero-image.ts +47 -0
- package/src/content/index.ts +27 -0
- package/src/content/journal.ts +91 -0
- package/src/content/roundtable.ts +163 -0
- package/src/core.ts +309 -0
- package/src/dashboard.ts +620 -0
- package/src/decision-docs.ts +284 -0
- package/src/dispatch.ts +13 -0
- package/src/edge-env.ts +58 -0
- package/src/email.ts +850 -0
- package/src/exports.ts +156 -0
- package/src/github-projects.ts +312 -0
- package/src/github.ts +670 -0
- package/src/groq.ts +247 -0
- package/src/health-page.ts +578 -0
- package/src/index.ts +89 -0
- package/src/kernel/argus-actions.ts +397 -0
- package/src/kernel/argus-correlation.ts +639 -0
- package/src/kernel/board.ts +91 -0
- package/src/kernel/briefing.ts +177 -0
- package/src/kernel/classify-memory-topic.ts +166 -0
- package/src/kernel/cognition.ts +377 -0
- package/src/kernel/court-cards.ts +163 -0
- package/src/kernel/dispatch.ts +587 -0
- package/src/kernel/domain.ts +50 -0
- package/src/kernel/dynamic-tools.ts +322 -0
- package/src/kernel/executor-port.ts +45 -0
- package/src/kernel/executors/claude.ts +73 -0
- package/src/kernel/executors/direct.ts +237 -0
- package/src/kernel/executors/groq.ts +18 -0
- package/src/kernel/executors/index.ts +87 -0
- package/src/kernel/executors/tarotscript.ts +104 -0
- package/src/kernel/executors/workers-ai.ts +54 -0
- package/src/kernel/insight-cache.ts +76 -0
- package/src/kernel/memory/agenda.ts +200 -0
- package/src/kernel/memory/blocks.ts +188 -0
- package/src/kernel/memory/consolidation.ts +194 -0
- package/src/kernel/memory/episodic.ts +241 -0
- package/src/kernel/memory/goals.ts +156 -0
- package/src/kernel/memory/graph.ts +290 -0
- package/src/kernel/memory/index.ts +11 -0
- package/src/kernel/memory/insights.ts +316 -0
- package/src/kernel/memory/procedural.ts +467 -0
- package/src/kernel/memory/pruning.ts +67 -0
- package/src/kernel/memory/recall.ts +367 -0
- package/src/kernel/memory/semantic.ts +315 -0
- package/src/kernel/memory/synthesis.ts +161 -0
- package/src/kernel/memory-adapter.ts +369 -0
- package/src/kernel/memory-guardrails.ts +76 -0
- package/src/kernel/port.ts +23 -0
- package/src/kernel/resilience.ts +322 -0
- package/src/kernel/router.ts +471 -0
- package/src/kernel/scheduled/agent-dispatch.ts +252 -0
- package/src/kernel/scheduled/argus-analytics.ts +247 -0
- package/src/kernel/scheduled/argus-heartbeat.ts +320 -0
- package/src/kernel/scheduled/argus-notify.ts +348 -0
- package/src/kernel/scheduled/board-sync.ts +110 -0
- package/src/kernel/scheduled/ci-watcher.ts +125 -0
- package/src/kernel/scheduled/cognitive-metrics.ts +377 -0
- package/src/kernel/scheduled/consolidation.ts +229 -0
- package/src/kernel/scheduled/content-drip.ts +47 -0
- package/src/kernel/scheduled/content.ts +6 -0
- package/src/kernel/scheduled/conversation-facts.ts +204 -0
- package/src/kernel/scheduled/cost-report.ts +84 -0
- package/src/kernel/scheduled/curiosity.ts +219 -0
- package/src/kernel/scheduled/dev-activity.ts +44 -0
- package/src/kernel/scheduled/digest.ts +317 -0
- package/src/kernel/scheduled/dreaming/agenda-triage.ts +115 -0
- package/src/kernel/scheduled/dreaming/facts.ts +239 -0
- package/src/kernel/scheduled/dreaming/index.ts +8 -0
- package/src/kernel/scheduled/dreaming/llm.ts +33 -0
- package/src/kernel/scheduled/dreaming/pattern-synthesis.ts +124 -0
- package/src/kernel/scheduled/dreaming/persona.ts +75 -0
- package/src/kernel/scheduled/dreaming/symbolic.ts +31 -0
- package/src/kernel/scheduled/dreaming/task-proposals.ts +80 -0
- package/src/kernel/scheduled/dreaming.ts +66 -0
- package/src/kernel/scheduled/entropy.ts +149 -0
- package/src/kernel/scheduled/escalation.ts +192 -0
- package/src/kernel/scheduled/feed-watcher.ts +206 -0
- package/src/kernel/scheduled/goals.ts +214 -0
- package/src/kernel/scheduled/governance.ts +41 -0
- package/src/kernel/scheduled/heartbeat.ts +220 -0
- package/src/kernel/scheduled/inbox-processor.ts +174 -0
- package/src/kernel/scheduled/index.ts +245 -0
- package/src/kernel/scheduled/issue-proposer.ts +478 -0
- package/src/kernel/scheduled/issue-watcher.ts +128 -0
- package/src/kernel/scheduled/pr-automerge.ts +213 -0
- package/src/kernel/scheduled/product-health.ts +107 -0
- package/src/kernel/scheduled/reflection.ts +373 -0
- package/src/kernel/scheduled/self-improvement.ts +114 -0
- package/src/kernel/scheduled/social-engage.ts +175 -0
- package/src/kernel/scheduled/task-audit.ts +60 -0
- package/src/kernel/symbolic.ts +156 -0
- package/src/kernel/types.ts +145 -0
- package/src/landing.ts +1190 -0
- package/src/lib/audit-chain/chain.ts +28 -0
- package/src/lib/audit-chain/types.ts +12 -0
- package/src/lib/observability/errors.ts +55 -0
- package/src/markdown.ts +164 -0
- package/src/mcp/handlers.ts +647 -0
- package/src/mcp/server.ts +184 -0
- package/src/mcp/tools.ts +316 -0
- package/src/mcp-client.ts +275 -0
- package/src/mcp-server.ts +2 -0
- package/src/operator/config.example.ts +60 -0
- package/src/operator/config.ts +60 -0
- package/src/operator/index.ts +46 -0
- package/src/operator/persona.example.ts +34 -0
- package/src/operator/persona.ts +34 -0
- package/src/operator/prompt-builder.ts +190 -0
- package/src/operator/types.ts +43 -0
- package/src/pulse.ts +1179 -0
- package/src/routes/bluesky.ts +116 -0
- package/src/routes/cc-tasks.ts +328 -0
- package/src/routes/codebeast.ts +1 -0
- package/src/routes/content.ts +194 -0
- package/src/routes/conversations.ts +25 -0
- package/src/routes/dynamic-tools.ts +111 -0
- package/src/routes/feedback.ts +192 -0
- package/src/routes/health.ts +147 -0
- package/src/routes/messages.ts +228 -0
- package/src/routes/observability.ts +82 -0
- package/src/routes/operator-logs.ts +42 -0
- package/src/routes/pages.ts +96 -0
- package/src/routes/sessions.ts +54 -0
- package/src/sanitize.ts +73 -0
- package/src/schema-enums.ts +155 -0
- package/src/search.ts +112 -0
- package/src/task-intelligence.ts +497 -0
- package/src/types.ts +194 -0
- package/src/ui.ts +5 -0
- package/src/version.ts +3 -0
- package/src/workers-ai-chat.ts +333 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// Workers AI executor with OpenAI-compatible tool calling
|
|
2
|
+
// Uses GPT-OSS-120B (or any tool-capable Workers AI model) for standard queries
|
|
3
|
+
|
|
4
|
+
import { McpClient, McpRegistry } from './mcp-client.js';
|
|
5
|
+
import { handleInProcessTool, callMcpWithRetry, buildContext, resolveMcpTool } from './claude.js';
|
|
6
|
+
import { getConversationHistory, budgetConversationHistory } from './kernel/memory/index.js';
|
|
7
|
+
|
|
8
|
+
// Re-export ClaudeConfig shape so dispatch can construct it
|
|
9
|
+
export interface WorkersAiChatConfig {
|
|
10
|
+
ai: Ai;
|
|
11
|
+
model: string; // e.g. '@cf/openai/gpt-oss-120b'
|
|
12
|
+
mcpClient: McpClient;
|
|
13
|
+
mcpRegistry?: McpRegistry;
|
|
14
|
+
db: D1Database;
|
|
15
|
+
channel: string;
|
|
16
|
+
conversationId?: string;
|
|
17
|
+
githubToken?: string;
|
|
18
|
+
githubRepo?: string;
|
|
19
|
+
braveApiKey?: string;
|
|
20
|
+
memoryBinding?: import('./types.js').MemoryServiceBinding;
|
|
21
|
+
resendApiKeys?: { resendApiKey: string; resendApiKeyPersonal: string };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── Tool format conversion ─────────────────────────────────
|
|
25
|
+
|
|
26
|
+
interface OpenAiFunctionTool {
|
|
27
|
+
type: 'function';
|
|
28
|
+
function: {
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
parameters: unknown;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ToolCall {
|
|
36
|
+
id: string;
|
|
37
|
+
type: string;
|
|
38
|
+
function: { name: string; arguments: string };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Responses API output item types
|
|
42
|
+
interface ResponsesOutputMessage {
|
|
43
|
+
type: 'message';
|
|
44
|
+
content: Array<{ type: string; text: string }>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface ResponsesOutputFunctionCall {
|
|
48
|
+
type: 'function_call';
|
|
49
|
+
id: string;
|
|
50
|
+
call_id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
arguments: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type ResponsesOutputItem = ResponsesOutputMessage | ResponsesOutputFunctionCall | { type: string; [k: string]: unknown };
|
|
56
|
+
|
|
57
|
+
// ai.run() may return standard Workers AI, OpenAI Chat Completions, or Responses API format
|
|
58
|
+
export interface AiChatResponse {
|
|
59
|
+
// Standard Workers AI format
|
|
60
|
+
response?: string;
|
|
61
|
+
tool_calls?: ToolCall[];
|
|
62
|
+
// OpenAI Chat Completions format (GPT-OSS-120B actual format)
|
|
63
|
+
choices?: unknown[];
|
|
64
|
+
// Responses API format
|
|
65
|
+
output?: ResponsesOutputItem[];
|
|
66
|
+
// Usage (present in all formats)
|
|
67
|
+
usage?: { prompt_tokens: number; completion_tokens: number; total_tokens?: number };
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// OpenAI Chat Completions format (what GPT-OSS actually returns via ai.run())
|
|
72
|
+
interface ChatCompletionsChoice {
|
|
73
|
+
index: number;
|
|
74
|
+
message: {
|
|
75
|
+
role: string;
|
|
76
|
+
content: string | null;
|
|
77
|
+
tool_calls?: ToolCall[];
|
|
78
|
+
};
|
|
79
|
+
finish_reason: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Parse text from any response format (standard Workers AI, Chat Completions, or Responses API)
|
|
83
|
+
export function extractText(result: AiChatResponse): string | undefined {
|
|
84
|
+
// Standard Workers AI format
|
|
85
|
+
if (result.response) return result.response;
|
|
86
|
+
// OpenAI Chat Completions format (GPT-OSS-120B)
|
|
87
|
+
if (result.choices) {
|
|
88
|
+
const choices = result.choices as ChatCompletionsChoice[];
|
|
89
|
+
if (choices.length > 0 && choices[0].message?.content != null && choices[0].message.content !== '') {
|
|
90
|
+
return choices[0].message.content;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Responses API format — find message items
|
|
94
|
+
if (result.output) {
|
|
95
|
+
for (const item of result.output) {
|
|
96
|
+
if (item.type === 'message' && 'content' in item) {
|
|
97
|
+
const msg = item as ResponsesOutputMessage;
|
|
98
|
+
const texts = msg.content.filter(c => c.type === 'output_text' || c.type === 'text').map(c => c.text);
|
|
99
|
+
if (texts.length > 0) return texts.join('');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Extract tool calls from any response format
|
|
107
|
+
export function extractToolCalls(result: AiChatResponse): ToolCall[] {
|
|
108
|
+
// Standard Workers AI format
|
|
109
|
+
if (result.tool_calls && result.tool_calls.length > 0) return result.tool_calls;
|
|
110
|
+
// OpenAI Chat Completions format (GPT-OSS-120B)
|
|
111
|
+
if (result.choices) {
|
|
112
|
+
const choices = result.choices as ChatCompletionsChoice[];
|
|
113
|
+
if (choices.length > 0 && choices[0].message?.tool_calls) {
|
|
114
|
+
return choices[0].message.tool_calls.filter(tc => tc.id && tc.function);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Responses API format — find function_call items
|
|
118
|
+
if (result.output) {
|
|
119
|
+
return result.output
|
|
120
|
+
.filter((item): item is ResponsesOutputFunctionCall => item.type === 'function_call')
|
|
121
|
+
.map(item => ({
|
|
122
|
+
id: item.call_id || item.id,
|
|
123
|
+
type: 'function',
|
|
124
|
+
function: { name: item.name, arguments: item.arguments },
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Extract usage from any format
|
|
131
|
+
export function extractUsage(result: AiChatResponse): { prompt_tokens: number; completion_tokens: number } | undefined {
|
|
132
|
+
if (result.usage) {
|
|
133
|
+
const u = result.usage as Record<string, number>;
|
|
134
|
+
return {
|
|
135
|
+
prompt_tokens: u.prompt_tokens ?? u.input_tokens ?? 0,
|
|
136
|
+
completion_tokens: u.completion_tokens ?? u.output_tokens ?? 0,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type ChatMessage =
|
|
143
|
+
| { role: 'system'; content: string }
|
|
144
|
+
| { role: 'user'; content: string }
|
|
145
|
+
| { role: 'assistant'; content: string; tool_calls?: ToolCall[] }
|
|
146
|
+
| { role: 'tool'; tool_call_id: string; content: string };
|
|
147
|
+
|
|
148
|
+
export function toOpenAiTools(anthropicTools: unknown[]): OpenAiFunctionTool[] {
|
|
149
|
+
return (anthropicTools as Array<{ name: string; description: string; input_schema: unknown }>).map(t => ({
|
|
150
|
+
type: 'function' as const,
|
|
151
|
+
function: {
|
|
152
|
+
name: t.name,
|
|
153
|
+
description: t.description,
|
|
154
|
+
parameters: t.input_schema,
|
|
155
|
+
},
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ─── Cost rates ─────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
const GPT_OSS_RATES = { input: 0.35, output: 0.75 }; // $/MTok
|
|
162
|
+
|
|
163
|
+
// ─── Main executor ──────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
const MAX_TOOL_ROUNDS = 10;
|
|
166
|
+
|
|
167
|
+
export async function executeWorkersAiChat(
|
|
168
|
+
config: WorkersAiChatConfig,
|
|
169
|
+
userText: string,
|
|
170
|
+
): Promise<{ text: string; cost: number }> {
|
|
171
|
+
// Build context reusing Claude's buildContext (needs a ClaudeConfig-shaped object)
|
|
172
|
+
const pseudoConfig = {
|
|
173
|
+
apiKey: '',
|
|
174
|
+
model: config.model,
|
|
175
|
+
mcpClient: config.mcpClient,
|
|
176
|
+
mcpRegistry: config.mcpRegistry,
|
|
177
|
+
db: config.db,
|
|
178
|
+
channel: config.channel,
|
|
179
|
+
conversationId: config.conversationId,
|
|
180
|
+
githubToken: config.githubToken,
|
|
181
|
+
githubRepo: config.githubRepo,
|
|
182
|
+
braveApiKey: config.braveApiKey,
|
|
183
|
+
userQuery: userText,
|
|
184
|
+
};
|
|
185
|
+
const { systemPrompt, tools: anthropicTools } = await buildContext(pseudoConfig);
|
|
186
|
+
const tools = toOpenAiTools(anthropicTools);
|
|
187
|
+
|
|
188
|
+
// Load conversation history
|
|
189
|
+
const history = config.conversationId
|
|
190
|
+
? await getConversationHistory(config.db, config.conversationId, 10)
|
|
191
|
+
: [];
|
|
192
|
+
const priorHistory = history.length > 0 && history[history.length - 1]?.role === 'user'
|
|
193
|
+
? history.slice(0, -1)
|
|
194
|
+
: history;
|
|
195
|
+
|
|
196
|
+
const messages: ChatMessage[] = [
|
|
197
|
+
{ role: 'system', content: systemPrompt },
|
|
198
|
+
...budgetConversationHistory(priorHistory).map(m => ({
|
|
199
|
+
role: m.role as 'user' | 'assistant',
|
|
200
|
+
content: m.content,
|
|
201
|
+
})),
|
|
202
|
+
{ role: 'user', content: userText },
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
let totalCost = 0;
|
|
206
|
+
const TOOL_ROUNDS = MAX_TOOL_ROUNDS - 2; // Reserve 2 rounds for wrap-up
|
|
207
|
+
|
|
208
|
+
// Phase 1: Tool execution rounds (0 to TOOL_ROUNDS-1)
|
|
209
|
+
for (let round = 0; round < TOOL_ROUNDS; round++) {
|
|
210
|
+
const result = await config.ai.run(config.model as Parameters<Ai['run']>[0], {
|
|
211
|
+
messages,
|
|
212
|
+
tools,
|
|
213
|
+
max_tokens: 4096,
|
|
214
|
+
temperature: 0.2,
|
|
215
|
+
top_p: 0.9,
|
|
216
|
+
frequency_penalty: 0.3,
|
|
217
|
+
} as Record<string, unknown>) as AiChatResponse;
|
|
218
|
+
|
|
219
|
+
const usage = extractUsage(result);
|
|
220
|
+
if (usage) {
|
|
221
|
+
totalCost += (usage.prompt_tokens * GPT_OSS_RATES.input
|
|
222
|
+
+ usage.completion_tokens * GPT_OSS_RATES.output) / 1_000_000;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const toolCalls = extractToolCalls(result);
|
|
226
|
+
const responseText = extractText(result);
|
|
227
|
+
|
|
228
|
+
// No tool calls → model is done
|
|
229
|
+
if (toolCalls.length === 0) {
|
|
230
|
+
return { text: responseText ?? '(no response)', cost: totalCost };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Add assistant + tool results to conversation
|
|
234
|
+
messages.push({ role: 'assistant', content: responseText ?? '', tool_calls: toolCalls });
|
|
235
|
+
|
|
236
|
+
for (const call of toolCalls) {
|
|
237
|
+
let args: Record<string, unknown> = {};
|
|
238
|
+
try { args = JSON.parse(call.function.arguments); } catch { /* empty args */ }
|
|
239
|
+
|
|
240
|
+
let toolResult: string;
|
|
241
|
+
const inProcess = await handleInProcessTool(
|
|
242
|
+
config.db, call.function.name, args,
|
|
243
|
+
config.githubToken, config.githubRepo, config.braveApiKey,
|
|
244
|
+
undefined, undefined, config.memoryBinding,
|
|
245
|
+
config.resendApiKeys,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
if (inProcess !== null) {
|
|
249
|
+
toolResult = inProcess;
|
|
250
|
+
} else {
|
|
251
|
+
const resolved = resolveMcpTool(call.function.name, config.mcpClient, config.mcpRegistry);
|
|
252
|
+
if (resolved) {
|
|
253
|
+
toolResult = await callMcpWithRetry(resolved.client, resolved.mcpName, args);
|
|
254
|
+
} else {
|
|
255
|
+
toolResult = `Unknown tool: ${call.function.name}`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
messages.push({ role: 'tool', tool_call_id: call.id, content: toolResult });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Phase 2: Force a text-only summary — NO tools provided, ignore any tool_calls
|
|
264
|
+
// Condense messages to avoid context overflow: strip tool_calls metadata and
|
|
265
|
+
// collapse tool results into a single assistant message. GPT-OSS can choke when
|
|
266
|
+
// the full tool-call conversation is sent without a tools definition.
|
|
267
|
+
const condensed: ChatMessage[] = [messages[0]]; // system prompt
|
|
268
|
+
const toolFindings: string[] = [];
|
|
269
|
+
let lastAssistantText = '';
|
|
270
|
+
for (let i = 1; i < messages.length; i++) {
|
|
271
|
+
const msg = messages[i];
|
|
272
|
+
if (msg.role === 'user' && !('tool_call_id' in msg)) {
|
|
273
|
+
condensed.push(msg);
|
|
274
|
+
} else if (msg.role === 'assistant') {
|
|
275
|
+
if (msg.content && msg.content.trim().length > 0) lastAssistantText = msg.content;
|
|
276
|
+
// Collect assistant text, skip tool_calls metadata
|
|
277
|
+
if (msg.content) toolFindings.push(msg.content);
|
|
278
|
+
} else if (msg.role === 'tool') {
|
|
279
|
+
// Truncate long tool results to keep context budget
|
|
280
|
+
const truncated = msg.content.length > 2000
|
|
281
|
+
? msg.content.slice(0, 2000) + '... [truncated]'
|
|
282
|
+
: msg.content;
|
|
283
|
+
toolFindings.push(truncated);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Inject condensed tool findings as a single assistant message
|
|
287
|
+
if (toolFindings.length > 0) {
|
|
288
|
+
const totalBudget = 30000; // ~30K chars ≈ ~10K tokens — safe for summary
|
|
289
|
+
let accumulated = '';
|
|
290
|
+
for (const finding of toolFindings) {
|
|
291
|
+
if (accumulated.length + finding.length > totalBudget) {
|
|
292
|
+
accumulated += '\n[... additional findings truncated for summary]';
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
accumulated += '\n' + finding;
|
|
296
|
+
}
|
|
297
|
+
condensed.push({ role: 'assistant', content: `Here is what I gathered:\n${accumulated.trim()}` });
|
|
298
|
+
}
|
|
299
|
+
condensed.push({ role: 'user', content: 'Based on everything you have gathered from the tools above, provide your complete answer now. Summarize your findings clearly and concisely.' });
|
|
300
|
+
|
|
301
|
+
let summaryText: string | undefined;
|
|
302
|
+
try {
|
|
303
|
+
const summaryResult = await config.ai.run(config.model as Parameters<Ai['run']>[0], {
|
|
304
|
+
messages: condensed,
|
|
305
|
+
max_tokens: 4096,
|
|
306
|
+
temperature: 0.2,
|
|
307
|
+
top_p: 0.9,
|
|
308
|
+
frequency_penalty: 0.3,
|
|
309
|
+
} as Record<string, unknown>) as AiChatResponse;
|
|
310
|
+
|
|
311
|
+
const summaryUsage = extractUsage(summaryResult);
|
|
312
|
+
if (summaryUsage) {
|
|
313
|
+
totalCost += (summaryUsage.prompt_tokens * GPT_OSS_RATES.input
|
|
314
|
+
+ summaryUsage.completion_tokens * GPT_OSS_RATES.output) / 1_000_000;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
summaryText = extractText(summaryResult);
|
|
318
|
+
if (!summaryText) {
|
|
319
|
+
console.warn('[workers-ai-chat] Summary phase returned no extractable text. Response keys:', Object.keys(summaryResult), 'Raw:', JSON.stringify(summaryResult).slice(0, 500));
|
|
320
|
+
}
|
|
321
|
+
} catch (err) {
|
|
322
|
+
console.error('[workers-ai-chat] Summary phase AI call failed:', err instanceof Error ? err.message : String(err));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Fallback: if summary phase failed, use the last meaningful assistant text
|
|
326
|
+
// from the tool-calling phase rather than returning a useless error string
|
|
327
|
+
if (!summaryText && lastAssistantText.length > 20) {
|
|
328
|
+
console.warn('[workers-ai-chat] Using last assistant text as fallback summary');
|
|
329
|
+
summaryText = lastAssistantText;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return { text: summaryText ?? '(could not generate summary)', cost: totalCost };
|
|
333
|
+
}
|