@gugacoder/agentic-sdk 0.2.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 (129) hide show
  1. package/dist/agent.d.ts +2 -0
  2. package/dist/agent.js +463 -0
  3. package/dist/context/compaction.d.ts +27 -0
  4. package/dist/context/compaction.js +219 -0
  5. package/dist/context/models.d.ts +6 -0
  6. package/dist/context/models.js +41 -0
  7. package/dist/context/tokenizer.d.ts +5 -0
  8. package/dist/context/tokenizer.js +11 -0
  9. package/dist/context/usage.d.ts +11 -0
  10. package/dist/context/usage.js +49 -0
  11. package/dist/display-schemas.d.ts +1865 -0
  12. package/dist/display-schemas.js +219 -0
  13. package/dist/index.d.ts +38 -0
  14. package/dist/index.js +28 -0
  15. package/dist/middleware/logging.d.ts +2 -0
  16. package/dist/middleware/logging.js +32 -0
  17. package/dist/prompts/assembly.d.ts +13 -0
  18. package/dist/prompts/assembly.js +229 -0
  19. package/dist/providers.d.ts +19 -0
  20. package/dist/providers.js +44 -0
  21. package/dist/proxy.d.ts +2 -0
  22. package/dist/proxy.js +103 -0
  23. package/dist/schemas.d.ts +228 -0
  24. package/dist/schemas.js +51 -0
  25. package/dist/session.d.ts +7 -0
  26. package/dist/session.js +102 -0
  27. package/dist/structured.d.ts +18 -0
  28. package/dist/structured.js +38 -0
  29. package/dist/tool-repair.d.ts +21 -0
  30. package/dist/tool-repair.js +72 -0
  31. package/dist/tools/api-spec.d.ts +4 -0
  32. package/dist/tools/api-spec.js +123 -0
  33. package/dist/tools/apply-patch.d.ts +484 -0
  34. package/dist/tools/apply-patch.js +157 -0
  35. package/dist/tools/ask-user.d.ts +14 -0
  36. package/dist/tools/ask-user.js +27 -0
  37. package/dist/tools/bash.d.ts +550 -0
  38. package/dist/tools/bash.js +43 -0
  39. package/dist/tools/batch.d.ts +13 -0
  40. package/dist/tools/batch.js +84 -0
  41. package/dist/tools/brave-search.d.ts +6 -0
  42. package/dist/tools/brave-search.js +19 -0
  43. package/dist/tools/code-search.d.ts +20 -0
  44. package/dist/tools/code-search.js +42 -0
  45. package/dist/tools/diagnostics.d.ts +4 -0
  46. package/dist/tools/diagnostics.js +69 -0
  47. package/dist/tools/display.d.ts +483 -0
  48. package/dist/tools/display.js +77 -0
  49. package/dist/tools/edit.d.ts +682 -0
  50. package/dist/tools/edit.js +47 -0
  51. package/dist/tools/glob.d.ts +4 -0
  52. package/dist/tools/glob.js +42 -0
  53. package/dist/tools/grep.d.ts +6 -0
  54. package/dist/tools/grep.js +69 -0
  55. package/dist/tools/http-request.d.ts +7 -0
  56. package/dist/tools/http-request.js +98 -0
  57. package/dist/tools/index.d.ts +1611 -0
  58. package/dist/tools/index.js +46 -0
  59. package/dist/tools/job-tools.d.ts +24 -0
  60. package/dist/tools/job-tools.js +67 -0
  61. package/dist/tools/list-dir.d.ts +5 -0
  62. package/dist/tools/list-dir.js +79 -0
  63. package/dist/tools/multi-edit.d.ts +814 -0
  64. package/dist/tools/multi-edit.js +57 -0
  65. package/dist/tools/read.d.ts +5 -0
  66. package/dist/tools/read.js +33 -0
  67. package/dist/tools/task.d.ts +21 -0
  68. package/dist/tools/task.js +51 -0
  69. package/dist/tools/todo.d.ts +14 -0
  70. package/dist/tools/todo.js +60 -0
  71. package/dist/tools/web-fetch.d.ts +4 -0
  72. package/dist/tools/web-fetch.js +126 -0
  73. package/dist/tools/web-search.d.ts +22 -0
  74. package/dist/tools/web-search.js +48 -0
  75. package/dist/tools/write.d.ts +550 -0
  76. package/dist/tools/write.js +30 -0
  77. package/dist/types.d.ts +201 -0
  78. package/dist/types.js +1 -0
  79. package/package.json +43 -0
  80. package/src/agent.ts +520 -0
  81. package/src/context/compaction.ts +265 -0
  82. package/src/context/models.ts +42 -0
  83. package/src/context/tokenizer.ts +12 -0
  84. package/src/context/usage.ts +65 -0
  85. package/src/display-schemas.ts +276 -0
  86. package/src/index.ts +43 -0
  87. package/src/middleware/logging.ts +37 -0
  88. package/src/prompts/assembly.ts +263 -0
  89. package/src/prompts/identity.md +10 -0
  90. package/src/prompts/patterns.md +7 -0
  91. package/src/prompts/safety.md +7 -0
  92. package/src/prompts/tool-guide.md +9 -0
  93. package/src/prompts/tools/bash.md +7 -0
  94. package/src/prompts/tools/edit.md +7 -0
  95. package/src/prompts/tools/glob.md +7 -0
  96. package/src/prompts/tools/grep.md +7 -0
  97. package/src/prompts/tools/read.md +7 -0
  98. package/src/prompts/tools/write.md +7 -0
  99. package/src/providers.ts +58 -0
  100. package/src/proxy.ts +101 -0
  101. package/src/schemas.ts +58 -0
  102. package/src/session.ts +110 -0
  103. package/src/structured.ts +65 -0
  104. package/src/tool-repair.ts +92 -0
  105. package/src/tools/api-spec.ts +158 -0
  106. package/src/tools/apply-patch.ts +188 -0
  107. package/src/tools/ask-user.ts +40 -0
  108. package/src/tools/bash.ts +51 -0
  109. package/src/tools/batch.ts +103 -0
  110. package/src/tools/brave-search.ts +24 -0
  111. package/src/tools/code-search.ts +69 -0
  112. package/src/tools/diagnostics.ts +93 -0
  113. package/src/tools/display.ts +105 -0
  114. package/src/tools/edit.ts +55 -0
  115. package/src/tools/glob.ts +46 -0
  116. package/src/tools/grep.ts +68 -0
  117. package/src/tools/http-request.ts +103 -0
  118. package/src/tools/index.ts +48 -0
  119. package/src/tools/job-tools.ts +84 -0
  120. package/src/tools/list-dir.ts +102 -0
  121. package/src/tools/multi-edit.ts +65 -0
  122. package/src/tools/read.ts +40 -0
  123. package/src/tools/task.ts +71 -0
  124. package/src/tools/todo.ts +82 -0
  125. package/src/tools/web-fetch.ts +155 -0
  126. package/src/tools/web-search.ts +75 -0
  127. package/src/tools/write.ts +34 -0
  128. package/src/types.ts +145 -0
  129. package/tsconfig.json +17 -0
