agent-accelerator 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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1220 -0
  3. package/SYSTEM_PROMPT.md +14 -0
  4. package/SYSTEM_PROMPT_AGENT.md +34 -0
  5. package/SYSTEM_PROMPT_TOOLS.md +9 -0
  6. package/bunfig.toml +2 -0
  7. package/package.json +59 -0
  8. package/src/agent/agent.ts +615 -0
  9. package/src/agent/context.ts +161 -0
  10. package/src/agent/delegation.ts +481 -0
  11. package/src/agent/loop.ts +569 -0
  12. package/src/agent/subagent.ts +83 -0
  13. package/src/ai-sdk/converters.ts +342 -0
  14. package/src/ai-sdk/errors.ts +122 -0
  15. package/src/ai-sdk/executor.ts +454 -0
  16. package/src/ai-sdk/index.ts +55 -0
  17. package/src/ai-sdk/model-provider.ts +303 -0
  18. package/src/ai-sdk/options.ts +306 -0
  19. package/src/ai-sdk/provider.ts +415 -0
  20. package/src/ai-sdk/registry.ts +416 -0
  21. package/src/data/README.md +84 -0
  22. package/src/index.ts +190 -0
  23. package/src/models/catalog-cache.ts +273 -0
  24. package/src/models/catalog.ts +503 -0
  25. package/src/streaming/event-stream.ts +211 -0
  26. package/src/streaming/sse-parser.ts +97 -0
  27. package/src/tokens/counter.ts +136 -0
  28. package/src/tools/executor.ts +365 -0
  29. package/src/tools/schema.ts +221 -0
  30. package/src/tools/tool.ts +101 -0
  31. package/src/types/agent.ts +87 -0
  32. package/src/types/core.ts +86 -0
  33. package/src/types/message.ts +115 -0
  34. package/src/types/model.ts +212 -0
  35. package/src/types/provider-payloads.ts +434 -0
  36. package/src/types/response.ts +158 -0
  37. package/src/types/tool.ts +61 -0
  38. package/src/utils/base64.ts +27 -0
  39. package/src/utils/cache.ts +146 -0
  40. package/src/utils/env.ts +78 -0
  41. package/src/utils/headers.ts +110 -0
  42. package/src/utils/media.ts +137 -0
  43. package/src/utils/serialization.ts +91 -0
  44. package/src/utils/session.ts +26 -0
  45. package/src/utils/thought-signature.ts +27 -0
  46. package/tsconfig.json +31 -0
