agent-accelerator 0.1.0 → 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/README.md +83 -112
- package/{SYSTEM_PROMPT_AGENT.md → examples/prompts/SYSTEM_PROMPT_AGENT.md} +1 -1
- package/package.json +14 -9
- package/src/agent/agent.ts +191 -71
- package/src/agent/context.ts +1 -0
- package/src/agent/delegation.ts +61 -12
- package/src/agent/loop.ts +71 -48
- package/src/data/README.md +6 -6
- package/src/index.ts +130 -45
- package/src/models/catalog-cache.ts +60 -9
- package/src/models/catalog.ts +53 -7
- package/src/providers/google.ts +926 -0
- package/src/providers/openai-compat.ts +1147 -0
- package/src/providers/openai.ts +959 -0
- package/src/providers/openrouter-responses.ts +949 -0
- package/src/providers/openrouter.ts +1037 -0
- package/src/{ai-sdk → providers}/registry.ts +43 -55
- package/src/providers.ts +490 -0
- package/src/streaming/sse-parser.ts +6 -4
- package/src/tools/executor.ts +19 -6
- package/src/tools/schema.ts +21 -11
- package/src/types/agent.ts +8 -1
- package/src/types/core.ts +1 -1
- package/src/types/message.ts +5 -0
- package/src/types/model.ts +3 -7
- package/src/types/provider-payloads.ts +2 -84
- package/src/types/tool.ts +6 -0
- package/src/update-models.ts +56 -0
- package/src/utils/cache.ts +1 -1
- package/src/utils/documents.ts +517 -0
- package/src/utils/env.ts +0 -7
- package/src/{ai-sdk → utils}/errors.ts +61 -2
- package/src/utils/headers.ts +10 -20
- package/src/utils/media.ts +5 -2
- package/src/utils/retry.ts +89 -0
- package/src/utils/serialization.ts +15 -0
- package/src/ai-sdk/converters.ts +0 -342
- package/src/ai-sdk/executor.ts +0 -454
- package/src/ai-sdk/index.ts +0 -55
- package/src/ai-sdk/model-provider.ts +0 -303
- package/src/ai-sdk/options.ts +0 -306
- package/src/ai-sdk/provider.ts +0 -415
- package/src/tokens/counter.ts +0 -136
- /package/{SYSTEM_PROMPT.md → examples/prompts/SYSTEM_PROMPT.md} +0 -0
- /package/{SYSTEM_PROMPT_TOOLS.md → examples/prompts/SYSTEM_PROMPT_TOOLS.md} +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { toConciseProviderError } from "./errors.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks whether a provider error is a cancellation (never retried).
|
|
5
|
+
*/
|
|
6
|
+
export function isAbortError(error: unknown): boolean {
|
|
7
|
+
const err = error as { name?: unknown; message?: unknown } | null;
|
|
8
|
+
if (!err || typeof err !== "object") return false;
|
|
9
|
+
if ((err as { name?: unknown }).name === "AbortError") return true;
|
|
10
|
+
return /abort|cancell?ed/i.test(String((err as { message?: unknown }).message ?? error));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks whether a provider error is transient and safe to retry.
|
|
15
|
+
*/
|
|
16
|
+
export function isTransientError(error: unknown): boolean {
|
|
17
|
+
if (!error) return false;
|
|
18
|
+
if (isAbortError(error)) return false;
|
|
19
|
+
const err = error as Record<string, unknown>;
|
|
20
|
+
if (err["transient"] === true) return true;
|
|
21
|
+
const status = Number(
|
|
22
|
+
(err["status"] as number | undefined) ??
|
|
23
|
+
(err["statusCode"] as number | undefined) ??
|
|
24
|
+
((err["response"] as Record<string, unknown> | undefined)?.["status"] as number | undefined) ??
|
|
25
|
+
((err["lastError"] as Record<string, unknown> | undefined)?.["status"] as number | undefined) ??
|
|
26
|
+
NaN
|
|
27
|
+
);
|
|
28
|
+
if ([408, 425, 429, 500, 502, 503, 504].includes(status)) return true;
|
|
29
|
+
const code = String((err["code"] as string | undefined) ?? "").toUpperCase();
|
|
30
|
+
if (["ECONNRESET", "ECONNREFUSED", "EAI_AGAIN", "UND_ERR_CONNECT_TIMEOUT", "EPIPE", "ETIMEDOUT"].includes(code)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return /(?:timed?\s?out|temporar(?:y|ily)|connection reset|connection refused|service unavailable|rate limit|\b5\d\d\b|overloaded)/i.test(
|
|
34
|
+
String((err["message"] as string | undefined) ?? error)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
39
|
+
if (ms <= 0) return Promise.resolve();
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const timer = setTimeout(() => {
|
|
42
|
+
signal?.removeEventListener("abort", onAbort);
|
|
43
|
+
resolve();
|
|
44
|
+
}, ms);
|
|
45
|
+
const onAbort = () => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
signal?.removeEventListener("abort", onAbort);
|
|
48
|
+
reject(Object.assign(new Error("Operation aborted"), { name: "AbortError" }));
|
|
49
|
+
};
|
|
50
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
51
|
+
if (signal?.aborted) onAbort();
|
|
52
|
+
// Tag as AbortError so callers never retry it
|
|
53
|
+
// (name assigned at reject site)
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Runs a provider call with bounded retries for transient failures.
|
|
59
|
+
*/
|
|
60
|
+
export async function withRetries<T>(
|
|
61
|
+
fn: () => Promise<T> | PromiseLike<T>,
|
|
62
|
+
options?: {
|
|
63
|
+
maxRetries?: number;
|
|
64
|
+
maxRetryDelayMs?: number;
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
label?: { providerId?: string; modelId?: string };
|
|
67
|
+
}
|
|
68
|
+
): Promise<T> {
|
|
69
|
+
const maxRetries = options?.maxRetries ?? 2;
|
|
70
|
+
const maxDelay = options?.maxRetryDelayMs ?? 5000;
|
|
71
|
+
const fail = (error: unknown): never => {
|
|
72
|
+
if (options?.label?.providerId && options?.label?.modelId) {
|
|
73
|
+
throw toConciseProviderError(error, options.label.providerId, options.label.modelId);
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
};
|
|
77
|
+
let attempt = 0;
|
|
78
|
+
for (;;) {
|
|
79
|
+
try {
|
|
80
|
+
return await fn();
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (options?.signal?.aborted || isAbortError(error)) throw error;
|
|
83
|
+
if (attempt >= maxRetries || !isTransientError(error)) fail(error);
|
|
84
|
+
const delay = Math.min(maxDelay, 250 * 2 ** attempt + Math.random() * 100);
|
|
85
|
+
attempt += 1;
|
|
86
|
+
await sleep(delay, options?.signal);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -77,6 +77,21 @@ export function toJsonSafe(value: unknown, seen = new WeakSet<object>()): unknow
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Escapes arbitrary text for embedding inside XML element content or
|
|
82
|
+
* double-quoted attributes.
|
|
83
|
+
*
|
|
84
|
+
* @example `const xml = "<Document>" + escapeXml(markdown) + "</Document>";`
|
|
85
|
+
*/
|
|
86
|
+
export function escapeXml(text: string): string {
|
|
87
|
+
return text
|
|
88
|
+
.replace(/&/g, "&")
|
|
89
|
+
.replace(/</g, "<")
|
|
90
|
+
.replace(/>/g, ">")
|
|
91
|
+
.replace(/"/g, """)
|
|
92
|
+
.replace(/'/g, "'");
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
/**
|
|
81
96
|
* Safely serializes arbitrary values without throwing on circular or unsupported data.
|
|
82
97
|
*
|
package/src/ai-sdk/converters.ts
DELETED
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
LanguageModelV4Prompt,
|
|
3
|
-
LanguageModelV4FunctionTool,
|
|
4
|
-
LanguageModelV4FinishReason,
|
|
5
|
-
} from "@ai-sdk/provider";
|
|
6
|
-
import type { ProviderContext, ContentPart, Message } from "../types/message.ts";
|
|
7
|
-
import type { StandardToolDeclaration, ToolCallRecord } from "../types/tool.ts";
|
|
8
|
-
import type { ProviderGenerateResult, ProviderRequestOptions } from "../types/model.ts";
|
|
9
|
-
import type { TokenUsage } from "../types/core.ts";
|
|
10
|
-
import { normalizeMediaInput } from "../utils/media.ts";
|
|
11
|
-
import { base64ToBytes } from "../utils/base64.ts";
|
|
12
|
-
import { safeStringify } from "../utils/serialization.ts";
|
|
13
|
-
import { stripSchemaForGoogle } from "../tools/schema.ts";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Converts Agent Accelerator tools to Vercel AI SDK tools format.
|
|
17
|
-
*/
|
|
18
|
-
export function toAiSdkTools(
|
|
19
|
-
tools?: StandardToolDeclaration[],
|
|
20
|
-
providerId?: string
|
|
21
|
-
): LanguageModelV4FunctionTool[] | undefined {
|
|
22
|
-
if (!tools || tools.length === 0) return undefined;
|
|
23
|
-
const norm = providerId?.toLowerCase().trim();
|
|
24
|
-
|
|
25
|
-
return tools.map((t) => {
|
|
26
|
-
const raw = (t.parameters || { type: "object", properties: {} }) as Record<string, unknown>;
|
|
27
|
-
const inputSchema = norm === "google" || norm === "gemini" ? (stripSchemaForGoogle(raw) as any) : (raw as any);
|
|
28
|
-
return {
|
|
29
|
-
type: "function",
|
|
30
|
-
name: t.name,
|
|
31
|
-
description: t.description,
|
|
32
|
-
inputSchema,
|
|
33
|
-
...(t.strict !== undefined ? { strict: t.strict } : {}),
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function guessFilename(input: unknown, mimeType: string): string | undefined {
|
|
39
|
-
if (typeof input !== "string" || input.startsWith("data:") || input.startsWith("http")) return undefined;
|
|
40
|
-
const base = input.split(/[\\/]/).pop()?.split("?")[0]?.trim();
|
|
41
|
-
if (base && base.includes(".")) return base;
|
|
42
|
-
const ext = mimeType.split("/")[1]?.split(";")[0]?.trim();
|
|
43
|
-
return ext ? `file.${ext}` : undefined;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Providers known to accept echoed reasoning in history. Vercel serializes
|
|
48
|
-
* prompt `reasoning` parts to `reasoning_content`, which strict
|
|
49
|
-
* OpenAI-compatible endpoints (groq, ollama, …) reject with 400 — so off-list
|
|
50
|
-
* providers get history without the thinking trace (tool calls preserved).
|
|
51
|
-
*/
|
|
52
|
-
const REASONING_HISTORY_PROVIDERS = new Set([
|
|
53
|
-
"google",
|
|
54
|
-
"gemini",
|
|
55
|
-
"openai",
|
|
56
|
-
"openrouter",
|
|
57
|
-
"opencode",
|
|
58
|
-
"opencode-go",
|
|
59
|
-
]);
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Converts Agent Accelerator ProviderContext to Vercel AI SDK LanguageModelV4Prompt.
|
|
63
|
-
*/
|
|
64
|
-
export async function toAiSdkPrompt(
|
|
65
|
-
context: ProviderContext,
|
|
66
|
-
providerId?: string
|
|
67
|
-
): Promise<LanguageModelV4Prompt> {
|
|
68
|
-
const prompt: LanguageModelV4Prompt = [];
|
|
69
|
-
|
|
70
|
-
if (context.systemPrompt) {
|
|
71
|
-
prompt.push({
|
|
72
|
-
role: "system",
|
|
73
|
-
content: context.systemPrompt,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
for (const msg of context.messages) {
|
|
78
|
-
if (typeof msg.content === "string") {
|
|
79
|
-
if (msg.role === "system") {
|
|
80
|
-
prompt.push({ role: "system", content: msg.content });
|
|
81
|
-
} else if (msg.role === "assistant") {
|
|
82
|
-
prompt.push({
|
|
83
|
-
role: "assistant",
|
|
84
|
-
content: [{ type: "text", text: msg.content }],
|
|
85
|
-
});
|
|
86
|
-
} else if (msg.role === "tool") {
|
|
87
|
-
prompt.push({
|
|
88
|
-
role: "tool",
|
|
89
|
-
content: [
|
|
90
|
-
{
|
|
91
|
-
type: "tool-result",
|
|
92
|
-
toolCallId: (msg as any).tool_call_id || (msg as any).id || "call_0",
|
|
93
|
-
toolName: (msg as any).name || "unknown",
|
|
94
|
-
output: { type: "text", value: msg.content },
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
});
|
|
98
|
-
} else {
|
|
99
|
-
prompt.push({
|
|
100
|
-
role: "user",
|
|
101
|
-
content: [{ type: "text", text: msg.content }],
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
} else if (Array.isArray(msg.content)) {
|
|
105
|
-
if (msg.role === "assistant") {
|
|
106
|
-
const assistantParts: Array<any> = [];
|
|
107
|
-
for (const part of msg.content) {
|
|
108
|
-
if (part.type === "text" && part.text) {
|
|
109
|
-
assistantParts.push({ type: "text", text: part.text });
|
|
110
|
-
} else if (part.type === "thinking" && part.thinking) {
|
|
111
|
-
const keepReasoning =
|
|
112
|
-
!providerId || REASONING_HISTORY_PROVIDERS.has(providerId.toLowerCase().trim());
|
|
113
|
-
if (keepReasoning) {
|
|
114
|
-
assistantParts.push({ type: "reasoning", text: part.thinking });
|
|
115
|
-
}
|
|
116
|
-
} else if (part.type === "tool_call") {
|
|
117
|
-
assistantParts.push({
|
|
118
|
-
type: "tool-call",
|
|
119
|
-
toolCallId: part.id,
|
|
120
|
-
toolName: part.name,
|
|
121
|
-
input: part.arguments || {},
|
|
122
|
-
providerMetadata: part.thoughtSignature
|
|
123
|
-
? { google: { thoughtSignature: part.thoughtSignature } }
|
|
124
|
-
: undefined,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if (assistantParts.length > 0) {
|
|
129
|
-
prompt.push({ role: "assistant", content: assistantParts });
|
|
130
|
-
}
|
|
131
|
-
} else if (msg.role === "tool") {
|
|
132
|
-
const toolParts: Array<any> = [];
|
|
133
|
-
for (const part of msg.content) {
|
|
134
|
-
if (part.type === "tool_result") {
|
|
135
|
-
toolParts.push({
|
|
136
|
-
type: "tool-result",
|
|
137
|
-
toolCallId: part.id,
|
|
138
|
-
toolName: part.name,
|
|
139
|
-
output: {
|
|
140
|
-
type: "text",
|
|
141
|
-
value: typeof part.result === "string" ? part.result : safeStringify(part.result),
|
|
142
|
-
},
|
|
143
|
-
isError: part.isError,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (toolParts.length > 0) {
|
|
148
|
-
prompt.push({ role: "tool", content: toolParts });
|
|
149
|
-
}
|
|
150
|
-
} else {
|
|
151
|
-
const userParts: Array<any> = [];
|
|
152
|
-
for (const part of msg.content) {
|
|
153
|
-
if (part.type === "text" && part.text) {
|
|
154
|
-
userParts.push({ type: "text", text: part.text });
|
|
155
|
-
} else if (
|
|
156
|
-
part.type === "image" ||
|
|
157
|
-
part.type === "audio" ||
|
|
158
|
-
part.type === "video" ||
|
|
159
|
-
part.type === "file"
|
|
160
|
-
) {
|
|
161
|
-
// Vercel V4 has no `image` part: every media part is a `file` part
|
|
162
|
-
// with a tagged data payload. Images use image/* media types so all
|
|
163
|
-
// providers route them to their native image input.
|
|
164
|
-
const raw = (part as any).image ?? (part as any).audio ?? (part as any).video ?? (part as any).file;
|
|
165
|
-
const norm = await normalizeMediaInput(raw, (part as any).mimeType);
|
|
166
|
-
const bytes = base64ToBytes(norm.base64Data);
|
|
167
|
-
const filename =
|
|
168
|
-
(part as any).filename ?? guessFilename(raw, norm.mimeType);
|
|
169
|
-
userParts.push({
|
|
170
|
-
type: "file",
|
|
171
|
-
mediaType: norm.mimeType,
|
|
172
|
-
data: { type: "data", data: bytes },
|
|
173
|
-
...(filename ? { filename } : {}),
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (userParts.length > 0) {
|
|
178
|
-
prompt.push({ role: "user", content: userParts });
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return prompt;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Extracts normalized TokenUsage from Vercel AI SDK usage structure.
|
|
189
|
-
*/
|
|
190
|
-
export function extractAiSdkUsage(rawUsage?: any): TokenUsage {
|
|
191
|
-
if (!rawUsage) {
|
|
192
|
-
return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const input =
|
|
196
|
-
rawUsage.inputTokens?.total ??
|
|
197
|
-
rawUsage.prompt_tokens ??
|
|
198
|
-
rawUsage.input_tokens ??
|
|
199
|
-
0;
|
|
200
|
-
|
|
201
|
-
const output =
|
|
202
|
-
rawUsage.outputTokens?.total ??
|
|
203
|
-
rawUsage.completion_tokens ??
|
|
204
|
-
rawUsage.output_tokens ??
|
|
205
|
-
0;
|
|
206
|
-
|
|
207
|
-
const total =
|
|
208
|
-
rawUsage.total_tokens ??
|
|
209
|
-
rawUsage.totalTokens ??
|
|
210
|
-
input + output;
|
|
211
|
-
|
|
212
|
-
const cached =
|
|
213
|
-
rawUsage.inputTokens?.cacheRead ??
|
|
214
|
-
rawUsage.prompt_tokens_details?.cached_tokens ??
|
|
215
|
-
rawUsage.cached_tokens ??
|
|
216
|
-
0;
|
|
217
|
-
|
|
218
|
-
const cacheWrite =
|
|
219
|
-
rawUsage.inputTokens?.cacheWrite ??
|
|
220
|
-
rawUsage.prompt_tokens_details?.cache_write_tokens ??
|
|
221
|
-
rawUsage.cache_write_tokens ??
|
|
222
|
-
0;
|
|
223
|
-
|
|
224
|
-
const thinking =
|
|
225
|
-
rawUsage.outputTokens?.reasoning ??
|
|
226
|
-
rawUsage.completion_tokens_details?.reasoning_tokens ??
|
|
227
|
-
rawUsage.reasoning_tokens ??
|
|
228
|
-
0;
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
inputTokens: input,
|
|
232
|
-
outputTokens: output,
|
|
233
|
-
totalTokens: total,
|
|
234
|
-
cachedTokens: cached,
|
|
235
|
-
cacheReadTokens: cached,
|
|
236
|
-
cacheWriteTokens: cacheWrite,
|
|
237
|
-
thinkingTokens: thinking,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Converts Vercel AI SDK doGenerate result into Agent Accelerator ProviderGenerateResult.
|
|
243
|
-
*/
|
|
244
|
-
export function fromAiSdkGenerateResult(
|
|
245
|
-
aiResult: any,
|
|
246
|
-
providerId: string,
|
|
247
|
-
modelId: string,
|
|
248
|
-
durationMs: number
|
|
249
|
-
): ProviderGenerateResult {
|
|
250
|
-
let text = "";
|
|
251
|
-
let thinking: string | undefined;
|
|
252
|
-
let thoughtSignature: string | undefined;
|
|
253
|
-
const toolCalls: ToolCallRecord[] = [];
|
|
254
|
-
|
|
255
|
-
if (aiResult.content && Array.isArray(aiResult.content)) {
|
|
256
|
-
for (const part of aiResult.content) {
|
|
257
|
-
if (part.type === "text" && part.text) {
|
|
258
|
-
text += part.text;
|
|
259
|
-
} else if (part.type === "reasoning" && part.text) {
|
|
260
|
-
thinking = (thinking ? thinking + "\n" : "") + part.text;
|
|
261
|
-
const sig =
|
|
262
|
-
part.providerMetadata?.google?.thoughtSignature ||
|
|
263
|
-
part.providerMetadata?.custom?.thoughtSignature;
|
|
264
|
-
if (sig && typeof sig === "string") {
|
|
265
|
-
thoughtSignature = sig;
|
|
266
|
-
}
|
|
267
|
-
} else if (part.type === "tool-call") {
|
|
268
|
-
let args: Record<string, unknown> = {};
|
|
269
|
-
if (typeof part.input === "string") {
|
|
270
|
-
try {
|
|
271
|
-
args = JSON.parse(part.input);
|
|
272
|
-
} catch {
|
|
273
|
-
args = { raw: part.input };
|
|
274
|
-
}
|
|
275
|
-
} else if (typeof part.input === "object" && part.input !== null) {
|
|
276
|
-
args = part.input as Record<string, unknown>;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const sig =
|
|
280
|
-
part.providerMetadata?.google?.thoughtSignature ||
|
|
281
|
-
part.providerMetadata?.custom?.thoughtSignature;
|
|
282
|
-
|
|
283
|
-
toolCalls.push({
|
|
284
|
-
id: part.toolCallId || `call_${Math.random().toString(36).slice(2, 9)}`,
|
|
285
|
-
name: part.toolName,
|
|
286
|
-
arguments: args,
|
|
287
|
-
rawArguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input || {}),
|
|
288
|
-
thoughtSignature: typeof sig === "string" ? sig : undefined,
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
if (sig && !thoughtSignature) {
|
|
292
|
-
thoughtSignature = sig;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Fallback / extraction from raw response body if Vercel AI SDK parts omitted custom metadata
|
|
299
|
-
const rawBody = aiResult.response?.body;
|
|
300
|
-
if (rawBody && typeof rawBody === "object") {
|
|
301
|
-
const rawChoice = rawBody.choices?.[0];
|
|
302
|
-
const rawMsg = rawChoice?.message;
|
|
303
|
-
if (!thinking && rawMsg?.reasoning_content) {
|
|
304
|
-
thinking = rawMsg.reasoning_content;
|
|
305
|
-
}
|
|
306
|
-
if (rawMsg?.tool_calls && Array.isArray(rawMsg.tool_calls)) {
|
|
307
|
-
for (let i = 0; i < rawMsg.tool_calls.length; i++) {
|
|
308
|
-
const rawTc = rawMsg.tool_calls[i];
|
|
309
|
-
const extraSig = rawTc?.extra_content?.google?.thought_signature;
|
|
310
|
-
if (extraSig && toolCalls[i]) {
|
|
311
|
-
toolCalls[i]!.thoughtSignature = extraSig;
|
|
312
|
-
if (!thoughtSignature) thoughtSignature = extraSig;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const usage = extractAiSdkUsage(aiResult.usage ?? rawBody?.usage);
|
|
319
|
-
const finishReason =
|
|
320
|
-
typeof aiResult.finishReason === "object" && aiResult.finishReason !== null
|
|
321
|
-
? aiResult.finishReason.raw || aiResult.finishReason.unified || "stop"
|
|
322
|
-
: String(aiResult.finishReason || "stop");
|
|
323
|
-
|
|
324
|
-
const responseId = aiResult.response?.id || rawBody?.id;
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
text,
|
|
328
|
-
thinking,
|
|
329
|
-
thoughtSignature,
|
|
330
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
331
|
-
usage,
|
|
332
|
-
finishReason,
|
|
333
|
-
responseId,
|
|
334
|
-
model: modelId,
|
|
335
|
-
provider: providerId as any,
|
|
336
|
-
raw: {
|
|
337
|
-
request: aiResult.request,
|
|
338
|
-
response: aiResult.response,
|
|
339
|
-
},
|
|
340
|
-
durationMs,
|
|
341
|
-
};
|
|
342
|
-
}
|