@@ -0,0 +1,2 @@
1
+ import type { AiAgentEvent, AiAgentOptions } from "./types.js";
2
+ export declare function runAiAgent(prompt: string, options: AiAgentOptions): AsyncGenerator<AiAgentEvent>;
package/dist/agent.js ADDED
@@ -0,0 +1,463 @@
1
+ import { streamText, stepCountIs, wrapLanguageModel } from "ai";
2
+ import { createAiProviderRegistry } from "./providers.js";
3
+ import { createMCPClient } from "@ai-sdk/mcp";
4
+ import { Experimental_StdioMCPTransport as StdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio";
5
+ import { codingTools } from "./tools/index.js";
6
+ import { createDisplayTools } from "./tools/display.js";
7
+ import { createBashTool } from "./tools/bash.js";
8
+ import { createWriteTool } from "./tools/write.js";
9
+ import { createEditTool } from "./tools/edit.js";
10
+ import { createMultiEditTool } from "./tools/multi-edit.js";
11
+ import { createApplyPatchTool } from "./tools/apply-patch.js";
12
+ import { createAskUserTool } from "./tools/ask-user.js";
13
+ import { createWebSearchTool } from "./tools/web-search.js";
14
+ import { createTaskTool } from "./tools/task.js";
15
+ import { createBatchTool } from "./tools/batch.js";
16
+ import { createCodeSearchTool } from "./tools/code-search.js";
17
+ import { loadSession, saveSession, filterOldMedia } from "./session.js";
18
+ import { getSystemPrompt, discoverProjectContext } from "./prompts/assembly.js";
19
+ import { getContextUsage } from "./context/usage.js";
20
+ import { compactMessages } from "./context/compaction.js";
21
+ import { createToolCallRepairHandler } from "./tool-repair.js";
22
+ import { randomUUID } from "node:crypto";
23
+ import { join } from "node:path";
24
+ const DEFAULT_SESSION_DIR = join(process.cwd(), "data", "ai-sessions");
25
+ const DEFAULT_MAX_STEPS = 30;
26
+ /**
27
+ * Attempt to create an OTel span for the agent run.
28
+ * Uses dynamic import so the build doesn't break when @opentelemetry/api isn't installed.
29
+ * Returns a Span-like object with .end(), or undefined when unavailable/disabled.
30
+ */
31
+ async function tryStartSessionSpan(options, sessionId) {
32
+ if (!options.telemetry?.enabled)
33
+ return undefined;
34
+ try {
35
+ const otel = await import("@opentelemetry/api");
36
+ const tracer = otel.trace.getTracer("ai-sdk");
37
+ return tracer.startSpan("ai.agent.run", {
38
+ attributes: {
39
+ "ai.session_id": sessionId,
40
+ "ai.model": options.model,
41
+ "ai.max_steps": options.maxSteps ?? DEFAULT_MAX_STEPS,
42
+ },
43
+ });
44
+ }
45
+ catch {
46
+ console.warn("[ai] telemetry.enabled is true but @opentelemetry/api is not installed. " +
47
+ "Install it as a dependency to enable custom spans. Continuing without session span.");
48
+ return undefined;
49
+ }
50
+ }
51
+ function createMcpTransport(config) {
52
+ if (config.transport.type === "stdio") {
53
+ return new StdioMCPTransport({
54
+ command: config.transport.command,
55
+ args: config.transport.args,
56
+ });
57
+ }
58
+ // http transport — passed directly to createMCPClient
59
+ return {
60
+ type: config.transport.type,
61
+ url: config.transport.url,
62
+ headers: config.transport.headers,
63
+ };
64
+ }
65
+ export async function* runAiAgent(prompt, options) {
66
+ const providers = createAiProviderRegistry({
67
+ apiKey: options.apiKey,
68
+ aliases: options.modelAliases,
69
+ providers: options.providers,
70
+ });
71
+ const sessionDir = options.sessionDir ?? DEFAULT_SESSION_DIR;
72
+ const sessionId = options.sessionId ?? randomUUID();
73
+ // Session resume: load history if sessionDir was provided
74
+ let previousMessages = [];
75
+ if (options.sessionDir) {
76
+ previousMessages = await loadSession(sessionDir);
77
+ }
78
+ const startMs = Date.now();
79
+ const userMsg = { role: "user", content: options.contentParts ?? prompt };
80
+ if (options.messageMeta) {
81
+ userMsg._meta = { ts: new Date().toISOString(), ...options.messageMeta };
82
+ }
83
+ const messages = [
84
+ ...previousMessages,
85
+ userMsg,
86
+ ];
87
+ yield { type: "init", sessionId };
88
+ // Session-level OTel span (F-006): wraps the entire agent run
89
+ const sessionSpan = await tryStartSessionSpan(options, sessionId);
90
+ // MCP client lifecycle: create clients, collect tools, close on exit
91
+ const mcpClients = [];
92
+ try {
93
+ // Connect to MCP servers and collect their tools
94
+ let mcpTools = {};
95
+ if (options.mcpServers && options.mcpServers.length > 0) {
96
+ const connectedServers = [];
97
+ for (const serverConfig of options.mcpServers) {
98
+ try {
99
+ const transport = createMcpTransport(serverConfig);
100
+ const client = await createMCPClient({
101
+ transport,
102
+ name: serverConfig.name,
103
+ });
104
+ mcpClients.push(client);
105
+ const serverTools = await client.tools();
106
+ mcpTools = { ...mcpTools, ...serverTools };
107
+ connectedServers.push(serverConfig.name);
108
+ }
109
+ catch (err) {
110
+ const msg = err instanceof Error ? err.message : String(err);
111
+ console.warn(`[ai] MCP server "${serverConfig.name}" failed to connect: ${msg}`);
112
+ yield { type: "mcp_error", server: serverConfig.name, error: msg };
113
+ }
114
+ }
115
+ yield { type: "mcp_connected", servers: connectedServers };
116
+ }
117
+ // Override pluggable tools with configured callbacks if provided
118
+ // codingTools have priority over MCP tools (spread order: MCP first, coding on top)
119
+ const dangerousTools = {
120
+ Bash: createBashTool(),
121
+ Write: createWriteTool(),
122
+ Edit: createEditTool(),
123
+ MultiEdit: createMultiEditTool(),
124
+ ApplyPatch: createApplyPatchTool(),
125
+ };
126
+ const displayTools = options.disableDisplayTools ? {} : createDisplayTools();
127
+ let tools = { ...mcpTools, ...codingTools, ...displayTools, ...dangerousTools };
128
+ if (options.tools) {
129
+ tools = { ...tools, ...options.tools };
130
+ }
131
+ if (options.onAskUser) {
132
+ tools = { ...tools, AskUser: createAskUserTool(options.onAskUser) };
133
+ }
134
+ if (options.onWebSearch) {
135
+ tools = { ...tools, WebSearch: createWebSearchTool(options.onWebSearch) };
136
+ }
137
+ if (options.onCodeSearch) {
138
+ tools = { ...tools, CodeSearch: createCodeSearchTool(options.onCodeSearch) };
139
+ }
140
+ // Always override Task tool with parent's config so sub-agents inherit model/apiKey
141
+ tools = {
142
+ ...tools,
143
+ Task: createTaskTool({
144
+ model: options.model,
145
+ apiKey: options.apiKey,
146
+ maxSubSteps: Math.min(options.maxSteps ?? DEFAULT_MAX_STEPS, 10),
147
+ }),
148
+ };
149
+ // Override Batch tool with the fully resolved tool registry
150
+ tools = {
151
+ ...tools,
152
+ Batch: createBatchTool(tools),
153
+ };
154
+ // Build system prompt based on 3 modes:
155
+ // 1. undefined → auto: base prompt (filtered by active tools) + project context
156
+ // 2. { append } → base prompt + project context + consumer's append text
157
+ // 3. string → override: consumer's string only, no base, no discovery
158
+ let systemPrompt;
159
+ if (typeof options.system === "string") {
160
+ // Mode 3: full override — consumer replaces everything
161
+ systemPrompt = options.system;
162
+ }
163
+ else {
164
+ // Mode 1 or 2: build base prompt from active tools
165
+ const activeTools = Object.keys(tools);
166
+ const base = getSystemPrompt(activeTools);
167
+ // Discover project context once (AGENTS.md / CLAUDE.md walk-up)
168
+ const cwd = options.cwd ?? process.cwd();
169
+ const projectContext = await discoverProjectContext(cwd);
170
+ const parts = [base];
171
+ if (projectContext) {
172
+ parts.push(projectContext);
173
+ }
174
+ // Mode 2: append consumer text after base + context
175
+ if (options.system && typeof options.system === "object" && "append" in options.system) {
176
+ parts.push(options.system.append);
177
+ }
178
+ systemPrompt = parts.join("\n\n");
179
+ }
180
+ // --- Context management: usage calculation + compaction ---
181
+ const toolDefinitions = tools;
182
+ let compacted = false;
183
+ // Calculate context usage before potential compaction
184
+ let ctxUsage = getContextUsage({
185
+ model: options.model,
186
+ systemPrompt: systemPrompt ?? "",
187
+ toolDefinitions,
188
+ messages,
189
+ contextWindow: options.contextWindow,
190
+ compactThreshold: options.compactThreshold,
191
+ });
192
+ // Compact if threshold exceeded and compaction is enabled
193
+ if (ctxUsage.willCompact && !options.disableCompaction) {
194
+ const compactResult = await compactMessages(messages, {
195
+ model: options.model,
196
+ apiKey: options.apiKey,
197
+ contextWindow: options.contextWindow,
198
+ systemPromptTokens: ctxUsage.systemPrompt,
199
+ toolDefinitionsTokens: ctxUsage.toolDefinitions,
200
+ middleware: options.middleware,
201
+ telemetry: options.telemetry,
202
+ providers,
203
+ });
204
+ if (compactResult.compacted) {
205
+ messages.length = 0;
206
+ messages.push(...compactResult.messages);
207
+ compacted = true;
208
+ // Recalculate usage after compaction
209
+ ctxUsage = getContextUsage({
210
+ model: options.model,
211
+ systemPrompt: systemPrompt ?? "",
212
+ toolDefinitions,
213
+ messages,
214
+ contextWindow: options.contextWindow,
215
+ compactThreshold: options.compactThreshold,
216
+ });
217
+ }
218
+ if (compactResult.warning) {
219
+ console.warn(`[ai] ${compactResult.warning}`);
220
+ }
221
+ }
222
+ // Emit context_status event before calling streamText
223
+ yield {
224
+ type: "context_status",
225
+ context: { ...ctxUsage, compacted },
226
+ };
227
+ // Step event tracking — onStepFinish pushes events, fullStream loop yields them
228
+ const pendingStepEvents = [];
229
+ let stepCounter = 0;
230
+ let previousToolCalls = [];
231
+ // Step data collection for telemetry (F-007)
232
+ const telemetryEnabled = options.telemetry?.enabled === true;
233
+ const collectedSteps = [];
234
+ let stepStartMs = Date.now();
235
+ // Tool repair: delegate to SDK's experimental_repairToolCall
236
+ const experimentalRepairToolCall = (options.repairToolCalls !== false)
237
+ ? createToolCallRepairHandler({
238
+ model: providers.model(options.model, options.provider),
239
+ maxAttempts: options.maxRepairAttempts ?? 1,
240
+ })
241
+ : undefined;
242
+ // prepareStep integration — call consumer's callback before streamText to get initial overrides
243
+ let stepModel = providers.model(options.model, options.provider);
244
+ let stepActiveTools;
245
+ let stepToolChoice = undefined;
246
+ if (options.prepareStep) {
247
+ const overrides = options.prepareStep({
248
+ stepNumber: 0,
249
+ stepCount: 0,
250
+ previousToolCalls: [],
251
+ });
252
+ if (overrides) {
253
+ if (overrides.model) {
254
+ stepModel = providers.model(overrides.model);
255
+ }
256
+ if (overrides.activeTools) {
257
+ stepActiveTools = overrides.activeTools;
258
+ }
259
+ if (overrides.toolChoice) {
260
+ stepToolChoice = overrides.toolChoice;
261
+ }
262
+ }
263
+ }
264
+ // Apply middleware pipeline to the model if provided
265
+ const effectiveModel = options.middleware && options.middleware.length > 0
266
+ ? wrapLanguageModel({ model: stepModel, middleware: options.middleware })
267
+ : stepModel;
268
+ // Build telemetry config for Vercel AI SDK when enabled
269
+ const telemetryConfig = options.telemetry?.enabled
270
+ ? {
271
+ isEnabled: true,
272
+ functionId: options.telemetry.functionId ?? "ai-agent",
273
+ recordInputs: false,
274
+ recordOutputs: false,
275
+ metadata: {
276
+ sessionId,
277
+ model: options.model,
278
+ ...options.telemetry.metadata,
279
+ },
280
+ }
281
+ : undefined;
282
+ // stopWhen integration — AbortController to cancel the stream when condition is met
283
+ const abortController = options.stopWhen ? new AbortController() : undefined;
284
+ let stoppedByStopWhen = false;
285
+ // providerOptions for extended thinking/reasoning (F-171)
286
+ const reasoningConfig = options.reasoning
287
+ ? {
288
+ anthropic: {
289
+ thinking: {
290
+ type: "enabled",
291
+ budgetTokens: typeof options.reasoning === "object"
292
+ ? options.reasoning.budgetTokens
293
+ : 5000,
294
+ },
295
+ },
296
+ }
297
+ : undefined;
298
+ const callStreamText = () => streamText({
299
+ model: effectiveModel,
300
+ tools,
301
+ maxRetries: 3,
302
+ stopWhen: stepCountIs(options.maxSteps ?? DEFAULT_MAX_STEPS),
303
+ messages: filterOldMedia(messages, messages.findLastIndex((m) => m.role === "user")),
304
+ system: systemPrompt,
305
+ ...(telemetryConfig ? { experimental_telemetry: telemetryConfig } : {}),
306
+ ...(stepActiveTools ? { activeTools: stepActiveTools } : {}),
307
+ ...(stepToolChoice ? { toolChoice: stepToolChoice } : {}),
308
+ ...(abortController ? { abortSignal: abortController.signal } : {}),
309
+ ...(experimentalRepairToolCall ? { experimental_repairToolCall: experimentalRepairToolCall } : {}),
310
+ ...(reasoningConfig ? { providerOptions: reasoningConfig } : {}),
311
+ onStepFinish: (stepResult) => {
312
+ const toolNames = (stepResult.toolCalls ?? []).map((tc) => tc.toolName);
313
+ const stepEvent = {
314
+ type: "step_finish",
315
+ step: stepCounter,
316
+ toolCalls: toolNames,
317
+ finishReason: stepResult.finishReason ?? "unknown",
318
+ };
319
+ pendingStepEvents.push(stepEvent);
320
+ // Collect per-step breakdown when telemetry is enabled (F-007)
321
+ if (telemetryEnabled) {
322
+ const now = Date.now();
323
+ collectedSteps.push({
324
+ stepNumber: stepCounter,
325
+ toolCalls: toolNames,
326
+ inputTokens: stepResult.usage?.inputTokens ?? stepResult.usage?.promptTokens ?? 0,
327
+ outputTokens: stepResult.usage?.outputTokens ?? stepResult.usage?.completionTokens ?? 0,
328
+ durationMs: now - stepStartMs,
329
+ });
330
+ stepStartMs = now;
331
+ }
332
+ previousToolCalls = toolNames;
333
+ stepCounter++;
334
+ // Evaluate stopWhen condition after recording the step event
335
+ if (options.stopWhen && options.stopWhen(stepEvent)) {
336
+ stoppedByStopWhen = true;
337
+ abortController.abort();
338
+ }
339
+ },
340
+ });
341
+ const result = callStreamText();
342
+ // Stream text deltas — surface API errors instead of hanging
343
+ let fullText = "";
344
+ try {
345
+ for await (const part of result.fullStream) {
346
+ if (part.type === "text-delta") {
347
+ const delta = part.text ?? part.delta ?? part.textDelta ?? "";
348
+ fullText += delta;
349
+ yield { type: "text", content: delta };
350
+ }
351
+ else if (part.type === "finish-step") {
352
+ // Drain pending step_finish events collected by onStepFinish
353
+ while (pendingStepEvents.length > 0) {
354
+ yield pendingStepEvents.shift();
355
+ }
356
+ }
357
+ else if (part.type === "reasoning" || part.type === "reasoning-delta") {
358
+ yield {
359
+ type: "reasoning",
360
+ content: part.delta ?? part.textDelta ?? part.text ?? "",
361
+ };
362
+ }
363
+ else if (part.type === "tool-call") {
364
+ yield {
365
+ type: "tool-call",
366
+ toolCallId: part.toolCallId,
367
+ toolName: part.toolName,
368
+ args: part.input ?? part.args ?? {},
369
+ };
370
+ }
371
+ else if (part.type === "tool-result") {
372
+ yield {
373
+ type: "tool-result",
374
+ toolCallId: part.toolCallId,
375
+ toolName: part.toolName,
376
+ result: part.output ?? part.result,
377
+ };
378
+ }
379
+ else if (part.type === "error") {
380
+ const errMsg = part.error?.message ?? JSON.stringify(part.error ?? part);
381
+ throw new Error(`OpenRouter API error: ${errMsg}`);
382
+ }
383
+ }
384
+ }
385
+ catch (err) {
386
+ // If stopWhen triggered the abort, this is expected — continue normally
387
+ if (stoppedByStopWhen && err instanceof Error && err.name === "AbortError") {
388
+ // Expected abort — fall through to drain remaining events and finalize
389
+ }
390
+ else {
391
+ // Re-throw with context if it's not already our error
392
+ const msg = err instanceof Error ? err.message : String(err);
393
+ if (!msg.startsWith("OpenRouter")) {
394
+ throw new Error(`OpenRouter stream error: ${msg}`);
395
+ }
396
+ throw err;
397
+ }
398
+ }
399
+ // Drain any remaining step events not yet emitted (edge case: stream ends without step-finish)
400
+ while (pendingStepEvents.length > 0) {
401
+ yield pendingStepEvents.shift();
402
+ }
403
+ // Persist session and collect usage — may fail if stream was aborted by stopWhen
404
+ let usage = {};
405
+ let steps = [];
406
+ let finishReason = stoppedByStopWhen ? "stop_when" : "unknown";
407
+ let totalCostUsd = 0;
408
+ try {
409
+ const response = await result.response;
410
+ const responseMsgs = response.messages.map((m) => ({
411
+ ...m,
412
+ _meta: { ts: new Date().toISOString() },
413
+ }));
414
+ await saveSession(sessionDir, [...messages, ...responseMsgs]);
415
+ usage = await result.totalUsage;
416
+ steps = await result.steps;
417
+ finishReason = stoppedByStopWhen ? "stop_when" : (await result.finishReason ?? "unknown");
418
+ // Fetch cost from OpenRouter generation endpoint
419
+ const genId = response.id;
420
+ if (genId) {
421
+ // Small delay — OpenRouter may not have the generation ready immediately
422
+ await new Promise((r) => setTimeout(r, 500));
423
+ const res = await fetch(`https://openrouter.ai/api/v1/generation?id=${genId}`, {
424
+ headers: { Authorization: `Bearer ${options.apiKey}` },
425
+ });
426
+ if (res.ok) {
427
+ const gen = (await res.json());
428
+ totalCostUsd = gen.data?.total_cost ?? 0;
429
+ }
430
+ }
431
+ }
432
+ catch {
433
+ // If stopWhen aborted the stream, response/usage promises may reject — use defaults
434
+ }
435
+ yield { type: "result", content: fullText };
436
+ yield {
437
+ type: "usage",
438
+ usage: {
439
+ inputTokens: usage.inputTokens ?? usage.promptTokens ?? 0,
440
+ outputTokens: usage.outputTokens ?? usage.completionTokens ?? 0,
441
+ cacheReadInputTokens: 0,
442
+ cacheCreationInputTokens: 0,
443
+ totalCostUsd,
444
+ numTurns: steps.length,
445
+ durationMs: Date.now() - startMs,
446
+ durationApiMs: 0,
447
+ stopReason: finishReason,
448
+ // Step breakdown — only populated when telemetry is enabled (F-007)
449
+ ...(telemetryEnabled && collectedSteps.length > 0
450
+ ? { steps: collectedSteps }
451
+ : {}),
452
+ },
453
+ };
454
+ }
455
+ finally {
456
+ // End session span before cleanup (F-006)
457
+ sessionSpan?.end();
458
+ // Close all MCP clients to avoid leaked connections/processes
459
+ for (const client of mcpClients) {
460
+ await client.close().catch(() => { });
461
+ }
462
+ }
463
+ }
@@ -0,0 +1,27 @@
1
+ import { type ModelMessage, type LanguageModelMiddleware } from "ai";
2
+ import type { AiTelemetryOptions } from "../types.js";
3
+ import type { createAiProviderRegistry } from "../providers.js";
4
+ export interface CompactOptions {
5
+ model: string;
6
+ apiKey: string;
7
+ contextWindow?: number;
8
+ systemPromptTokens: number;
9
+ toolDefinitionsTokens: number;
10
+ middleware?: LanguageModelMiddleware[];
11
+ telemetry?: AiTelemetryOptions;
12
+ /** Provider registry para reuso. Se nao fornecido, cria um internamente (backward compat). */
13
+ providers?: ReturnType<typeof createAiProviderRegistry>;
14
+ }
15
+ export interface CompactResult {
16
+ messages: ModelMessage[];
17
+ compacted: boolean;
18
+ warning?: string;
19
+ }
20
+ /**
21
+ * Compacts conversation messages by summarizing older messages (head)
22
+ * and keeping recent messages (tail) intact.
23
+ *
24
+ * Uses the same model and apiKey as the agent for summarization.
25
+ * If summarization fails, returns original messages with a warning.
26
+ */
27
+ export declare function compactMessages(messages: ModelMessage[], options: CompactOptions): Promise<CompactResult>;