@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.
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +463 -0
- package/dist/context/compaction.d.ts +27 -0
- package/dist/context/compaction.js +219 -0
- package/dist/context/models.d.ts +6 -0
- package/dist/context/models.js +41 -0
- package/dist/context/tokenizer.d.ts +5 -0
- package/dist/context/tokenizer.js +11 -0
- package/dist/context/usage.d.ts +11 -0
- package/dist/context/usage.js +49 -0
- package/dist/display-schemas.d.ts +1865 -0
- package/dist/display-schemas.js +219 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +28 -0
- package/dist/middleware/logging.d.ts +2 -0
- package/dist/middleware/logging.js +32 -0
- package/dist/prompts/assembly.d.ts +13 -0
- package/dist/prompts/assembly.js +229 -0
- package/dist/providers.d.ts +19 -0
- package/dist/providers.js +44 -0
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.js +103 -0
- package/dist/schemas.d.ts +228 -0
- package/dist/schemas.js +51 -0
- package/dist/session.d.ts +7 -0
- package/dist/session.js +102 -0
- package/dist/structured.d.ts +18 -0
- package/dist/structured.js +38 -0
- package/dist/tool-repair.d.ts +21 -0
- package/dist/tool-repair.js +72 -0
- package/dist/tools/api-spec.d.ts +4 -0
- package/dist/tools/api-spec.js +123 -0
- package/dist/tools/apply-patch.d.ts +484 -0
- package/dist/tools/apply-patch.js +157 -0
- package/dist/tools/ask-user.d.ts +14 -0
- package/dist/tools/ask-user.js +27 -0
- package/dist/tools/bash.d.ts +550 -0
- package/dist/tools/bash.js +43 -0
- package/dist/tools/batch.d.ts +13 -0
- package/dist/tools/batch.js +84 -0
- package/dist/tools/brave-search.d.ts +6 -0
- package/dist/tools/brave-search.js +19 -0
- package/dist/tools/code-search.d.ts +20 -0
- package/dist/tools/code-search.js +42 -0
- package/dist/tools/diagnostics.d.ts +4 -0
- package/dist/tools/diagnostics.js +69 -0
- package/dist/tools/display.d.ts +483 -0
- package/dist/tools/display.js +77 -0
- package/dist/tools/edit.d.ts +682 -0
- package/dist/tools/edit.js +47 -0
- package/dist/tools/glob.d.ts +4 -0
- package/dist/tools/glob.js +42 -0
- package/dist/tools/grep.d.ts +6 -0
- package/dist/tools/grep.js +69 -0
- package/dist/tools/http-request.d.ts +7 -0
- package/dist/tools/http-request.js +98 -0
- package/dist/tools/index.d.ts +1611 -0
- package/dist/tools/index.js +46 -0
- package/dist/tools/job-tools.d.ts +24 -0
- package/dist/tools/job-tools.js +67 -0
- package/dist/tools/list-dir.d.ts +5 -0
- package/dist/tools/list-dir.js +79 -0
- package/dist/tools/multi-edit.d.ts +814 -0
- package/dist/tools/multi-edit.js +57 -0
- package/dist/tools/read.d.ts +5 -0
- package/dist/tools/read.js +33 -0
- package/dist/tools/task.d.ts +21 -0
- package/dist/tools/task.js +51 -0
- package/dist/tools/todo.d.ts +14 -0
- package/dist/tools/todo.js +60 -0
- package/dist/tools/web-fetch.d.ts +4 -0
- package/dist/tools/web-fetch.js +126 -0
- package/dist/tools/web-search.d.ts +22 -0
- package/dist/tools/web-search.js +48 -0
- package/dist/tools/write.d.ts +550 -0
- package/dist/tools/write.js +30 -0
- package/dist/types.d.ts +201 -0
- package/dist/types.js +1 -0
- package/package.json +43 -0
- package/src/agent.ts +520 -0
- package/src/context/compaction.ts +265 -0
- package/src/context/models.ts +42 -0
- package/src/context/tokenizer.ts +12 -0
- package/src/context/usage.ts +65 -0
- package/src/display-schemas.ts +276 -0
- package/src/index.ts +43 -0
- package/src/middleware/logging.ts +37 -0
- package/src/prompts/assembly.ts +263 -0
- package/src/prompts/identity.md +10 -0
- package/src/prompts/patterns.md +7 -0
- package/src/prompts/safety.md +7 -0
- package/src/prompts/tool-guide.md +9 -0
- package/src/prompts/tools/bash.md +7 -0
- package/src/prompts/tools/edit.md +7 -0
- package/src/prompts/tools/glob.md +7 -0
- package/src/prompts/tools/grep.md +7 -0
- package/src/prompts/tools/read.md +7 -0
- package/src/prompts/tools/write.md +7 -0
- package/src/providers.ts +58 -0
- package/src/proxy.ts +101 -0
- package/src/schemas.ts +58 -0
- package/src/session.ts +110 -0
- package/src/structured.ts +65 -0
- package/src/tool-repair.ts +92 -0
- package/src/tools/api-spec.ts +158 -0
- package/src/tools/apply-patch.ts +188 -0
- package/src/tools/ask-user.ts +40 -0
- package/src/tools/bash.ts +51 -0
- package/src/tools/batch.ts +103 -0
- package/src/tools/brave-search.ts +24 -0
- package/src/tools/code-search.ts +69 -0
- package/src/tools/diagnostics.ts +93 -0
- package/src/tools/display.ts +105 -0
- package/src/tools/edit.ts +55 -0
- package/src/tools/glob.ts +46 -0
- package/src/tools/grep.ts +68 -0
- package/src/tools/http-request.ts +103 -0
- package/src/tools/index.ts +48 -0
- package/src/tools/job-tools.ts +84 -0
- package/src/tools/list-dir.ts +102 -0
- package/src/tools/multi-edit.ts +65 -0
- package/src/tools/read.ts +40 -0
- package/src/tools/task.ts +71 -0
- package/src/tools/todo.ts +82 -0
- package/src/tools/web-fetch.ts +155 -0
- package/src/tools/web-search.ts +75 -0
- package/src/tools/write.ts +34 -0
- package/src/types.ts +145 -0
- package/tsconfig.json +17 -0
package/dist/agent.d.ts
ADDED
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>;
|