@@ -0,0 +1,454 @@
1
+ import type { LanguageModelV4 } from "@ai-sdk/provider";
2
+ import type { ProviderContext } from "../types/message.ts";
3
+ import type { ProviderRequestOptions, ProviderGenerateResult } from "../types/model.ts";
4
+ import { AssistantMessageEventStream } from "../streaming/event-stream.ts";
5
+ import { AgentResponse } from "../types/response.ts";
6
+ import type { ToolCallRecord } from "../types/tool.ts";
7
+ import {
8
+ toAiSdkPrompt,
9
+ toAiSdkTools,
10
+ fromAiSdkGenerateResult,
11
+ extractAiSdkUsage,
12
+ } from "./converters.ts";
13
+ import { streamThoughtSignatures } from "./provider.ts";
14
+ import { buildAiSdkCallOptions, withAiSdkRetries } from "./options.ts";
15
+ import { assertModalitiesSupported } from "./errors.ts";
16
+
17
+ export interface ExecuteAiSdkOptions {
18
+ headers?: Record<string, string>;
19
+ signal?: AbortSignal;
20
+ }
21
+
22
+ function extractContextThoughtSignatures(context: ProviderContext): Record<string, string> {
23
+ const map: Record<string, string> = {};
24
+ for (const msg of context.messages) {
25
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
26
+ for (const part of msg.content) {
27
+ if (part.type === "tool_call" && (part as any).thoughtSignature) {
28
+ map[part.id] = (part as any).thoughtSignature;
29
+ map["default"] = (part as any).thoughtSignature;
30
+ }
31
+ }
32
+ }
33
+ }
34
+ return map;
35
+ }
36
+
37
+ function toProviderRawData(request?: any, response?: any): any {
38
+ return {
39
+ request: {
40
+ url: request?.url || "",
41
+ method: request?.method || "POST",
42
+ headers: request?.headers || {},
43
+ body: request?.body,
44
+ },
45
+ response: response
46
+ ? {
47
+ status: response.status || 200,
48
+ statusText: response.statusText || "OK",
49
+ headers: response.headers || {},
50
+ body: response.body,
51
+ }
52
+ : undefined,
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Executes a generate call using Vercel AI SDK's LanguageModelV4.
58
+ */
59
+ export async function executeAiSdkGenerate(
60
+ model: LanguageModelV4,
61
+ providerId: string,
62
+ modelId: string,
63
+ context: ProviderContext,
64
+ options?: ProviderRequestOptions
65
+ ): Promise<ProviderGenerateResult> {
66
+ const startTime = Date.now();
67
+ assertModalitiesSupported(context, providerId, modelId);
68
+ const prompt = await toAiSdkPrompt(context, providerId);
69
+ const tools = toAiSdkTools(options?.tools as any, providerId);
70
+ const callOptions = buildAiSdkCallOptions(providerId, options);
71
+
72
+ const sigMap = extractContextThoughtSignatures(context);
73
+
74
+ const effectiveHeaders: Record<string, string> = {
75
+ ...(callOptions.headers || {}),
76
+ ...(Object.keys(sigMap).length > 0 ? { "x-thought-signature-map": JSON.stringify(sigMap) } : {}),
77
+ ...(options?.cache?.cachedContentId ? { "x-cached-content-id": options.cache.cachedContentId } : {}),
78
+ };
79
+
80
+ const aiResult = await withAiSdkRetries(
81
+ () =>
82
+ model.doGenerate({
83
+ prompt,
84
+ tools: tools && tools.length > 0 ? tools : undefined,
85
+ headers: effectiveHeaders,
86
+ abortSignal: callOptions.abortSignal,
87
+ reasoning: callOptions.reasoning as never,
88
+ toolChoice: callOptions.toolChoice as never,
89
+ providerOptions: callOptions.providerOptions as never,
90
+ }),
91
+ { maxRetries: options?.maxRetries, maxRetryDelayMs: options?.maxRetryDelayMs, signal: options?.signal, label: { providerId, modelId } }
92
+ );
93
+
94
+ return fromAiSdkGenerateResult(
95
+ aiResult,
96
+ providerId,
97
+ modelId,
98
+ Date.now() - startTime
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Executes a streaming call using Vercel AI SDK's LanguageModelV4.
104
+ */
105
+ export function executeAiSdkStream(
106
+ model: LanguageModelV4,
107
+ providerId: string,
108
+ modelId: string,
109
+ context: ProviderContext,
110
+ options?: ProviderRequestOptions
111
+ ): AssistantMessageEventStream {
112
+ const eventStream = new AssistantMessageEventStream();
113
+ const startTime = Date.now();
114
+
115
+ // Cancellation: Vercel AI SDK supports AbortSignal on doStream for all our
116
+ // providers (OpenAI / Google / OpenAI-compatible all abort the HTTP fetch).
117
+ // Merge the user signal + stream.cancel() into one linked controller so both
118
+ // `controller.abort()` and `break` / `stream.cancel()` stop the request.
119
+ const linked = new AbortController();
120
+ const forwardUserAbort = () => {
121
+ try { linked.abort((options?.signal as any)?.reason); } catch { try { linked.abort(); } catch {} }
122
+ };
123
+ if (options?.signal?.aborted) forwardUserAbort();
124
+ else options?.signal?.addEventListener("abort", forwardUserAbort, { once: true });
125
+ const removeStreamCancel = eventStream.onCancel(() => {
126
+ try { linked.abort(); } catch {}
127
+ });
128
+
129
+ (async () => {
130
+ let reader: { cancel(): unknown; releaseLock(): void; read(): Promise<{ done: boolean; value: any }> } | null = null;
131
+ const cancelReader = () => {
132
+ try { reader?.cancel(); } catch {}
133
+ };
134
+ linked.signal.addEventListener("abort", cancelReader, { once: true });
135
+ try {
136
+ assertModalitiesSupported(context, providerId, modelId);
137
+ const prompt = await toAiSdkPrompt(context, providerId);
138
+ const tools = toAiSdkTools(options?.tools as any, providerId);
139
+ const callOptions = buildAiSdkCallOptions(providerId, { ...options, signal: linked.signal });
140
+
141
+ const sigMap = extractContextThoughtSignatures(context);
142
+ const effectiveHeaders = {
143
+ ...(callOptions.headers || {}),
144
+ ...(Object.keys(sigMap).length > 0 ? { "x-thought-signature-map": JSON.stringify(sigMap) } : {}),
145
+ ...(options?.cache?.cachedContentId ? { "x-cached-content-id": options.cache.cachedContentId } : {}),
146
+ };
147
+
148
+ const aiStreamResult = await withAiSdkRetries(
149
+ () =>
150
+ model.doStream({
151
+ prompt,
152
+ tools: tools && tools.length > 0 ? tools : undefined,
153
+ headers: effectiveHeaders,
154
+ abortSignal: callOptions.abortSignal,
155
+ reasoning: callOptions.reasoning as never,
156
+ toolChoice: callOptions.toolChoice as never,
157
+ providerOptions: callOptions.providerOptions as never,
158
+ }),
159
+ { maxRetries: options?.maxRetries, maxRetryDelayMs: options?.maxRetryDelayMs, signal: options?.signal, label: { providerId, modelId } }
160
+ );
161
+
162
+ const raw = toProviderRawData(aiStreamResult.request, aiStreamResult.response);
163
+ eventStream.push({
164
+ type: "start",
165
+ raw,
166
+ });
167
+
168
+ let accumulatedText = "";
169
+ let accumulatedThinking = "";
170
+ let reasoningSwitchedToContent = false;
171
+ let rollingReasoningTail = "";
172
+ let inContentThinking = false;
173
+ const toolCalls: ToolCallRecord[] = [];
174
+ let finalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
175
+ let finalFinishReason = "stop";
176
+ let finalResponseId: string | undefined;
177
+ let finalThoughtSignature: string | undefined;
178
+
179
+ reader = aiStreamResult.stream.getReader();
180
+ if (linked.signal.aborted || eventStream.isCancelled()) {
181
+ try { await reader.cancel(); } catch {}
182
+ throw Object.assign(new Error("Stream aborted"), { name: "AbortError" });
183
+ }
184
+
185
+ while (true) {
186
+ if (linked.signal.aborted || eventStream.isCancelled()) {
187
+ try { await reader.cancel(); } catch {}
188
+ throw Object.assign(new Error("Stream aborted"), { name: "AbortError" });
189
+ }
190
+ const { done, value } = await reader.read();
191
+ if (done) break;
192
+
193
+ switch (value.type) {
194
+ case "response-metadata": {
195
+ if (value.id) finalResponseId = value.id;
196
+ break;
197
+ }
198
+
199
+ case "reasoning-delta": {
200
+ const delta = value.delta;
201
+ if (delta) {
202
+ if (reasoningSwitchedToContent) {
203
+ accumulatedText += delta;
204
+ eventStream.push({
205
+ type: "text_delta",
206
+ delta,
207
+ partialText: accumulatedText,
208
+ });
209
+ } else {
210
+ const window = rollingReasoningTail + delta;
211
+ const thinkCloseMatch = window.match(/<\/(?:think|thought)>/i);
212
+
213
+ if (thinkCloseMatch && thinkCloseMatch.index !== undefined) {
214
+ const closeIndex = thinkCloseMatch.index;
215
+ const closeEnd = closeIndex + thinkCloseMatch[0].length;
216
+ const tailLen = rollingReasoningTail.length;
217
+
218
+ const deltaBeforeTag = delta.slice(0, Math.max(0, closeIndex - tailLen));
219
+ if (deltaBeforeTag) {
220
+ accumulatedThinking += deltaBeforeTag;
221
+ eventStream.push({
222
+ type: "thinking_delta",
223
+ thinkingDelta: deltaBeforeTag,
224
+ partialThinking: accumulatedThinking,
225
+ });
226
+ }
227
+
228
+ reasoningSwitchedToContent = true;
229
+
230
+ const deltaAfterTag = delta.slice(Math.max(0, closeEnd - tailLen));
231
+ if (deltaAfterTag) {
232
+ accumulatedText += deltaAfterTag;
233
+ eventStream.push({
234
+ type: "text_delta",
235
+ delta: deltaAfterTag,
236
+ partialText: accumulatedText,
237
+ });
238
+ }
239
+ } else if (
240
+ accumulatedText.length === 0 &&
241
+ ((accumulatedThinking.length === 0 && delta.trimStart().match(/^(?:#{1,4}\s+|\*\*(?:Final Answer|Conclusion|Executive Summary|Executive Report|Report|Summary)\*\*)/i)) ||
242
+ window.match(/(\n\s*(?:#{1,4}\s+|\*\*(?:Final Answer|Conclusion|Executive Summary|Executive Report|Report|Summary)\*\*))/i))
243
+ ) {
244
+ const headerMatch = window.match(/(\n\s*(?:#{1,4}\s+|\*\*(?:Final Answer|Conclusion|Executive Summary|Executive Report|Report|Summary)\*\*))/i);
245
+ if (headerMatch && headerMatch.index !== undefined) {
246
+ const matchStart = headerMatch.index;
247
+ const firstSymbol = headerMatch[0].search(/[#*]/);
248
+ const contentStartIndexInWindow = matchStart + (firstSymbol >= 0 ? firstSymbol : 0);
249
+ const tailLen = rollingReasoningTail.length;
250
+
251
+ const splitBeforeInDelta = Math.min(delta.length, Math.max(0, matchStart - tailLen));
252
+ const splitAfterInDelta = Math.min(delta.length, Math.max(0, contentStartIndexInWindow - tailLen));
253
+
254
+ const before = delta.slice(0, splitBeforeInDelta);
255
+ const after = delta.slice(splitAfterInDelta);
256
+
257
+ if (before) {
258
+ accumulatedThinking += before;
259
+ eventStream.push({
260
+ type: "thinking_delta",
261
+ thinkingDelta: before,
262
+ partialThinking: accumulatedThinking,
263
+ });
264
+ }
265
+ reasoningSwitchedToContent = true;
266
+ if (after) {
267
+ accumulatedText += after;
268
+ eventStream.push({
269
+ type: "text_delta",
270
+ delta: after,
271
+ partialText: accumulatedText,
272
+ });
273
+ }
274
+ } else {
275
+ reasoningSwitchedToContent = true;
276
+ accumulatedText += delta;
277
+ eventStream.push({
278
+ type: "text_delta",
279
+ delta,
280
+ partialText: accumulatedText,
281
+ });
282
+ }
283
+ } else {
284
+ accumulatedThinking += delta;
285
+ eventStream.push({
286
+ type: "thinking_delta",
287
+ thinkingDelta: delta,
288
+ partialThinking: accumulatedThinking,
289
+ });
290
+ rollingReasoningTail = (rollingReasoningTail + delta).slice(-64);
291
+ }
292
+ }
293
+ }
294
+ break;
295
+ }
296
+
297
+ case "text-delta": {
298
+ let textChunk = value.delta;
299
+ if (!inContentThinking && textChunk.includes("<think>")) {
300
+ const [before, after] = textChunk.split("<think>");
301
+ if (before) {
302
+ accumulatedText += before;
303
+ eventStream.push({ type: "text_delta", delta: before, partialText: accumulatedText });
304
+ }
305
+ inContentThinking = true;
306
+ textChunk = after || "";
307
+ }
308
+ if (inContentThinking) {
309
+ if (textChunk.includes("</think>")) {
310
+ const [thought, after] = textChunk.split("</think>");
311
+ if (thought) {
312
+ accumulatedThinking += thought;
313
+ eventStream.push({ type: "thinking_delta", thinkingDelta: thought, partialThinking: accumulatedThinking });
314
+ }
315
+ inContentThinking = false;
316
+ if (after) {
317
+ accumulatedText += after;
318
+ eventStream.push({ type: "text_delta", delta: after, partialText: accumulatedText });
319
+ }
320
+ } else if (textChunk) {
321
+ accumulatedThinking += textChunk;
322
+ eventStream.push({ type: "thinking_delta", thinkingDelta: textChunk, partialThinking: accumulatedThinking });
323
+ }
324
+ } else if (textChunk) {
325
+ accumulatedText += textChunk;
326
+ eventStream.push({
327
+ type: "text_delta",
328
+ delta: textChunk,
329
+ partialText: accumulatedText,
330
+ });
331
+ }
332
+ break;
333
+ }
334
+
335
+ case "tool-call": {
336
+ let args: Record<string, unknown> = {};
337
+ if (typeof value.input === "string") {
338
+ try {
339
+ args = JSON.parse(value.input);
340
+ } catch {
341
+ args = { raw: value.input };
342
+ }
343
+ } else if (typeof value.input === "object" && value.input !== null) {
344
+ args = value.input as Record<string, unknown>;
345
+ }
346
+
347
+ let sig =
348
+ (value as any).providerMetadata?.google?.thoughtSignature ||
349
+ (value as any).providerMetadata?.custom?.thoughtSignature;
350
+ if (!sig) {
351
+ sig =
352
+ streamThoughtSignatures.get(value.toolCallId) ||
353
+ streamThoughtSignatures.get("latest");
354
+ }
355
+
356
+ const record: ToolCallRecord = {
357
+ id: value.toolCallId,
358
+ name: value.toolName,
359
+ arguments: args,
360
+ rawArguments: typeof value.input === "string" ? value.input : JSON.stringify(value.input || {}),
361
+ thoughtSignature: typeof sig === "string" ? sig : undefined,
362
+ };
363
+
364
+ toolCalls.push(record);
365
+ if (sig && !finalThoughtSignature) finalThoughtSignature = sig;
366
+
367
+ eventStream.push({
368
+ type: "tool_call_complete",
369
+ toolCall: record,
370
+ });
371
+ break;
372
+ }
373
+
374
+ case "finish": {
375
+ if (value.finishReason) {
376
+ finalFinishReason =
377
+ typeof value.finishReason === "object"
378
+ ? value.finishReason.raw || value.finishReason.unified || "stop"
379
+ : String(value.finishReason);
380
+ }
381
+ if (value.usage) {
382
+ finalUsage = extractAiSdkUsage(value.usage);
383
+ eventStream.push({ type: "usage", usage: finalUsage });
384
+ }
385
+ break;
386
+ }
387
+
388
+ case "error": {
389
+ throw value.error;
390
+ }
391
+ }
392
+ }
393
+
394
+ if (linked.signal.aborted || eventStream.isCancelled()) {
395
+ throw Object.assign(new Error("Stream aborted"), { name: "AbortError" });
396
+ }
397
+
398
+ let finalText = accumulatedText;
399
+ let finalThinking = accumulatedThinking.length > 0 ? accumulatedThinking : undefined;
400
+ if (!finalText && toolCalls.length === 0 && accumulatedThinking) {
401
+ if (accumulatedThinking.includes("</think>")) {
402
+ const parts = accumulatedThinking.split(/<\/(?:think|thought)>/i);
403
+ finalThinking = parts[0]!.replace(/<(?:think|thought)>/i, "").trim() || undefined;
404
+ finalText = parts.slice(1).join("").trim();
405
+ } else {
406
+ finalText = accumulatedThinking;
407
+ finalThinking = undefined;
408
+ }
409
+ }
410
+
411
+ const finalAgentResponse = new AgentResponse({
412
+ text: finalText,
413
+ thinking: finalThinking,
414
+ thoughtSignature: finalThoughtSignature,
415
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
416
+ usage: finalUsage,
417
+ finishReason: finalFinishReason,
418
+ responseId: finalResponseId,
419
+ model: modelId,
420
+ provider: providerId as any,
421
+ raw: toProviderRawData(aiStreamResult.request, aiStreamResult.response),
422
+ durationMs: Date.now() - startTime,
423
+ });
424
+
425
+ eventStream.push({
426
+ type: "done",
427
+ delta: "",
428
+ usage: finalUsage,
429
+ finishReason: finalFinishReason,
430
+ responseId: finalResponseId,
431
+ });
432
+
433
+ eventStream.end(finalAgentResponse);
434
+ } catch (err: any) {
435
+ const raw = err instanceof Error ? err : new Error(String(err));
436
+ const isAbort =
437
+ linked.signal.aborted ||
438
+ eventStream.isCancelled() ||
439
+ (raw as any)?.name === "AbortError" ||
440
+ /abort|cancell?ed/i.test(String((raw as any)?.message ?? raw));
441
+ const finalErr = isAbort
442
+ ? Object.assign(raw.name === "AbortError" ? raw : new Error("Stream aborted"), { name: "AbortError" })
443
+ : raw;
444
+ eventStream.fail(finalErr);
445
+ } finally {
446
+ try { reader?.releaseLock(); } catch {}
447
+ try { options?.signal?.removeEventListener("abort", forwardUserAbort); } catch {}
448
+ try { linked.signal.removeEventListener("abort", cancelReader); } catch {}
449
+ try { removeStreamCancel(); } catch {}
450
+ }
451
+ })();
452
+
453
+ return eventStream;
454
+ }
@@ -0,0 +1,55 @@
1
+ export {
2
+ getAiSdkProvider,
3
+ getAiSdkModel,
4
+ type AiSdkModelOptions,
5
+ type AnyAiSdkProvider,
6
+ } from "./provider.ts";
7
+
8
+ export {
9
+ toAiSdkPrompt,
10
+ toAiSdkTools,
11
+ fromAiSdkGenerateResult,
12
+ extractAiSdkUsage,
13
+ } from "./converters.ts";
14
+
15
+ export {
16
+ executeAiSdkGenerate,
17
+ executeAiSdkStream,
18
+ } from "./executor.ts";
19
+
20
+ export {
21
+ AiSdkBaseProvider,
22
+ GoogleAiSdkProvider,
23
+ OpenAiAiSdkProvider,
24
+ OpenCodeAiSdkProvider,
25
+ OpenRouterAiSdkProvider,
26
+ CustomAiSdkProvider,
27
+ createGenericModelSpec,
28
+ } from "./model-provider.ts";
29
+
30
+ export {
31
+ getProvider,
32
+ resolveModel,
33
+ ModelProvider,
34
+ ensureCustomProvider,
35
+ normalizeProviderPrefix,
36
+ type ResolvedModel,
37
+ type ModelProviderConfig,
38
+ type ModelProviderInstance,
39
+ } from "./registry.ts";
40
+
41
+ export {
42
+ mapThinkingToReasoning,
43
+ resolveEffectiveThinking,
44
+ mapToolChoice,
45
+ mapThinkingToProviderOptions,
46
+ mapServiceTierToProviderOptions,
47
+ mapCacheToProviderOptions,
48
+ buildAiSdkCallOptions,
49
+ withAiSdkRetries,
50
+ isTransientAiSdkError,
51
+ type AiSdkReasoning,
52
+ type AiSdkToolChoice,
53
+ type AiSdkCallOptions,
54
+ } from "./options.ts";
55
+ export { toConciseProviderError, assertModalitiesSupported } from "./errors.ts";