agents 0.17.3 → 0.18.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-tool-types-CNyE1iz_.d.ts → agent-tool-types-BNUGGBzQ.d.ts} +191 -61
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-BeOheFBK.d.ts → agent-tools-BFbzVLFc.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/chat/index.d.ts +29 -9
- package/dist/chat/index.js +31 -41
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +25 -1
- package/dist/chat/react.js +249 -93
- package/dist/chat/react.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/{client-BZ-B3NhC.js → client-CcjiFpTf.js} +352 -81
- package/dist/client-CcjiFpTf.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/cloudflare-BldFV0Pa.js +117 -0
- package/dist/cloudflare-BldFV0Pa.js.map +1 -0
- package/dist/index.d.ts +13 -11
- package/dist/index.js +153 -48
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +18 -14
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/index.d.ts +38 -30
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/ai/index.d.ts +155 -0
- package/dist/observability/ai/index.js +1845 -0
- package/dist/observability/ai/index.js.map +1 -0
- package/dist/react.d.ts +1 -1
- package/dist/{retries-CvHJwSuh.d.ts → retries-CAvxtG9d.d.ts} +16 -7
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +20 -2
- package/dist/retries.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sub-routing.d.ts +6 -6
- package/dist/vite.d.ts +4 -4
- package/dist/vite.js +4 -2
- package/dist/vite.js.map +1 -1
- package/dist/{wire-types-nflOzNuU.js → wire-types-CU9rLoeS.js} +33 -2
- package/dist/wire-types-CU9rLoeS.js.map +1 -0
- package/dist/workflows.d.ts +1 -1
- package/docs/agent-class.md +9 -1
- package/docs/configuration.md +20 -0
- package/docs/mcp-client.md +52 -4
- package/docs/observability.md +282 -0
- package/package.json +7 -2
- package/dist/client-BZ-B3NhC.js.map +0 -1
- package/dist/wire-types-nflOzNuU.js.map +0 -1
|
@@ -0,0 +1,1845 @@
|
|
|
1
|
+
import { n as writeSpanAttributes, t as tracer } from "../../cloudflare-BldFV0Pa.js";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
//#region src/observability/ai/read.ts
|
|
4
|
+
/** Narrows an unknown value to a string. */
|
|
5
|
+
function readString(value) {
|
|
6
|
+
return typeof value === "string" ? value : void 0;
|
|
7
|
+
}
|
|
8
|
+
/** Narrows an unknown value to a number. */
|
|
9
|
+
function readNumber(value) {
|
|
10
|
+
return typeof value === "number" ? value : void 0;
|
|
11
|
+
}
|
|
12
|
+
/** AI SDK token counts are either a plain number or `{ total?: number, ... }`. */
|
|
13
|
+
function readTokenCount(value) {
|
|
14
|
+
if (typeof value === "number") return value;
|
|
15
|
+
if (typeof value === "object" && value !== null) {
|
|
16
|
+
const total = value.total;
|
|
17
|
+
return typeof total === "number" ? total : void 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Reads a numeric sub-field from a nested AI SDK token count object. */
|
|
21
|
+
function readNestedTokenField(value, key) {
|
|
22
|
+
if (typeof value !== "object" || value === null) return;
|
|
23
|
+
const nested = value[key];
|
|
24
|
+
return typeof nested === "number" ? nested : void 0;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/observability/genai/attributes.ts
|
|
28
|
+
/**
|
|
29
|
+
* Local trace attribute keys used by the Cloudflare GenAI projection.
|
|
30
|
+
*
|
|
31
|
+
* `gen_ai.*` keys follow OpenTelemetry GenAI semantic conventions where they
|
|
32
|
+
* exist (Development status — tracked so spec churn stays an internal edit;
|
|
33
|
+
* none of these constants are exported from the package). Keys with no
|
|
34
|
+
* semconv home live under the `cloudflare.agents.*` vendor namespace — never
|
|
35
|
+
* bare top-level keys, never `ai.*` (the Vercel AI SDK's de-facto namespace).
|
|
36
|
+
*/
|
|
37
|
+
const TraceAttribute = {
|
|
38
|
+
Cloudflare: {
|
|
39
|
+
AIGatewayLogID: "cloudflare.ai_gateway.log.id",
|
|
40
|
+
CallID: "cloudflare.agents.call.id",
|
|
41
|
+
IntegrationName: "cloudflare.agents.integration.name",
|
|
42
|
+
MetadataPrefix: "cloudflare.agents.metadata.",
|
|
43
|
+
OperationName: "cloudflare.agents.operation.name",
|
|
44
|
+
ResponseFinishReason: "cloudflare.agents.response.finish_reason",
|
|
45
|
+
ToolApprovalState: "cloudflare.agents.tool.approval.state",
|
|
46
|
+
ToolCount: "cloudflare.agents.tool.count",
|
|
47
|
+
TurnAdmission: "cloudflare.agents.turn.admission",
|
|
48
|
+
TurnChannel: "cloudflare.agents.turn.channel",
|
|
49
|
+
TurnContinuation: "cloudflare.agents.turn.continuation",
|
|
50
|
+
TurnGeneration: "cloudflare.agents.turn.generation",
|
|
51
|
+
TurnRequestID: "cloudflare.agents.turn.request_id",
|
|
52
|
+
TurnTrigger: "cloudflare.agents.turn.trigger",
|
|
53
|
+
UsageTotalTokens: "cloudflare.agents.usage.total_tokens"
|
|
54
|
+
},
|
|
55
|
+
General: { UserID: "user.id" },
|
|
56
|
+
GenAI: {
|
|
57
|
+
AgentID: "gen_ai.agent.id",
|
|
58
|
+
AgentName: "gen_ai.agent.name",
|
|
59
|
+
AgentVersion: "gen_ai.agent.version",
|
|
60
|
+
ConversationID: "gen_ai.conversation.id",
|
|
61
|
+
InputMessages: "gen_ai.input.messages",
|
|
62
|
+
OperationName: "gen_ai.operation.name",
|
|
63
|
+
OperationNameValueChat: "chat",
|
|
64
|
+
OperationNameValueExecuteTool: "execute_tool",
|
|
65
|
+
OperationNameValueInvokeAgent: "invoke_agent",
|
|
66
|
+
OutputMessages: "gen_ai.output.messages",
|
|
67
|
+
OutputType: "gen_ai.output.type",
|
|
68
|
+
ProviderName: "gen_ai.provider.name",
|
|
69
|
+
RequestFrequencyPenalty: "gen_ai.request.frequency_penalty",
|
|
70
|
+
RequestMaxTokens: "gen_ai.request.max_tokens",
|
|
71
|
+
RequestModel: "gen_ai.request.model",
|
|
72
|
+
RequestPresencePenalty: "gen_ai.request.presence_penalty",
|
|
73
|
+
RequestSeed: "gen_ai.request.seed",
|
|
74
|
+
RequestStream: "gen_ai.request.stream",
|
|
75
|
+
RequestTemperature: "gen_ai.request.temperature",
|
|
76
|
+
RequestTopK: "gen_ai.request.top_k",
|
|
77
|
+
RequestTopP: "gen_ai.request.top_p",
|
|
78
|
+
ResponseID: "gen_ai.response.id",
|
|
79
|
+
ResponseModel: "gen_ai.response.model",
|
|
80
|
+
ResponseTimeToFirstChunk: "gen_ai.response.time_to_first_chunk",
|
|
81
|
+
ToolCallArguments: "gen_ai.tool.call.arguments",
|
|
82
|
+
ToolCallID: "gen_ai.tool.call.id",
|
|
83
|
+
ToolCallResult: "gen_ai.tool.call.result",
|
|
84
|
+
ToolName: "gen_ai.tool.name",
|
|
85
|
+
ToolType: "gen_ai.tool.type",
|
|
86
|
+
UsageCacheCreationInputTokens: "gen_ai.usage.cache_creation.input_tokens",
|
|
87
|
+
UsageCacheReadInputTokens: "gen_ai.usage.cache_read.input_tokens",
|
|
88
|
+
UsageInputTokens: "gen_ai.usage.input_tokens",
|
|
89
|
+
UsageOutputTokens: "gen_ai.usage.output_tokens",
|
|
90
|
+
UsageReasoningOutputTokens: "gen_ai.usage.reasoning.output_tokens"
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/observability/genai/telemetry.ts
|
|
95
|
+
/**
|
|
96
|
+
* Builds a semconv-formula span name (`"{operation} {target}"`), falling back
|
|
97
|
+
* to the bare operation when the target is unavailable or the combined name
|
|
98
|
+
* exceeds the Workers Observability 64 UTF-8-byte budget. The full target
|
|
99
|
+
* remains available as an attribute; the stable query key is always
|
|
100
|
+
* `gen_ai.operation.name`, never the span name.
|
|
101
|
+
*/
|
|
102
|
+
function spanName(operation, target) {
|
|
103
|
+
if (!target) return operation;
|
|
104
|
+
const name = `${operation} ${target}`;
|
|
105
|
+
return new TextEncoder().encode(name).length <= 64 ? name : operation;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Normalizes an AI SDK provider identifier to the semconv
|
|
109
|
+
* `gen_ai.provider.name` enum where a member exists: sub-provider suffixes
|
|
110
|
+
* are stripped (`anthropic.messages` → `anthropic`) and known aliases mapped.
|
|
111
|
+
* Unknown providers pass through verbatim (semconv sanctions custom values).
|
|
112
|
+
*/
|
|
113
|
+
function normalizeProviderName(provider) {
|
|
114
|
+
if (provider === void 0) return;
|
|
115
|
+
const lower = provider.toLowerCase();
|
|
116
|
+
for (const [prefix, value] of [
|
|
117
|
+
["google.vertex", "gcp.vertex_ai"],
|
|
118
|
+
["google.generative-ai", "gcp.gemini"],
|
|
119
|
+
["google-vertex", "gcp.vertex_ai"],
|
|
120
|
+
["amazon-bedrock", "aws.bedrock"],
|
|
121
|
+
["azure-openai", "azure.ai.openai"],
|
|
122
|
+
["anthropic", "anthropic"],
|
|
123
|
+
["openai", "openai"],
|
|
124
|
+
["azure", "azure.ai.inference"],
|
|
125
|
+
["google", "gcp.gemini"],
|
|
126
|
+
["mistral", "mistral_ai"],
|
|
127
|
+
["cohere", "cohere"],
|
|
128
|
+
["bedrock", "aws.bedrock"],
|
|
129
|
+
["groq", "groq"],
|
|
130
|
+
["deepseek", "deepseek"],
|
|
131
|
+
["perplexity", "perplexity"],
|
|
132
|
+
["xai", "x_ai"]
|
|
133
|
+
]) if (lower === prefix || lower.startsWith(`${prefix}.`) || lower.startsWith(`${prefix}-`)) return value;
|
|
134
|
+
return provider;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Reserved telemetry-metadata keys that map to dedicated attributes on the
|
|
138
|
+
* operation root span. Everything else scalar passes through under
|
|
139
|
+
* `cloudflare.agents.metadata.{key}`; identity keys are consumed by
|
|
140
|
+
* SemanticContext extraction and never passed through.
|
|
141
|
+
*/
|
|
142
|
+
const RESERVED_METADATA_ATTRIBUTES = {
|
|
143
|
+
[TraceAttribute.Cloudflare.TurnAdmission]: TraceAttribute.Cloudflare.TurnAdmission,
|
|
144
|
+
[TraceAttribute.Cloudflare.TurnChannel]: TraceAttribute.Cloudflare.TurnChannel,
|
|
145
|
+
[TraceAttribute.Cloudflare.TurnContinuation]: TraceAttribute.Cloudflare.TurnContinuation,
|
|
146
|
+
[TraceAttribute.Cloudflare.TurnGeneration]: TraceAttribute.Cloudflare.TurnGeneration,
|
|
147
|
+
[TraceAttribute.Cloudflare.TurnRequestID]: TraceAttribute.Cloudflare.TurnRequestID,
|
|
148
|
+
[TraceAttribute.Cloudflare.TurnTrigger]: TraceAttribute.Cloudflare.TurnTrigger,
|
|
149
|
+
[TraceAttribute.General.UserID]: TraceAttribute.General.UserID
|
|
150
|
+
};
|
|
151
|
+
const CONSUMED_METADATA_KEYS = /* @__PURE__ */ new Set([
|
|
152
|
+
"agentId",
|
|
153
|
+
"agentName",
|
|
154
|
+
"agentVersion",
|
|
155
|
+
"conversationId",
|
|
156
|
+
"gen_ai.agent.id",
|
|
157
|
+
"gen_ai.agent.name",
|
|
158
|
+
"gen_ai.agent.version",
|
|
159
|
+
"gen_ai.conversation.id"
|
|
160
|
+
]);
|
|
161
|
+
/**
|
|
162
|
+
* Projects the AI SDK's per-call `experimental_telemetry.metadata` onto root
|
|
163
|
+
* span attributes: reserved keys map to their dedicated attributes, any other
|
|
164
|
+
* SCALAR entry passes through as `cloudflare.agents.metadata.{key}`, and
|
|
165
|
+
* object/array values are dropped (scalar-only attribute rule).
|
|
166
|
+
*/
|
|
167
|
+
function metadataAttributes(metadata) {
|
|
168
|
+
if (metadata === void 0) return {};
|
|
169
|
+
const attributes = {};
|
|
170
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
171
|
+
if (CONSUMED_METADATA_KEYS.has(key)) continue;
|
|
172
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") continue;
|
|
173
|
+
const reserved = Object.hasOwn(RESERVED_METADATA_ATTRIBUTES, key) ? RESERVED_METADATA_ATTRIBUTES[key] : void 0;
|
|
174
|
+
attributes[reserved ?? `${TraceAttribute.Cloudflare.MetadataPrefix}${key}`] = value;
|
|
175
|
+
}
|
|
176
|
+
return attributes;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Cheap root-span name for an agent operation: needs only the agent name (the
|
|
180
|
+
* one value instrumentation may read before the sampling check), never the
|
|
181
|
+
* full attribute spec.
|
|
182
|
+
*/
|
|
183
|
+
function operationSpanName(agentName) {
|
|
184
|
+
return spanName(TraceAttribute.GenAI.OperationNameValueInvokeAgent, agentName);
|
|
185
|
+
}
|
|
186
|
+
/** Builds the root span for an SDK operation such as generateText or streamText. */
|
|
187
|
+
function operationSpan(input) {
|
|
188
|
+
return {
|
|
189
|
+
attributes: {
|
|
190
|
+
...input.attributes,
|
|
191
|
+
[TraceAttribute.Cloudflare.IntegrationName]: input.integration,
|
|
192
|
+
[TraceAttribute.Cloudflare.OperationName]: input.operation,
|
|
193
|
+
[TraceAttribute.GenAI.AgentID]: input.context?.agentId,
|
|
194
|
+
[TraceAttribute.GenAI.AgentName]: input.context?.agentName,
|
|
195
|
+
[TraceAttribute.GenAI.AgentVersion]: input.context?.agentVersion,
|
|
196
|
+
[TraceAttribute.GenAI.ConversationID]: input.context?.conversationId,
|
|
197
|
+
[TraceAttribute.GenAI.OperationName]: TraceAttribute.GenAI.OperationNameValueInvokeAgent,
|
|
198
|
+
[TraceAttribute.GenAI.ProviderName]: normalizeProviderName(input.provider),
|
|
199
|
+
...requestAttributes(input.request, input.model)
|
|
200
|
+
},
|
|
201
|
+
name: spanName(TraceAttribute.GenAI.OperationNameValueInvokeAgent, input.context?.agentName)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** Builds the child span for an underlying model call. */
|
|
205
|
+
function modelCallSpan(input) {
|
|
206
|
+
return {
|
|
207
|
+
attributes: {
|
|
208
|
+
...input.attributes,
|
|
209
|
+
[TraceAttribute.Cloudflare.IntegrationName]: input.integration,
|
|
210
|
+
[TraceAttribute.Cloudflare.OperationName]: input.operation,
|
|
211
|
+
[TraceAttribute.GenAI.OperationName]: TraceAttribute.GenAI.OperationNameValueChat,
|
|
212
|
+
[TraceAttribute.GenAI.ProviderName]: normalizeProviderName(input.provider),
|
|
213
|
+
...requestAttributes(input.request, input.model)
|
|
214
|
+
},
|
|
215
|
+
name: spanName(TraceAttribute.GenAI.OperationNameValueChat, input.model)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function requestAttributes(request, model) {
|
|
219
|
+
return {
|
|
220
|
+
[TraceAttribute.GenAI.OutputType]: request?.outputType,
|
|
221
|
+
[TraceAttribute.GenAI.RequestFrequencyPenalty]: request?.frequencyPenalty,
|
|
222
|
+
[TraceAttribute.GenAI.RequestMaxTokens]: request?.maxTokens,
|
|
223
|
+
[TraceAttribute.GenAI.RequestModel]: model,
|
|
224
|
+
[TraceAttribute.GenAI.RequestPresencePenalty]: request?.presencePenalty,
|
|
225
|
+
[TraceAttribute.GenAI.RequestSeed]: request?.seed,
|
|
226
|
+
[TraceAttribute.GenAI.RequestStream]: request?.stream === true ? true : void 0,
|
|
227
|
+
[TraceAttribute.GenAI.RequestTemperature]: request?.temperature,
|
|
228
|
+
[TraceAttribute.GenAI.RequestTopK]: request?.topK,
|
|
229
|
+
[TraceAttribute.GenAI.RequestTopP]: request?.topP
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/** Builds the child span for a tool execution. */
|
|
233
|
+
function toolCallSpan(input) {
|
|
234
|
+
return {
|
|
235
|
+
attributes: {
|
|
236
|
+
[TraceAttribute.Cloudflare.IntegrationName]: input.integration,
|
|
237
|
+
[TraceAttribute.Cloudflare.OperationName]: input.operation,
|
|
238
|
+
[TraceAttribute.GenAI.OperationName]: TraceAttribute.GenAI.OperationNameValueExecuteTool,
|
|
239
|
+
[TraceAttribute.GenAI.ToolCallID]: input.toolCallId,
|
|
240
|
+
[TraceAttribute.GenAI.ToolName]: input.toolName,
|
|
241
|
+
[TraceAttribute.GenAI.ToolType]: "function"
|
|
242
|
+
},
|
|
243
|
+
name: spanName(TraceAttribute.GenAI.OperationNameValueExecuteTool, input.toolName)
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** Builds a bounded child span for one tool-approval lifecycle segment. */
|
|
247
|
+
function toolApprovalSpan(input) {
|
|
248
|
+
return {
|
|
249
|
+
attributes: {
|
|
250
|
+
[TraceAttribute.Cloudflare.IntegrationName]: "ai-sdk",
|
|
251
|
+
[TraceAttribute.Cloudflare.OperationName]: "tool.approval",
|
|
252
|
+
[TraceAttribute.Cloudflare.ToolApprovalState]: input.state,
|
|
253
|
+
[TraceAttribute.GenAI.OperationName]: TraceAttribute.GenAI.OperationNameValueExecuteTool,
|
|
254
|
+
[TraceAttribute.GenAI.ToolCallID]: input.toolCallId,
|
|
255
|
+
[TraceAttribute.GenAI.ToolName]: input.toolName,
|
|
256
|
+
[TraceAttribute.GenAI.ToolType]: "function"
|
|
257
|
+
},
|
|
258
|
+
name: spanName("tool_approval", input.toolName)
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/** Projects a completed model operation into canonical finish attributes. */
|
|
262
|
+
function finishAttributes(input) {
|
|
263
|
+
return {
|
|
264
|
+
[TraceAttribute.Cloudflare.AIGatewayLogID]: input.aiGatewayLogId,
|
|
265
|
+
[TraceAttribute.Cloudflare.ResponseFinishReason]: input.finishReason,
|
|
266
|
+
[TraceAttribute.Cloudflare.ToolCount]: input.toolCallCount,
|
|
267
|
+
[TraceAttribute.Cloudflare.UsageTotalTokens]: totalTokens(input.usage),
|
|
268
|
+
[TraceAttribute.GenAI.ResponseID]: input.response?.id,
|
|
269
|
+
[TraceAttribute.GenAI.ResponseModel]: input.response?.model,
|
|
270
|
+
[TraceAttribute.GenAI.ResponseTimeToFirstChunk]: input.timeToFirstChunkSeconds,
|
|
271
|
+
[TraceAttribute.GenAI.UsageCacheCreationInputTokens]: input.usage?.cacheCreationInputTokens,
|
|
272
|
+
[TraceAttribute.GenAI.UsageCacheReadInputTokens]: input.usage?.cacheReadInputTokens,
|
|
273
|
+
[TraceAttribute.GenAI.UsageInputTokens]: input.usage?.inputTokens,
|
|
274
|
+
[TraceAttribute.GenAI.UsageOutputTokens]: input.usage?.outputTokens,
|
|
275
|
+
[TraceAttribute.GenAI.UsageReasoningOutputTokens]: input.usage?.reasoningTokens
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/** Attribute projection for an AI Gateway log reference discovered on error. */
|
|
279
|
+
function aiGatewayLogAttributes(aiGatewayLogId) {
|
|
280
|
+
return { [TraceAttribute.Cloudflare.AIGatewayLogID]: aiGatewayLogId };
|
|
281
|
+
}
|
|
282
|
+
function totalTokens(usage) {
|
|
283
|
+
if (usage?.totalTokens !== void 0) return usage.totalTokens;
|
|
284
|
+
return usage?.inputTokens !== void 0 && usage.outputTokens !== void 0 ? usage.inputTokens + usage.outputTokens : void 0;
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/observability/ai/v6/extract.ts
|
|
288
|
+
function finishAttributesFromResult(result, options = {}) {
|
|
289
|
+
return finishAttributes({
|
|
290
|
+
aiGatewayLogId: options.aiGatewayLogId,
|
|
291
|
+
finishReason: extractFinishReason$1(result),
|
|
292
|
+
response: options.includeResponse ? extractResponseInfo(result) : void 0,
|
|
293
|
+
toolCallCount: extractToolCallCount(result),
|
|
294
|
+
usage: extractAISDKv6TokenUsage(result)
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
function extractRequestSummary(params, operation) {
|
|
298
|
+
return {
|
|
299
|
+
frequencyPenalty: readNumber(params.frequencyPenalty),
|
|
300
|
+
maxTokens: readNumber(params.maxOutputTokens ?? params.maxTokens),
|
|
301
|
+
outputType: operation === "generateObject" || operation === "streamObject" ? "json" : "text",
|
|
302
|
+
presencePenalty: readNumber(params.presencePenalty),
|
|
303
|
+
seed: readNumber(params.seed),
|
|
304
|
+
stream: operation === "streamText" || operation === "streamObject",
|
|
305
|
+
temperature: readNumber(params.temperature),
|
|
306
|
+
topK: readNumber(params.topK),
|
|
307
|
+
topP: readNumber(params.topP)
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/** Extracts model identity from an AI SDK v6 model object. */
|
|
311
|
+
function extractModelInfo(value) {
|
|
312
|
+
if (typeof value === "string") return value.length > 0 ? {
|
|
313
|
+
modelId: value,
|
|
314
|
+
provider: void 0
|
|
315
|
+
} : void 0;
|
|
316
|
+
if (typeof value !== "object" || value === null) return;
|
|
317
|
+
const record = value;
|
|
318
|
+
const modelId = typeof record.modelId === "string" ? record.modelId : void 0;
|
|
319
|
+
const provider = typeof record.provider === "string" ? record.provider : void 0;
|
|
320
|
+
if (modelId === void 0 && provider === void 0) return;
|
|
321
|
+
return {
|
|
322
|
+
modelId,
|
|
323
|
+
provider
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Extracts token usage from an AI SDK v6 result or stream chunk.
|
|
328
|
+
*
|
|
329
|
+
* AI SDK v6 exposes usage as `{ inputTokens, outputTokens, totalTokens }`
|
|
330
|
+
* where `inputTokens`/`outputTokens` may be plain numbers or nested objects
|
|
331
|
+
* like `{ total, cacheRead, cacheWrite }` / `{ total, reasoning }`.
|
|
332
|
+
*/
|
|
333
|
+
function extractAISDKv6TokenUsage(value) {
|
|
334
|
+
if (typeof value !== "object" || value === null) return;
|
|
335
|
+
const record = value;
|
|
336
|
+
const raw = record.totalUsage ?? record.usage;
|
|
337
|
+
if (typeof raw !== "object" || raw === null) return;
|
|
338
|
+
const usage = raw;
|
|
339
|
+
const inputTokens = readTokenCount(usage.inputTokens);
|
|
340
|
+
const outputTokens = readTokenCount(usage.outputTokens);
|
|
341
|
+
const totalTokens = readNumber(usage.totalTokens);
|
|
342
|
+
const cacheReadInputTokens = readNestedTokenField(usage.inputTokenDetails, "cacheReadTokens") ?? readNestedTokenField(usage.inputTokens, "cacheRead") ?? readNumber(usage.cachedInputTokens);
|
|
343
|
+
const cacheCreationInputTokens = readNestedTokenField(usage.inputTokenDetails, "cacheWriteTokens") ?? readNestedTokenField(usage.inputTokens, "cacheWrite");
|
|
344
|
+
const reasoningTokens = readNestedTokenField(usage.outputTokenDetails, "reasoningTokens") ?? readNestedTokenField(usage.outputTokens, "reasoning") ?? readNumber(usage.reasoningTokens);
|
|
345
|
+
if (inputTokens === void 0 && outputTokens === void 0 && totalTokens === void 0 && cacheReadInputTokens === void 0 && cacheCreationInputTokens === void 0 && reasoningTokens === void 0) return;
|
|
346
|
+
return {
|
|
347
|
+
...cacheCreationInputTokens !== void 0 ? { cacheCreationInputTokens } : {},
|
|
348
|
+
...cacheReadInputTokens !== void 0 ? { cacheReadInputTokens } : {},
|
|
349
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
350
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
351
|
+
...reasoningTokens !== void 0 ? { reasoningTokens } : {},
|
|
352
|
+
...totalTokens !== void 0 ? { totalTokens } : {}
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function extractToolCallCount(value) {
|
|
356
|
+
if (typeof value !== "object" || value === null) return;
|
|
357
|
+
const toolCalls = value.toolCalls;
|
|
358
|
+
return Array.isArray(toolCalls) && toolCalls.length > 0 ? toolCalls.length : void 0;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Reads a finish reason from an AI SDK v6 result or `finish`-type stream chunk.
|
|
362
|
+
*/
|
|
363
|
+
function extractFinishReason$1(value) {
|
|
364
|
+
if (typeof value !== "object" || value === null) return;
|
|
365
|
+
const finishReason = value.finishReason;
|
|
366
|
+
if (typeof finishReason === "string") return finishReason;
|
|
367
|
+
if (typeof finishReason === "object" && finishReason !== null) {
|
|
368
|
+
const unified = finishReason.unified;
|
|
369
|
+
return typeof unified === "string" ? unified : void 0;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function extractResponseInfo(value) {
|
|
373
|
+
if (typeof value !== "object" || value === null) return;
|
|
374
|
+
const record = value;
|
|
375
|
+
if (record.type === "response-metadata") {
|
|
376
|
+
const id = readString(record.id);
|
|
377
|
+
const model = readString(record.modelId);
|
|
378
|
+
if (id === void 0 && model === void 0) return;
|
|
379
|
+
return {
|
|
380
|
+
...id !== void 0 ? { id } : {},
|
|
381
|
+
...model !== void 0 ? { model } : {}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const response = typeof record.response === "object" && record.response !== null ? record.response : void 0;
|
|
385
|
+
const id = readString(record.responseId ?? response?.id);
|
|
386
|
+
const model = readString(record.responseModel ?? response?.modelId ?? response?.model);
|
|
387
|
+
if (id === void 0 && model === void 0) return;
|
|
388
|
+
return {
|
|
389
|
+
...id !== void 0 ? { id } : {},
|
|
390
|
+
...model !== void 0 ? { model } : {}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region src/observability/ai/ai-gateway.ts
|
|
395
|
+
const MAX_AI_GATEWAY_LOG_ID_BYTES = 256;
|
|
396
|
+
const AI_GATEWAY_CONTAINER_KEYS = /* @__PURE__ */ new Set([
|
|
397
|
+
"aigateway",
|
|
398
|
+
"binding",
|
|
399
|
+
"cause",
|
|
400
|
+
"cloudflare",
|
|
401
|
+
"config",
|
|
402
|
+
"context",
|
|
403
|
+
"error",
|
|
404
|
+
"gateway",
|
|
405
|
+
"providermetadata",
|
|
406
|
+
"rawresponse",
|
|
407
|
+
"response",
|
|
408
|
+
"workersai"
|
|
409
|
+
]);
|
|
410
|
+
/**
|
|
411
|
+
* Reads an AI Gateway log id from explicit provider surfaces only: response
|
|
412
|
+
* headers, provider metadata, gateway errors, or a Workers AI binding. The
|
|
413
|
+
* walk is bounded and uses data-property descriptors, so telemetry cannot get
|
|
414
|
+
* stuck on cycles or invoke arbitrary application getters. Unknown shapes fail
|
|
415
|
+
* open and simply omit the attribute.
|
|
416
|
+
*/
|
|
417
|
+
function extractAIGatewayLogId(value) {
|
|
418
|
+
const seen = /* @__PURE__ */ new Set();
|
|
419
|
+
let visited = 0;
|
|
420
|
+
const visit = (candidate, depth, gatewayScoped, providerMetadata, responseScoped) => {
|
|
421
|
+
if (candidate === null || candidate === void 0 || depth > 6 || typeof candidate !== "object" && typeof candidate !== "function" || seen.has(candidate) || visited >= 200) return;
|
|
422
|
+
seen.add(candidate);
|
|
423
|
+
visited += 1;
|
|
424
|
+
if (typeof Response !== "undefined" && candidate instanceof Response) return readHeaderLogId(candidate.headers);
|
|
425
|
+
let descriptors;
|
|
426
|
+
try {
|
|
427
|
+
descriptors = Object.getOwnPropertyDescriptors(candidate);
|
|
428
|
+
} catch {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const objectName = dataString(descriptors.name);
|
|
432
|
+
const scopedHere = gatewayScoped || objectName !== void 0 && isGatewayKey(normalizeKey(objectName));
|
|
433
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
434
|
+
if (!("value" in descriptor)) continue;
|
|
435
|
+
const normalizedKey = normalizeKey(key);
|
|
436
|
+
if (normalizedKey === "cfaiglogid" || normalizedKey === "aigatewaylogid") {
|
|
437
|
+
const logId = boundedAIGatewayLogId(descriptor.value);
|
|
438
|
+
if (logId !== void 0) return logId;
|
|
439
|
+
}
|
|
440
|
+
if (normalizedKey === "responseheaders" || normalizedKey === "headers" && responseScoped) {
|
|
441
|
+
const logId = readHeaderLogId(descriptor.value);
|
|
442
|
+
if (logId !== void 0) return logId;
|
|
443
|
+
}
|
|
444
|
+
if (normalizedKey === "logid" && scopedHere) {
|
|
445
|
+
const logId = boundedAIGatewayLogId(descriptor.value);
|
|
446
|
+
if (logId !== void 0) return logId;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
450
|
+
if (!("value" in descriptor)) continue;
|
|
451
|
+
const normalizedKey = normalizeKey(key);
|
|
452
|
+
if (!providerMetadata && !AI_GATEWAY_CONTAINER_KEYS.has(normalizedKey)) continue;
|
|
453
|
+
const nested = visit(descriptor.value, depth + 1, scopedHere || isGatewayKey(normalizedKey), providerMetadata || normalizedKey === "providermetadata", normalizedKey === "response" || normalizedKey === "rawresponse");
|
|
454
|
+
if (nested !== void 0) return nested;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
return visit(value, 0, false, false, false);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* workers-ai-provider currently exposes the gateway ID on its `Ai` binding,
|
|
461
|
+
* not in the LanguageModel result. Clone only that known runtime shape and
|
|
462
|
+
* proxy `binding.run` so the ID is captured as that call settles instead of
|
|
463
|
+
* reading the binding's mutable latest value later when the span ends.
|
|
464
|
+
*/
|
|
465
|
+
function captureAIGatewayLogFromModel(model, provider) {
|
|
466
|
+
let logId;
|
|
467
|
+
const capture = {
|
|
468
|
+
model,
|
|
469
|
+
get: () => logId,
|
|
470
|
+
reset: () => {
|
|
471
|
+
logId = void 0;
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
if (!provider?.toLowerCase().startsWith("workersai")) return capture;
|
|
475
|
+
try {
|
|
476
|
+
const modelDescriptors = Object.getOwnPropertyDescriptors(model);
|
|
477
|
+
const configDescriptor = modelDescriptors.config;
|
|
478
|
+
if (!configDescriptor || !("value" in configDescriptor)) return capture;
|
|
479
|
+
const config = configDescriptor.value;
|
|
480
|
+
if (typeof config !== "object" || config === null) return capture;
|
|
481
|
+
const configDescriptors = Object.getOwnPropertyDescriptors(config);
|
|
482
|
+
const bindingDescriptor = configDescriptors.binding;
|
|
483
|
+
if (!bindingDescriptor || !("value" in bindingDescriptor)) return capture;
|
|
484
|
+
const binding = bindingDescriptor.value;
|
|
485
|
+
if (typeof binding !== "object" || binding === null || !("aiGatewayLogId" in binding) || typeof binding.run !== "function") return capture;
|
|
486
|
+
const bindingProxy = new Proxy(binding, { get(target, property, receiver) {
|
|
487
|
+
if (property !== "run") return Reflect.get(target, property, receiver);
|
|
488
|
+
return (...args) => {
|
|
489
|
+
const run = Reflect.get(target, property, target);
|
|
490
|
+
const previousLogId = extractAIGatewayLogId(target);
|
|
491
|
+
logId = void 0;
|
|
492
|
+
const captureResult = (result) => {
|
|
493
|
+
const resultLogId = extractAIGatewayLogId(result);
|
|
494
|
+
const currentLogId = extractAIGatewayLogId(target);
|
|
495
|
+
logId = resultLogId ?? (currentLogId !== previousLogId ? currentLogId : void 0);
|
|
496
|
+
};
|
|
497
|
+
try {
|
|
498
|
+
return Promise.resolve(Reflect.apply(run, target, args)).then((result) => {
|
|
499
|
+
captureResult(result);
|
|
500
|
+
return result;
|
|
501
|
+
}, (cause) => {
|
|
502
|
+
captureResult(cause);
|
|
503
|
+
throw cause;
|
|
504
|
+
});
|
|
505
|
+
} catch (cause) {
|
|
506
|
+
captureResult(cause);
|
|
507
|
+
throw cause;
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
} });
|
|
511
|
+
const configClone = Object.create(Object.getPrototypeOf(config), {
|
|
512
|
+
...configDescriptors,
|
|
513
|
+
binding: {
|
|
514
|
+
...bindingDescriptor,
|
|
515
|
+
value: bindingProxy
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
capture.model = Object.create(Object.getPrototypeOf(model), {
|
|
519
|
+
...modelDescriptors,
|
|
520
|
+
config: {
|
|
521
|
+
...configDescriptor,
|
|
522
|
+
value: configClone
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
} catch {}
|
|
526
|
+
return capture;
|
|
527
|
+
}
|
|
528
|
+
function readHeaderLogId(value) {
|
|
529
|
+
if (isHeadersLike(value)) try {
|
|
530
|
+
return boundedAIGatewayLogId(value.get("cf-aig-log-id"));
|
|
531
|
+
} catch {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (typeof value !== "object" || value === null) return;
|
|
535
|
+
let descriptors;
|
|
536
|
+
try {
|
|
537
|
+
descriptors = Object.getOwnPropertyDescriptors(value);
|
|
538
|
+
} catch {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
for (const [key, descriptor] of Object.entries(descriptors)) if ("value" in descriptor && key.toLowerCase() === "cf-aig-log-id") return boundedAIGatewayLogId(descriptor.value);
|
|
542
|
+
}
|
|
543
|
+
function isHeadersLike(value) {
|
|
544
|
+
if (typeof value !== "object" || value === null) return false;
|
|
545
|
+
try {
|
|
546
|
+
return "get" in value && typeof value.get === "function";
|
|
547
|
+
} catch {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function isGatewayKey(value) {
|
|
552
|
+
return value.includes("gateway") || value.includes("aig") || value.includes("workersai") || value === "cloudflare";
|
|
553
|
+
}
|
|
554
|
+
function normalizeKey(value) {
|
|
555
|
+
return value.toLowerCase().replaceAll(/[-_.]/g, "");
|
|
556
|
+
}
|
|
557
|
+
function dataString(descriptor) {
|
|
558
|
+
return descriptor && "value" in descriptor ? nonEmptyString(descriptor.value) : void 0;
|
|
559
|
+
}
|
|
560
|
+
function nonEmptyString(value) {
|
|
561
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
562
|
+
}
|
|
563
|
+
function boundedAIGatewayLogId(value) {
|
|
564
|
+
const id = nonEmptyString(value);
|
|
565
|
+
return id !== void 0 && new TextEncoder().encode(id).length <= MAX_AI_GATEWAY_LOG_ID_BYTES ? id : void 0;
|
|
566
|
+
}
|
|
567
|
+
//#endregion
|
|
568
|
+
//#region src/observability/ai/content.ts
|
|
569
|
+
const MAX_ATTRIBUTE_BYTES = 28 * 1024;
|
|
570
|
+
const PROTECTED_HEAD_MESSAGES = 2;
|
|
571
|
+
function inputMessageAttributes(value, enabled) {
|
|
572
|
+
if (!enabled || typeof value !== "object" || value === null) return {};
|
|
573
|
+
const record = value;
|
|
574
|
+
const messages = Array.isArray(record.prompt) ? record.prompt : Array.isArray(record.messages) ? record.messages : typeof record.prompt === "string" ? [{
|
|
575
|
+
role: "user",
|
|
576
|
+
content: record.prompt
|
|
577
|
+
}] : void 0;
|
|
578
|
+
return { [TraceAttribute.GenAI.InputMessages]: serializeMessages(messages === void 0 ? void 0 : formatInputMessages(messages)) };
|
|
579
|
+
}
|
|
580
|
+
function outputMessageAttributes(value, enabled) {
|
|
581
|
+
if (!enabled || typeof value !== "object" || value === null) return {};
|
|
582
|
+
const record = value;
|
|
583
|
+
const parts = outputParts(record);
|
|
584
|
+
const finishReason = readFinishReason(record);
|
|
585
|
+
return outputMessageAttributesFrom(parts.length > 0 || finishReason !== void 0 ? [outputMessage(parts, finishReason)] : void 0);
|
|
586
|
+
}
|
|
587
|
+
function outputMessageAttributesFrom(messages) {
|
|
588
|
+
return { [TraceAttribute.GenAI.OutputMessages]: serializeMessages(messages) };
|
|
589
|
+
}
|
|
590
|
+
function toolInputAttributes(value, enabled) {
|
|
591
|
+
return enabled ? { [TraceAttribute.GenAI.ToolCallArguments]: serialize(value) } : {};
|
|
592
|
+
}
|
|
593
|
+
function toolOutputAttributes(value, enabled) {
|
|
594
|
+
return enabled ? { [TraceAttribute.GenAI.ToolCallResult]: serialize(value) } : {};
|
|
595
|
+
}
|
|
596
|
+
function createStreamMessages() {
|
|
597
|
+
let text = "";
|
|
598
|
+
let reasoning = "";
|
|
599
|
+
const toolParts = [];
|
|
600
|
+
return {
|
|
601
|
+
messages(finishReason) {
|
|
602
|
+
const parts = [
|
|
603
|
+
...reasoning ? [{
|
|
604
|
+
type: "reasoning",
|
|
605
|
+
content: reasoning
|
|
606
|
+
}] : [],
|
|
607
|
+
...text ? [{
|
|
608
|
+
type: "text",
|
|
609
|
+
content: text
|
|
610
|
+
}] : [],
|
|
611
|
+
...toolParts
|
|
612
|
+
];
|
|
613
|
+
return parts.length > 0 || finishReason !== void 0 ? [outputMessage(parts, finishReason)] : void 0;
|
|
614
|
+
},
|
|
615
|
+
observe(chunk) {
|
|
616
|
+
if (typeof chunk !== "object" || chunk === null) return;
|
|
617
|
+
const record = chunk;
|
|
618
|
+
const delta = record.text ?? record.delta;
|
|
619
|
+
if (record.type === "text-delta" && typeof delta === "string") text += delta;
|
|
620
|
+
else if (record.type === "reasoning-delta" && typeof delta === "string") reasoning += delta;
|
|
621
|
+
else if (record.type === "tool-call" || record.type === "tool-result") {
|
|
622
|
+
const part = formatMessagePart(record);
|
|
623
|
+
if (part !== void 0) toolParts.push(part);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function formatInputMessages(messages) {
|
|
629
|
+
const formatted = [];
|
|
630
|
+
for (const message of messages) {
|
|
631
|
+
const next = formatInputMessage(message);
|
|
632
|
+
if (next !== void 0) formatted.push(next);
|
|
633
|
+
}
|
|
634
|
+
return formatted;
|
|
635
|
+
}
|
|
636
|
+
function formatInputMessage(value) {
|
|
637
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
638
|
+
const record = value;
|
|
639
|
+
if (typeof record.role !== "string") return void 0;
|
|
640
|
+
const parts = (Array.isArray(record.parts) ? record.parts : Array.isArray(record.content) ? record.content : typeof record.content === "string" ? [record.content] : []).map(formatMessagePart).filter((part) => part !== void 0);
|
|
641
|
+
const name = typeof record.name === "string" ? record.name : void 0;
|
|
642
|
+
return {
|
|
643
|
+
role: record.role,
|
|
644
|
+
parts,
|
|
645
|
+
...name !== void 0 ? { name } : {}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function outputParts(record) {
|
|
649
|
+
if (Array.isArray(record.content)) return record.content.map(formatMessagePart).filter((part) => part !== void 0);
|
|
650
|
+
const parts = [];
|
|
651
|
+
appendReasoningParts(parts, record.reasoning);
|
|
652
|
+
if (typeof record.text === "string" && record.text.length > 0) parts.push({
|
|
653
|
+
type: "text",
|
|
654
|
+
content: record.text
|
|
655
|
+
});
|
|
656
|
+
if (Array.isArray(record.toolCalls)) for (const toolCall of record.toolCalls) {
|
|
657
|
+
const part = formatMessagePart(toolCall);
|
|
658
|
+
if (part !== void 0) parts.push(part);
|
|
659
|
+
}
|
|
660
|
+
return parts;
|
|
661
|
+
}
|
|
662
|
+
function appendReasoningParts(parts, reasoning) {
|
|
663
|
+
if (typeof reasoning === "string" && reasoning.length > 0) {
|
|
664
|
+
parts.push({
|
|
665
|
+
type: "reasoning",
|
|
666
|
+
content: reasoning
|
|
667
|
+
});
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (!Array.isArray(reasoning)) return;
|
|
671
|
+
for (const entry of reasoning) {
|
|
672
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
673
|
+
const text = entry.text;
|
|
674
|
+
if (typeof text === "string" && text.length > 0) parts.push({
|
|
675
|
+
type: "reasoning",
|
|
676
|
+
content: text
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function formatMessagePart(value) {
|
|
681
|
+
if (typeof value === "string") return {
|
|
682
|
+
type: "text",
|
|
683
|
+
content: value
|
|
684
|
+
};
|
|
685
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
686
|
+
const record = value;
|
|
687
|
+
if (typeof record.type !== "string") return void 0;
|
|
688
|
+
switch (record.type) {
|
|
689
|
+
case "text":
|
|
690
|
+
case "reasoning": {
|
|
691
|
+
const content = record.content ?? record.text;
|
|
692
|
+
return typeof content === "string" ? {
|
|
693
|
+
type: record.type,
|
|
694
|
+
content
|
|
695
|
+
} : void 0;
|
|
696
|
+
}
|
|
697
|
+
case "tool-call":
|
|
698
|
+
case "tool_call": return formatToolCall(record);
|
|
699
|
+
case "tool-result":
|
|
700
|
+
case "tool_result":
|
|
701
|
+
case "tool_call_response": return formatToolCallResponse(record);
|
|
702
|
+
default: return { type: record.type.replaceAll("-", "_") };
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function formatToolCall(record) {
|
|
706
|
+
const name = record.name ?? record.toolName;
|
|
707
|
+
if (typeof name !== "string") return void 0;
|
|
708
|
+
const id = record.id ?? record.toolCallId;
|
|
709
|
+
const args = record.arguments ?? record.input;
|
|
710
|
+
return {
|
|
711
|
+
type: "tool_call",
|
|
712
|
+
...typeof id === "string" ? { id } : {},
|
|
713
|
+
name,
|
|
714
|
+
...args !== void 0 ? { arguments: parseJson(args) } : {}
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function formatToolCallResponse(record) {
|
|
718
|
+
const id = record.id ?? record.toolCallId;
|
|
719
|
+
const rawResponse = record.response ?? record.output ?? record.result ?? null;
|
|
720
|
+
return {
|
|
721
|
+
type: "tool_call_response",
|
|
722
|
+
...typeof id === "string" ? { id } : {},
|
|
723
|
+
response: toolResponse(rawResponse)
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function toolResponse(value) {
|
|
727
|
+
if (typeof value !== "object" || value === null) return value;
|
|
728
|
+
const record = value;
|
|
729
|
+
switch (record.type) {
|
|
730
|
+
case "text":
|
|
731
|
+
case "error-text":
|
|
732
|
+
case "json":
|
|
733
|
+
case "error-json":
|
|
734
|
+
case "content": return record.value ?? null;
|
|
735
|
+
case "execution-denied": return {
|
|
736
|
+
denied: true,
|
|
737
|
+
...typeof record.reason === "string" ? { reason: record.reason } : {}
|
|
738
|
+
};
|
|
739
|
+
default: return value;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function outputMessage(parts, finishReason) {
|
|
743
|
+
return {
|
|
744
|
+
role: "assistant",
|
|
745
|
+
parts,
|
|
746
|
+
finish_reason: normalizeFinishReason(finishReason ?? "unknown")
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function readFinishReason(record) {
|
|
750
|
+
const value = record.finishReason ?? record.finish_reason;
|
|
751
|
+
if (typeof value === "string") return value;
|
|
752
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
753
|
+
const unified = value.unified;
|
|
754
|
+
return typeof unified === "string" ? unified : void 0;
|
|
755
|
+
}
|
|
756
|
+
function normalizeFinishReason(value) {
|
|
757
|
+
switch (value) {
|
|
758
|
+
case "content-filter": return "content_filter";
|
|
759
|
+
case "tool-calls":
|
|
760
|
+
case "tool_calls": return "tool_call";
|
|
761
|
+
case "other":
|
|
762
|
+
case "unknown": return "stop";
|
|
763
|
+
default: return value;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function parseJson(value) {
|
|
767
|
+
if (typeof value !== "string") return value;
|
|
768
|
+
try {
|
|
769
|
+
return JSON.parse(value);
|
|
770
|
+
} catch {
|
|
771
|
+
return value;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function serializeMessages(messages) {
|
|
775
|
+
if (messages === void 0) return void 0;
|
|
776
|
+
const kept = [...messages];
|
|
777
|
+
while (true) {
|
|
778
|
+
const json = stringify(kept);
|
|
779
|
+
if (json === void 0) return void 0;
|
|
780
|
+
if (byteLength(json) <= MAX_ATTRIBUTE_BYTES) return json;
|
|
781
|
+
if (kept.length <= PROTECTED_HEAD_MESSAGES) return void 0;
|
|
782
|
+
kept.splice(PROTECTED_HEAD_MESSAGES, 1);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
function serialize(value) {
|
|
786
|
+
const json = stringify(value);
|
|
787
|
+
return json !== void 0 && byteLength(json) <= MAX_ATTRIBUTE_BYTES ? json : void 0;
|
|
788
|
+
}
|
|
789
|
+
function stringify(value) {
|
|
790
|
+
if (value === void 0) return void 0;
|
|
791
|
+
try {
|
|
792
|
+
return JSON.stringify(value);
|
|
793
|
+
} catch {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function byteLength(value) {
|
|
798
|
+
return new TextEncoder().encode(value).length;
|
|
799
|
+
}
|
|
800
|
+
//#endregion
|
|
801
|
+
//#region src/observability/ai/v6/streams.ts
|
|
802
|
+
function finishWhenStreamCompletes(result, span, options = {}) {
|
|
803
|
+
return patchStreamFields(result, {
|
|
804
|
+
onComplete: (summary) => {
|
|
805
|
+
span.finish(finishAttributesFromStreamSummary(summary, options.includeResponse === true, options.includeAIGatewayLog === true, options.aiGatewayLogId, options.storeMessages === true));
|
|
806
|
+
},
|
|
807
|
+
onError: (cause, observedAIGatewayLogId) => {
|
|
808
|
+
if (options.includeAIGatewayLog) writeSpanAttributes(span, aiGatewayLogAttributes(observedAIGatewayLogId ?? extractAIGatewayLogId(cause) ?? options.aiGatewayLogId));
|
|
809
|
+
span.fail(cause);
|
|
810
|
+
}
|
|
811
|
+
}, options.startedAtMs, options.includeAIGatewayLog ? options.aiGatewayLogId : void 0, options.storeMessages === true);
|
|
812
|
+
}
|
|
813
|
+
function finishAttributesFromStreamSummary(summary, includeResponse, includeAIGatewayLog, initialAIGatewayLogId, storeMessages) {
|
|
814
|
+
return {
|
|
815
|
+
...finishAttributes({
|
|
816
|
+
aiGatewayLogId: includeAIGatewayLog ? summary?.aiGatewayLogId ?? initialAIGatewayLogId : void 0,
|
|
817
|
+
finishReason: summary?.finishReason,
|
|
818
|
+
response: includeResponse ? summary?.response : void 0,
|
|
819
|
+
timeToFirstChunkSeconds: summary?.timeToFirstChunkSeconds,
|
|
820
|
+
toolCallCount: summary?.toolCallCount,
|
|
821
|
+
usage: summary?.usage
|
|
822
|
+
}),
|
|
823
|
+
...outputMessageAttributesFrom(storeMessages ? summary?.outputMessages : void 0)
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
function patchStreamFields(result, hooks, startedAtMs, aiGatewayLogId, storeMessages) {
|
|
827
|
+
if (typeof result !== "object" || result === null) {
|
|
828
|
+
hooks.onComplete(void 0);
|
|
829
|
+
return result;
|
|
830
|
+
}
|
|
831
|
+
const record = result;
|
|
832
|
+
let patchedAny = false;
|
|
833
|
+
let closed = false;
|
|
834
|
+
const completeOnce = (summary) => {
|
|
835
|
+
if (closed) return;
|
|
836
|
+
closed = true;
|
|
837
|
+
hooks.onComplete(summary);
|
|
838
|
+
};
|
|
839
|
+
const errorOnce = (cause, observedAIGatewayLogId) => {
|
|
840
|
+
if (closed) return;
|
|
841
|
+
closed = true;
|
|
842
|
+
hooks.onError(cause, observedAIGatewayLogId);
|
|
843
|
+
};
|
|
844
|
+
try {
|
|
845
|
+
if (isReadableStream(record.baseStream)) {
|
|
846
|
+
Object.defineProperty(record, "baseStream", {
|
|
847
|
+
configurable: true,
|
|
848
|
+
enumerable: true,
|
|
849
|
+
value: wrapReadableStream(record.baseStream, {
|
|
850
|
+
onComplete: completeOnce,
|
|
851
|
+
onError: errorOnce
|
|
852
|
+
}, startedAtMs, aiGatewayLogId, storeMessages),
|
|
853
|
+
writable: true
|
|
854
|
+
});
|
|
855
|
+
return result;
|
|
856
|
+
}
|
|
857
|
+
const streamField = findStreamField(record, [
|
|
858
|
+
"partialObjectStream",
|
|
859
|
+
"textStream",
|
|
860
|
+
"fullStream",
|
|
861
|
+
"stream"
|
|
862
|
+
]);
|
|
863
|
+
if (streamField) {
|
|
864
|
+
Object.defineProperty(record, streamField.field, {
|
|
865
|
+
configurable: true,
|
|
866
|
+
enumerable: true,
|
|
867
|
+
value: streamField.kind === "readable" ? wrapReadableStream(streamField.stream, {
|
|
868
|
+
onComplete: completeOnce,
|
|
869
|
+
onError: errorOnce
|
|
870
|
+
}, startedAtMs, aiGatewayLogId, storeMessages) : wrapAsyncIterable(streamField.stream, {
|
|
871
|
+
onComplete: completeOnce,
|
|
872
|
+
onError: errorOnce
|
|
873
|
+
}, startedAtMs, aiGatewayLogId, storeMessages),
|
|
874
|
+
writable: true
|
|
875
|
+
});
|
|
876
|
+
patchedAny = true;
|
|
877
|
+
}
|
|
878
|
+
} catch {
|
|
879
|
+
patchedAny = false;
|
|
880
|
+
}
|
|
881
|
+
if (!patchedAny) {
|
|
882
|
+
hooks.onComplete(void 0);
|
|
883
|
+
return result;
|
|
884
|
+
}
|
|
885
|
+
return result;
|
|
886
|
+
}
|
|
887
|
+
function findStreamField(result, candidateFields) {
|
|
888
|
+
for (const field of candidateFields) try {
|
|
889
|
+
const stream = result[field];
|
|
890
|
+
if (isReadableStream(stream)) return {
|
|
891
|
+
field,
|
|
892
|
+
kind: "readable",
|
|
893
|
+
stream
|
|
894
|
+
};
|
|
895
|
+
if (isAsyncIterable$1(stream)) return {
|
|
896
|
+
field,
|
|
897
|
+
kind: "asyncIterable",
|
|
898
|
+
stream
|
|
899
|
+
};
|
|
900
|
+
} catch {}
|
|
901
|
+
}
|
|
902
|
+
function wrapReadableStream(stream, hooks, startedAtMs, aiGatewayLogId, storeMessages) {
|
|
903
|
+
let reader;
|
|
904
|
+
const state = createStreamState(hooks, startedAtMs, aiGatewayLogId, storeMessages);
|
|
905
|
+
return new ReadableStream({
|
|
906
|
+
async pull(controller) {
|
|
907
|
+
reader ??= stream.getReader();
|
|
908
|
+
try {
|
|
909
|
+
const result = await reader.read();
|
|
910
|
+
if (state.closed) return;
|
|
911
|
+
if (result.done) {
|
|
912
|
+
state.complete();
|
|
913
|
+
controller.close();
|
|
914
|
+
releaseReader();
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
state.observeChunk(result.value);
|
|
918
|
+
controller.enqueue(result.value);
|
|
919
|
+
} catch (cause) {
|
|
920
|
+
if (!state.closed) {
|
|
921
|
+
state.fail(cause);
|
|
922
|
+
controller.error(cause);
|
|
923
|
+
}
|
|
924
|
+
releaseReader();
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
async cancel(reason) {
|
|
928
|
+
state.cancel();
|
|
929
|
+
try {
|
|
930
|
+
if (reader) {
|
|
931
|
+
await reader.cancel(reason);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
await stream.cancel(reason);
|
|
935
|
+
} catch (cause) {
|
|
936
|
+
state.fail(cause);
|
|
937
|
+
throw cause;
|
|
938
|
+
} finally {
|
|
939
|
+
releaseReader();
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
function releaseReader() {
|
|
944
|
+
if (!reader) return;
|
|
945
|
+
try {
|
|
946
|
+
reader.releaseLock();
|
|
947
|
+
} catch {} finally {
|
|
948
|
+
reader = void 0;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
function wrapAsyncIterable(stream, hooks, startedAtMs, aiGatewayLogId, storeMessages) {
|
|
953
|
+
return { async *[Symbol.asyncIterator]() {
|
|
954
|
+
const state = createStreamState(hooks, startedAtMs, aiGatewayLogId, storeMessages);
|
|
955
|
+
try {
|
|
956
|
+
for await (const chunk of stream) {
|
|
957
|
+
state.observeChunk(chunk);
|
|
958
|
+
yield chunk;
|
|
959
|
+
}
|
|
960
|
+
state.complete();
|
|
961
|
+
} catch (cause) {
|
|
962
|
+
state.fail(cause);
|
|
963
|
+
throw cause;
|
|
964
|
+
} finally {
|
|
965
|
+
state.cancel();
|
|
966
|
+
}
|
|
967
|
+
} };
|
|
968
|
+
}
|
|
969
|
+
function createStreamState(hooks, startedAtMs, initialAIGatewayLogId, storeMessages) {
|
|
970
|
+
let closed = false;
|
|
971
|
+
let aiGatewayLogId = initialAIGatewayLogId;
|
|
972
|
+
let finishReason;
|
|
973
|
+
let toolCallCount = 0;
|
|
974
|
+
let usage;
|
|
975
|
+
let response;
|
|
976
|
+
let observedError;
|
|
977
|
+
let observedAbort = false;
|
|
978
|
+
let firstChunkAtMs;
|
|
979
|
+
const output = createStreamMessages();
|
|
980
|
+
const settleObserved = () => {
|
|
981
|
+
if (observedError) {
|
|
982
|
+
hooks.onError(observedError.cause, aiGatewayLogId);
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
if (observedAbort) {
|
|
986
|
+
hooks.onError({ name: "AbortError" }, aiGatewayLogId);
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
989
|
+
return false;
|
|
990
|
+
};
|
|
991
|
+
return {
|
|
992
|
+
get closed() {
|
|
993
|
+
return closed;
|
|
994
|
+
},
|
|
995
|
+
cancel() {
|
|
996
|
+
if (closed) return;
|
|
997
|
+
closed = true;
|
|
998
|
+
if (settleObserved()) return;
|
|
999
|
+
hooks.onComplete(void 0);
|
|
1000
|
+
},
|
|
1001
|
+
complete() {
|
|
1002
|
+
if (closed) return;
|
|
1003
|
+
closed = true;
|
|
1004
|
+
if (settleObserved()) return;
|
|
1005
|
+
hooks.onComplete(streamSummaryFromParts({
|
|
1006
|
+
aiGatewayLogId,
|
|
1007
|
+
finishReason,
|
|
1008
|
+
outputMessages: storeMessages ? output.messages(finishReason) : void 0,
|
|
1009
|
+
response,
|
|
1010
|
+
timeToFirstChunkSeconds: firstChunkAtMs === void 0 || startedAtMs === void 0 ? void 0 : (firstChunkAtMs - startedAtMs) / 1e3,
|
|
1011
|
+
toolCallCount,
|
|
1012
|
+
usage
|
|
1013
|
+
}));
|
|
1014
|
+
},
|
|
1015
|
+
fail(cause) {
|
|
1016
|
+
if (closed) return;
|
|
1017
|
+
closed = true;
|
|
1018
|
+
hooks.onError(cause, aiGatewayLogId);
|
|
1019
|
+
},
|
|
1020
|
+
observeChunk(rawChunk) {
|
|
1021
|
+
firstChunkAtMs ??= Date.now();
|
|
1022
|
+
const chunk = unwrapChunkEnvelope(rawChunk);
|
|
1023
|
+
aiGatewayLogId = extractAIGatewayLogId(chunk) ?? aiGatewayLogId;
|
|
1024
|
+
if (storeMessages) output.observe(chunk);
|
|
1025
|
+
if (isErrorChunk(chunk)) observedError = { cause: chunk.error };
|
|
1026
|
+
if (isAbortChunk(chunk)) observedAbort = true;
|
|
1027
|
+
if (isToolCallChunk(chunk)) toolCallCount += 1;
|
|
1028
|
+
finishReason = extractFinishReason$1(chunk) ?? finishReason;
|
|
1029
|
+
usage = extractAISDKv6TokenUsage(chunk) ?? usage;
|
|
1030
|
+
response = extractResponseInfo(chunk) ?? response;
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* The streamText result's private `baseStream` carries `{ part, partialOutput }`
|
|
1036
|
+
* envelopes rather than bare stream parts; `fullStream` and provider-level
|
|
1037
|
+
* streams carry bare parts. Unwrap the envelope when present so chunk
|
|
1038
|
+
* inspection sees the actual part in both cases.
|
|
1039
|
+
*/
|
|
1040
|
+
function unwrapChunkEnvelope(chunk) {
|
|
1041
|
+
if (typeof chunk !== "object" || chunk === null) return chunk;
|
|
1042
|
+
const part = chunk.part;
|
|
1043
|
+
return typeof part === "object" && part !== null && "type" in part ? part : chunk;
|
|
1044
|
+
}
|
|
1045
|
+
function isErrorChunk(chunk) {
|
|
1046
|
+
return typeof chunk === "object" && chunk !== null && chunk.type === "error";
|
|
1047
|
+
}
|
|
1048
|
+
function isAbortChunk(chunk) {
|
|
1049
|
+
return typeof chunk === "object" && chunk !== null && chunk.type === "abort";
|
|
1050
|
+
}
|
|
1051
|
+
function streamSummaryFromParts(input) {
|
|
1052
|
+
return {
|
|
1053
|
+
...input.aiGatewayLogId !== void 0 ? { aiGatewayLogId: input.aiGatewayLogId } : {},
|
|
1054
|
+
...input.finishReason !== void 0 ? { finishReason: input.finishReason } : {},
|
|
1055
|
+
...input.outputMessages !== void 0 ? { outputMessages: input.outputMessages } : {},
|
|
1056
|
+
...input.response ? { response: input.response } : {},
|
|
1057
|
+
...input.timeToFirstChunkSeconds !== void 0 ? { timeToFirstChunkSeconds: input.timeToFirstChunkSeconds } : {},
|
|
1058
|
+
...input.toolCallCount > 0 ? { toolCallCount: input.toolCallCount } : {},
|
|
1059
|
+
...input.usage ? { usage: input.usage } : {}
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
function isReadableStream(value) {
|
|
1063
|
+
return typeof value === "object" && value !== null && "pipeThrough" in value && typeof value.pipeThrough === "function" && "getReader" in value && typeof value.getReader === "function";
|
|
1064
|
+
}
|
|
1065
|
+
function isToolCallChunk(chunk) {
|
|
1066
|
+
return typeof chunk === "object" && chunk !== null && chunk.type === "tool-call";
|
|
1067
|
+
}
|
|
1068
|
+
function isAsyncIterable$1(value) {
|
|
1069
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
1070
|
+
}
|
|
1071
|
+
//#endregion
|
|
1072
|
+
//#region src/observability/ai/v6/model.ts
|
|
1073
|
+
function wrapModel(tracer, wrapLanguageModel, model, parentOperation, storeMessages) {
|
|
1074
|
+
if (!wrapLanguageModel) return model;
|
|
1075
|
+
if (typeof model !== "object" || model === null) return model;
|
|
1076
|
+
const modelInfo = extractModelInfo(model);
|
|
1077
|
+
const aiGatewayLog = captureAIGatewayLogFromModel(model, modelInfo?.provider);
|
|
1078
|
+
return wrapLanguageModel({
|
|
1079
|
+
model: aiGatewayLog.model,
|
|
1080
|
+
middleware: {
|
|
1081
|
+
wrapGenerate: async ({ doGenerate, params }) => {
|
|
1082
|
+
const span = modelCallSpanForModel("doGenerate", modelInfo, params, parentOperation, storeMessages);
|
|
1083
|
+
return tracer.withSpan(span.name, span.attributes, async (modelCall) => {
|
|
1084
|
+
aiGatewayLog.reset();
|
|
1085
|
+
try {
|
|
1086
|
+
const result = await doGenerate();
|
|
1087
|
+
modelCall.finish({
|
|
1088
|
+
...finishAttributesFromResult(result, {
|
|
1089
|
+
aiGatewayLogId: extractAIGatewayLogId(result) ?? aiGatewayLog.get(),
|
|
1090
|
+
includeResponse: true
|
|
1091
|
+
}),
|
|
1092
|
+
...outputMessageAttributes(result, storeMessages)
|
|
1093
|
+
});
|
|
1094
|
+
return result;
|
|
1095
|
+
} catch (cause) {
|
|
1096
|
+
recordAIGatewayLogOnError(modelCall, cause, aiGatewayLog.get());
|
|
1097
|
+
throw cause;
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
},
|
|
1101
|
+
wrapStream: async ({ doStream, params }) => {
|
|
1102
|
+
const span = modelCallSpanForModel("doStream", modelInfo, params, parentOperation, storeMessages);
|
|
1103
|
+
return tracer.openSpan(span.name, span.attributes, async (modelCall) => {
|
|
1104
|
+
aiGatewayLog.reset();
|
|
1105
|
+
try {
|
|
1106
|
+
const startedAtMs = Date.now();
|
|
1107
|
+
const result = await doStream();
|
|
1108
|
+
return finishWhenStreamCompletes(result, modelCall, {
|
|
1109
|
+
aiGatewayLogId: extractAIGatewayLogId(result) ?? aiGatewayLog.get(),
|
|
1110
|
+
includeAIGatewayLog: true,
|
|
1111
|
+
includeResponse: true,
|
|
1112
|
+
storeMessages,
|
|
1113
|
+
startedAtMs
|
|
1114
|
+
});
|
|
1115
|
+
} catch (cause) {
|
|
1116
|
+
recordAIGatewayLogOnError(modelCall, cause, aiGatewayLog.get());
|
|
1117
|
+
modelCall.fail(cause);
|
|
1118
|
+
throw cause;
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
function recordAIGatewayLogOnError(span, cause, capturedLogId) {
|
|
1126
|
+
writeSpanAttributes(span, aiGatewayLogAttributes(extractAIGatewayLogId(cause) ?? capturedLogId));
|
|
1127
|
+
}
|
|
1128
|
+
function modelCallSpanForModel(operation, model, params, parentOperation, storeMessages) {
|
|
1129
|
+
const record = typeof params === "object" && params !== null ? params : {};
|
|
1130
|
+
const span = modelCallSpan({
|
|
1131
|
+
integration: "ai-sdk",
|
|
1132
|
+
model: model?.modelId,
|
|
1133
|
+
operation,
|
|
1134
|
+
provider: model?.provider,
|
|
1135
|
+
request: extractRequestSummary(record, parentOperation)
|
|
1136
|
+
});
|
|
1137
|
+
return {
|
|
1138
|
+
...span,
|
|
1139
|
+
attributes: {
|
|
1140
|
+
...span.attributes,
|
|
1141
|
+
...inputMessageAttributes(record, storeMessages)
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
//#endregion
|
|
1146
|
+
//#region src/observability/ai/v6/tools.ts
|
|
1147
|
+
function wrapTools(tracer, tools, storeTools) {
|
|
1148
|
+
if (typeof tools !== "object" || tools === null) return tools;
|
|
1149
|
+
const toolRecord = tools;
|
|
1150
|
+
const wrappedTools = {};
|
|
1151
|
+
for (const [toolName, tool] of Object.entries(toolRecord)) wrappedTools[toolName] = wrapTool(tracer, toolName, tool, storeTools);
|
|
1152
|
+
return wrappedTools;
|
|
1153
|
+
}
|
|
1154
|
+
function wrapTool(tracer, toolName, tool, storeTools) {
|
|
1155
|
+
if (typeof tool !== "object" || tool === null) return tool;
|
|
1156
|
+
const toolRecord = tool;
|
|
1157
|
+
const hasExecute = typeof toolRecord.execute === "function";
|
|
1158
|
+
const hasApproval = typeof toolRecord.needsApproval === "boolean" || typeof toolRecord.needsApproval === "function";
|
|
1159
|
+
if (!hasExecute && !hasApproval) return tool;
|
|
1160
|
+
const wrappedTool = Object.assign(Object.create(Object.getPrototypeOf(tool)), tool);
|
|
1161
|
+
if (hasApproval) wrapApprovalCheck(tracer, wrappedTool, toolRecord, tool, toolName);
|
|
1162
|
+
if (!hasExecute) return wrappedTool;
|
|
1163
|
+
const execute = toolRecord.execute;
|
|
1164
|
+
if (typeof execute !== "function") return wrappedTool;
|
|
1165
|
+
const originalExecute = execute.bind(tool);
|
|
1166
|
+
wrappedTool.execute = (...args) => {
|
|
1167
|
+
const span = toolCallSpan({
|
|
1168
|
+
integration: "ai-sdk",
|
|
1169
|
+
operation: "tool.execute",
|
|
1170
|
+
toolCallId: extractToolCallId(args[1]),
|
|
1171
|
+
toolName
|
|
1172
|
+
});
|
|
1173
|
+
const attributes = {
|
|
1174
|
+
...span.attributes,
|
|
1175
|
+
...toolInputAttributes(args[0], storeTools)
|
|
1176
|
+
};
|
|
1177
|
+
return tracer.openSpan(span.name, attributes, (toolSpan) => {
|
|
1178
|
+
const inSpanContext = AsyncLocalStorage.snapshot();
|
|
1179
|
+
const approval = approvalResponseForOptions(args[1], extractToolCallId(args[1]));
|
|
1180
|
+
if (approval?.approved === true) recordApprovalChild(tracer, toolName, approval.toolCallId, "approved");
|
|
1181
|
+
const result = originalExecute(...args);
|
|
1182
|
+
if (isPromiseLike(result)) return Promise.resolve(result).then((resolved) => settleToolResult(resolved, toolSpan, inSpanContext, storeTools), (cause) => {
|
|
1183
|
+
toolSpan.fail(cause);
|
|
1184
|
+
throw cause;
|
|
1185
|
+
});
|
|
1186
|
+
return settleToolResult(result, toolSpan, inSpanContext, storeTools);
|
|
1187
|
+
});
|
|
1188
|
+
};
|
|
1189
|
+
return wrappedTool;
|
|
1190
|
+
}
|
|
1191
|
+
function wrapApprovalCheck(tracer, wrappedTool, toolRecord, tool, toolName) {
|
|
1192
|
+
const approval = toolRecord.needsApproval;
|
|
1193
|
+
const original = typeof approval === "function" ? approval.bind(tool) : void 0;
|
|
1194
|
+
wrappedTool.needsApproval = (...args) => {
|
|
1195
|
+
const result = original ? original(...args) : approval;
|
|
1196
|
+
const recordRequested = (needed) => {
|
|
1197
|
+
const toolCallId = extractToolCallId(args[1]);
|
|
1198
|
+
if (needed === true && !hasApprovalResponse(args[1], toolCallId)) recordApprovalSegment(tracer, toolName, toolCallId, "requested");
|
|
1199
|
+
return needed;
|
|
1200
|
+
};
|
|
1201
|
+
return isPromiseLike(result) ? Promise.resolve(result).then(recordRequested) : recordRequested(result);
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
/** Records denied responses, whose tool never reaches execute(). */
|
|
1205
|
+
function recordDeniedApprovalResponses(tracer, messages) {
|
|
1206
|
+
for (const response of approvalResponses(messages)) if (!response.approved) recordApprovalSegment(tracer, response.toolName, response.toolCallId, "denied");
|
|
1207
|
+
}
|
|
1208
|
+
function recordApprovalSegment(tracer, toolName, toolCallId, state) {
|
|
1209
|
+
const tool = toolCallSpan({
|
|
1210
|
+
integration: "ai-sdk",
|
|
1211
|
+
operation: "tool.approval",
|
|
1212
|
+
toolCallId,
|
|
1213
|
+
toolName
|
|
1214
|
+
});
|
|
1215
|
+
tracer.withSpan(tool.name, tool.attributes, () => {
|
|
1216
|
+
recordApprovalChild(tracer, toolName, toolCallId, state);
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
function recordApprovalChild(tracer, toolName, toolCallId, state) {
|
|
1220
|
+
const approval = toolApprovalSpan({
|
|
1221
|
+
state,
|
|
1222
|
+
toolCallId,
|
|
1223
|
+
toolName
|
|
1224
|
+
});
|
|
1225
|
+
tracer.withSpan(approval.name, approval.attributes, () => void 0);
|
|
1226
|
+
}
|
|
1227
|
+
function hasApprovalResponse(options, toolCallId) {
|
|
1228
|
+
return approvalResponseForOptions(options, toolCallId) !== void 0;
|
|
1229
|
+
}
|
|
1230
|
+
function approvalResponseForOptions(options, toolCallId) {
|
|
1231
|
+
if (toolCallId === void 0 || typeof options !== "object" || options === null) return;
|
|
1232
|
+
return approvalResponses(options.messages).find((response) => response.toolCallId === toolCallId);
|
|
1233
|
+
}
|
|
1234
|
+
function approvalResponses(messagesValue) {
|
|
1235
|
+
if (!Array.isArray(messagesValue)) return [];
|
|
1236
|
+
const approvalToTool = /* @__PURE__ */ new Map();
|
|
1237
|
+
const toolNames = /* @__PURE__ */ new Map();
|
|
1238
|
+
for (const message of messagesValue) {
|
|
1239
|
+
if (typeof message !== "object" || message === null) continue;
|
|
1240
|
+
const content = message.content;
|
|
1241
|
+
if (!Array.isArray(content)) continue;
|
|
1242
|
+
for (const part of content) {
|
|
1243
|
+
if (typeof part !== "object" || part === null) continue;
|
|
1244
|
+
const record = part;
|
|
1245
|
+
const type = readString(record.type);
|
|
1246
|
+
if (type === "tool-call") {
|
|
1247
|
+
const toolCallId = readString(record.toolCallId);
|
|
1248
|
+
const toolName = readString(record.toolName);
|
|
1249
|
+
if (toolCallId && toolName) toolNames.set(toolCallId, toolName);
|
|
1250
|
+
} else if (type === "tool-approval-request") {
|
|
1251
|
+
const approvalId = readString(record.approvalId);
|
|
1252
|
+
const toolCallId = readString(record.toolCallId);
|
|
1253
|
+
if (approvalId && toolCallId) approvalToTool.set(approvalId, toolCallId);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
const lastMessage = messagesValue.at(-1);
|
|
1258
|
+
const lastContent = typeof lastMessage === "object" && lastMessage !== null ? lastMessage.content : void 0;
|
|
1259
|
+
const decisions = [];
|
|
1260
|
+
if (Array.isArray(lastContent)) for (const part of lastContent) {
|
|
1261
|
+
if (typeof part !== "object" || part === null) continue;
|
|
1262
|
+
const record = part;
|
|
1263
|
+
if (record.type !== "tool-approval-response") continue;
|
|
1264
|
+
const approvalId = readString(record.approvalId);
|
|
1265
|
+
if (approvalId && typeof record.approved === "boolean") decisions.push({
|
|
1266
|
+
approvalId,
|
|
1267
|
+
approved: record.approved
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
return decisions.flatMap(({ approvalId, approved }) => {
|
|
1271
|
+
const toolCallId = approvalToTool.get(approvalId);
|
|
1272
|
+
if (!toolCallId) return [];
|
|
1273
|
+
return [{
|
|
1274
|
+
approved,
|
|
1275
|
+
toolCallId,
|
|
1276
|
+
toolName: toolNames.get(toolCallId) ?? "tool"
|
|
1277
|
+
}];
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Finishes the tool span for a settled result. Streaming tools (async
|
|
1282
|
+
* generators) return an iterable whose consumption is the tool's real
|
|
1283
|
+
* duration, so the span closes when iteration ends instead of at creation.
|
|
1284
|
+
*/
|
|
1285
|
+
function settleToolResult(result, span, inSpanContext, storeTools) {
|
|
1286
|
+
if (isAsyncIterable(result)) return finishWhenIterableCompletes(result, span, inSpanContext, storeTools);
|
|
1287
|
+
span.finish(toolOutputAttributes(result, storeTools));
|
|
1288
|
+
return result;
|
|
1289
|
+
}
|
|
1290
|
+
function finishWhenIterableCompletes(iterable, span, inSpanContext, storeTools) {
|
|
1291
|
+
return { async *[Symbol.asyncIterator]() {
|
|
1292
|
+
const iterator = inSpanContext(() => iterable[Symbol.asyncIterator]());
|
|
1293
|
+
let exhausted = false;
|
|
1294
|
+
let returnValue;
|
|
1295
|
+
try {
|
|
1296
|
+
while (true) {
|
|
1297
|
+
const step = await inSpanContext(() => iterator.next());
|
|
1298
|
+
if (step.done) {
|
|
1299
|
+
exhausted = true;
|
|
1300
|
+
returnValue = step.value;
|
|
1301
|
+
return step.value;
|
|
1302
|
+
}
|
|
1303
|
+
yield step.value;
|
|
1304
|
+
}
|
|
1305
|
+
} catch (cause) {
|
|
1306
|
+
span.fail(cause);
|
|
1307
|
+
throw cause;
|
|
1308
|
+
} finally {
|
|
1309
|
+
if (!exhausted) try {
|
|
1310
|
+
await inSpanContext(() => iterator.return?.(void 0));
|
|
1311
|
+
} catch {}
|
|
1312
|
+
span.finish(toolOutputAttributes(returnValue, storeTools));
|
|
1313
|
+
}
|
|
1314
|
+
} };
|
|
1315
|
+
}
|
|
1316
|
+
/** Reads the AI SDK tool-call id from the execute options argument. */
|
|
1317
|
+
function extractToolCallId(options) {
|
|
1318
|
+
if (typeof options !== "object" || options === null) return;
|
|
1319
|
+
return readString(options.toolCallId);
|
|
1320
|
+
}
|
|
1321
|
+
function isAsyncIterable(value) {
|
|
1322
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
1323
|
+
}
|
|
1324
|
+
function isPromiseLike(value) {
|
|
1325
|
+
return value !== null && value !== void 0 && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
|
|
1326
|
+
}
|
|
1327
|
+
//#endregion
|
|
1328
|
+
//#region src/observability/ai/v6/wrap.ts
|
|
1329
|
+
/**
|
|
1330
|
+
* Wraps an AI SDK namespace object with v6 tracing while preserving its public
|
|
1331
|
+
* shape and overloaded call signatures.
|
|
1332
|
+
*/
|
|
1333
|
+
function createAISDKV6Wrapper(ai, instrumentation) {
|
|
1334
|
+
const target = isModuleNamespace(ai) ? Object.setPrototypeOf({}, ai) : ai;
|
|
1335
|
+
const wrapperCache = /* @__PURE__ */ new Map();
|
|
1336
|
+
return new Proxy(target, { get(proxyTarget, property, receiver) {
|
|
1337
|
+
const original = Reflect.get(proxyTarget, property, receiver);
|
|
1338
|
+
if (isWrappedOperationName(property) && typeof original === "function") {
|
|
1339
|
+
let wrapper = wrapperCache.get(property);
|
|
1340
|
+
if (!wrapper) {
|
|
1341
|
+
wrapper = createOperationWrapper(property, toAISDKV6Operation(original), readWrapLanguageModel(ai), instrumentation);
|
|
1342
|
+
wrapperCache.set(property, wrapper);
|
|
1343
|
+
}
|
|
1344
|
+
return wrapper;
|
|
1345
|
+
}
|
|
1346
|
+
return original;
|
|
1347
|
+
} });
|
|
1348
|
+
}
|
|
1349
|
+
function readWrapLanguageModel(ai) {
|
|
1350
|
+
const value = ai.wrapLanguageModel;
|
|
1351
|
+
if (typeof value !== "function") return;
|
|
1352
|
+
return value;
|
|
1353
|
+
}
|
|
1354
|
+
function toAISDKV6Operation(value) {
|
|
1355
|
+
return value;
|
|
1356
|
+
}
|
|
1357
|
+
function isModuleNamespace(value) {
|
|
1358
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1359
|
+
if (value.constructor?.name === "Module") return true;
|
|
1360
|
+
try {
|
|
1361
|
+
const firstKey = Object.keys(value)[0];
|
|
1362
|
+
if (firstKey === void 0) return false;
|
|
1363
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, firstKey);
|
|
1364
|
+
return descriptor ? !descriptor.configurable && !descriptor.writable : false;
|
|
1365
|
+
} catch {
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
function createOperationWrapper(operationName, operation, wrapLanguageModel, instrumentation) {
|
|
1370
|
+
const storage = {
|
|
1371
|
+
storeMessages: instrumentation.options?.storeMessages === true,
|
|
1372
|
+
storeTools: instrumentation.options?.storeTools === true
|
|
1373
|
+
};
|
|
1374
|
+
if (isStreamOperation$1(operationName)) return (params, ...args) => {
|
|
1375
|
+
return instrumentation.tracer.openSpan(operationSpanName(agentNameForCall(params)), {}, (operationSpan) => {
|
|
1376
|
+
if (!operationSpan.isTraced) {
|
|
1377
|
+
operationSpan.finish();
|
|
1378
|
+
return operation(params, ...args);
|
|
1379
|
+
}
|
|
1380
|
+
writeSpanAttributes(operationSpan, operationSpanForCall(operationName, extractModelInfo(params.model), params, instrumentation.options).attributes);
|
|
1381
|
+
recordDeniedApprovalResponses(instrumentation.tracer, params.messages);
|
|
1382
|
+
const startedAtMs = Date.now();
|
|
1383
|
+
const result = operation(operationParamsForCall(params, operationName, wrapLanguageModel, instrumentation.tracer, storage), ...args);
|
|
1384
|
+
const hasModelSpan = canWrapModel(wrapLanguageModel, params.model);
|
|
1385
|
+
return finishWhenStreamCompletes(result, operationSpan, {
|
|
1386
|
+
includeResponse: !hasModelSpan,
|
|
1387
|
+
startedAtMs: hasModelSpan ? void 0 : startedAtMs
|
|
1388
|
+
});
|
|
1389
|
+
});
|
|
1390
|
+
};
|
|
1391
|
+
return async (params, ...args) => {
|
|
1392
|
+
return instrumentation.tracer.withSpan(operationSpanName(agentNameForCall(params)), {}, async (operationSpan) => {
|
|
1393
|
+
if (!operationSpan.isTraced) return operation(params, ...args);
|
|
1394
|
+
writeSpanAttributes(operationSpan, operationSpanForCall(operationName, extractModelInfo(params.model), params, instrumentation.options).attributes);
|
|
1395
|
+
recordDeniedApprovalResponses(instrumentation.tracer, params.messages);
|
|
1396
|
+
const result = await operation(operationParamsForCall(params, operationName, wrapLanguageModel, instrumentation.tracer, storage), ...args);
|
|
1397
|
+
operationSpan.finish(finishAttributesFromResult(result, { includeResponse: !canWrapModel(wrapLanguageModel, params.model) }));
|
|
1398
|
+
return result;
|
|
1399
|
+
});
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Reads only the agent name (metadata.agentName / gen_ai.agent.name /
|
|
1404
|
+
* functionId) for the span name. `functionId` is the AI SDK's canonical
|
|
1405
|
+
* projection to `gen_ai.agent.name`; an explicit metadata name takes priority.
|
|
1406
|
+
*/
|
|
1407
|
+
function agentNameForCall(params) {
|
|
1408
|
+
const telemetry = typeof params.experimental_telemetry === "object" && params.experimental_telemetry !== null ? params.experimental_telemetry : void 0;
|
|
1409
|
+
const metadata = typeof telemetry?.metadata === "object" && telemetry.metadata !== null ? telemetry.metadata : void 0;
|
|
1410
|
+
return readString(metadata?.agentName ?? metadata?.["gen_ai.agent.name"]) ?? readString(telemetry?.functionId);
|
|
1411
|
+
}
|
|
1412
|
+
function operationParamsForCall(params, operationName, wrapLanguageModel, tracer, storage) {
|
|
1413
|
+
return {
|
|
1414
|
+
...params,
|
|
1415
|
+
...shouldWrapTools(operationName) && params.tools !== void 0 ? { tools: wrapTools(tracer, params.tools, storage.storeTools) } : {},
|
|
1416
|
+
...params.model !== void 0 ? { model: wrapModel(tracer, wrapLanguageModel, params.model, operationName, storage.storeMessages) } : {}
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function canWrapModel(wrapLanguageModel, model) {
|
|
1420
|
+
return wrapLanguageModel !== void 0 && typeof model === "object" && model !== null;
|
|
1421
|
+
}
|
|
1422
|
+
function isStreamOperation$1(operationName) {
|
|
1423
|
+
return operationName === "streamObject" || operationName === "streamText";
|
|
1424
|
+
}
|
|
1425
|
+
function shouldWrapTools(operationName) {
|
|
1426
|
+
return operationName === "generateText" || operationName === "streamText";
|
|
1427
|
+
}
|
|
1428
|
+
function isWrappedOperationName(value) {
|
|
1429
|
+
return value === "generateObject" || value === "generateText" || value === "streamObject" || value === "streamText";
|
|
1430
|
+
}
|
|
1431
|
+
function operationSpanForCall(operation, model, params, options) {
|
|
1432
|
+
return operationSpan({
|
|
1433
|
+
attributes: {
|
|
1434
|
+
...metadataAttributes(telemetryMetadata(params)),
|
|
1435
|
+
...contextAttributes(params, options)
|
|
1436
|
+
},
|
|
1437
|
+
context: semanticContext(params),
|
|
1438
|
+
integration: "ai-sdk",
|
|
1439
|
+
model: model?.modelId,
|
|
1440
|
+
operation,
|
|
1441
|
+
provider: model?.provider,
|
|
1442
|
+
request: extractRequestSummary(params, operation)
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
/** Reads the per-call `experimental_telemetry.metadata` record, if present. */
|
|
1446
|
+
function telemetryMetadata(params) {
|
|
1447
|
+
const telemetry = typeof params.experimental_telemetry === "object" && params.experimental_telemetry !== null ? params.experimental_telemetry : void 0;
|
|
1448
|
+
return typeof telemetry?.metadata === "object" && telemetry.metadata !== null ? telemetry.metadata : void 0;
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Reads agent/conversation semantic context from the AI SDK's own
|
|
1452
|
+
* `experimental_telemetry` fields. The AI SDK maps `functionId` to
|
|
1453
|
+
* `gen_ai.agent.name`; explicit `metadata.agentName` / `gen_ai.agent.name`
|
|
1454
|
+
* takes priority. Other semantic fields come only from metadata.
|
|
1455
|
+
*/
|
|
1456
|
+
function semanticContext(params) {
|
|
1457
|
+
const telemetry = typeof params.experimental_telemetry === "object" && params.experimental_telemetry !== null ? params.experimental_telemetry : void 0;
|
|
1458
|
+
const metadata = typeof telemetry?.metadata === "object" && telemetry.metadata !== null ? telemetry.metadata : void 0;
|
|
1459
|
+
return {
|
|
1460
|
+
agentId: metadataValue$1(metadata, "agentId", "gen_ai.agent.id"),
|
|
1461
|
+
agentName: metadataValue$1(metadata, "agentName", "gen_ai.agent.name") ?? readString(telemetry?.functionId),
|
|
1462
|
+
agentVersion: metadataValue$1(metadata, "agentVersion", "gen_ai.agent.version"),
|
|
1463
|
+
conversationId: metadataValue$1(metadata, "conversationId", "gen_ai.conversation.id")
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
function metadataValue$1(metadata, key, semanticKey) {
|
|
1467
|
+
return readString(metadata?.[key] ?? metadata?.[semanticKey]);
|
|
1468
|
+
}
|
|
1469
|
+
function contextAttributes(params, options) {
|
|
1470
|
+
const attributes = {};
|
|
1471
|
+
const runtimeContext = typeof params.experimental_context === "object" && params.experimental_context !== null ? params.experimental_context : void 0;
|
|
1472
|
+
for (const key of options?.includeRuntimeContext ?? []) {
|
|
1473
|
+
const value = runtimeContext?.[key];
|
|
1474
|
+
if (isScalarAttributeValue$1(value)) attributes[`cloudflare.agents.runtime_context.${key}`] = value;
|
|
1475
|
+
}
|
|
1476
|
+
return Object.keys(attributes).length > 0 ? attributes : void 0;
|
|
1477
|
+
}
|
|
1478
|
+
function isScalarAttributeValue$1(value) {
|
|
1479
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
1480
|
+
}
|
|
1481
|
+
//#endregion
|
|
1482
|
+
//#region src/observability/ai/v7/extract.ts
|
|
1483
|
+
/** Extracts the safe operation name from an AI SDK v7 operation id. */
|
|
1484
|
+
function operationNameFromId(operationId) {
|
|
1485
|
+
const value = readString(operationId);
|
|
1486
|
+
if (value === void 0) return "ai-sdk";
|
|
1487
|
+
return value.startsWith("ai.") ? value.slice(3) : value;
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Extracts safe GenAI semantic context from an AI SDK v7 event. The AI SDK's
|
|
1491
|
+
* canonical OpenTelemetry projection maps `functionId` to agent name. v7 has
|
|
1492
|
+
* no telemetry metadata bag, so other identity fields come from the SDK-
|
|
1493
|
+
* filtered runtime context only when the caller explicitly includes them.
|
|
1494
|
+
*/
|
|
1495
|
+
function semanticContextFromEvent(event) {
|
|
1496
|
+
const record = eventRecord(event);
|
|
1497
|
+
const runtimeContext = typeof record.runtimeContext === "object" && record.runtimeContext !== null ? record.runtimeContext : void 0;
|
|
1498
|
+
return {
|
|
1499
|
+
agentId: metadataValue(runtimeContext, "agentId", "gen_ai.agent.id"),
|
|
1500
|
+
agentName: metadataValue(runtimeContext, "agentName", "gen_ai.agent.name") ?? readString(record.functionId),
|
|
1501
|
+
agentVersion: metadataValue(runtimeContext, "agentVersion", "gen_ai.agent.version"),
|
|
1502
|
+
conversationId: metadataValue(runtimeContext, "conversationId", "gen_ai.conversation.id")
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
/** Extracts safe request settings from an AI SDK v7 event. */
|
|
1506
|
+
function requestSummaryFromEvent(event, operationName) {
|
|
1507
|
+
const record = eventRecord(event);
|
|
1508
|
+
return {
|
|
1509
|
+
frequencyPenalty: readNumber(record.frequencyPenalty),
|
|
1510
|
+
maxTokens: readNumber(record.maxOutputTokens ?? record.maxTokens),
|
|
1511
|
+
outputType: operationName === "generateObject" || operationName === "streamObject" ? "json" : "text",
|
|
1512
|
+
presencePenalty: readNumber(record.presencePenalty),
|
|
1513
|
+
seed: readNumber(record.seed),
|
|
1514
|
+
stream: operationName === "streamText" || operationName === "streamObject",
|
|
1515
|
+
temperature: readNumber(record.temperature),
|
|
1516
|
+
topK: readNumber(record.topK),
|
|
1517
|
+
topP: readNumber(record.topP)
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
/** Extracts safe finish attributes from an AI SDK v7 result-like event. */
|
|
1521
|
+
function finishAttributesFromEvent(event, options = {}) {
|
|
1522
|
+
const record = eventRecord(event);
|
|
1523
|
+
return finishAttributes({
|
|
1524
|
+
aiGatewayLogId: options.includeAIGatewayLog ? extractAIGatewayLogId(record) : void 0,
|
|
1525
|
+
finishReason: extractFinishReason(record),
|
|
1526
|
+
response: options.includeResponse ? responseSummaryFromEvent(record) : void 0,
|
|
1527
|
+
timeToFirstChunkSeconds: options.includePerformance ? timeToFirstChunkSeconds(record) : void 0,
|
|
1528
|
+
usage: tokenUsageFromEvent(record)
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
/** Builds correlation attributes for AI SDK v7 callback ids. */
|
|
1532
|
+
function correlationAttributes(input) {
|
|
1533
|
+
return {
|
|
1534
|
+
[TraceAttribute.Cloudflare.CallID]: input.callId,
|
|
1535
|
+
[TraceAttribute.GenAI.ToolCallID]: input.toolCallId
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
function metadataValue(metadata, key, semanticKey) {
|
|
1539
|
+
return readString(metadata?.[key] ?? metadata?.[semanticKey]);
|
|
1540
|
+
}
|
|
1541
|
+
function eventRecord(event) {
|
|
1542
|
+
return event;
|
|
1543
|
+
}
|
|
1544
|
+
function extractFinishReason(event) {
|
|
1545
|
+
const finishReason = event.finishReason;
|
|
1546
|
+
if (typeof finishReason === "string") return finishReason;
|
|
1547
|
+
if (typeof finishReason === "object" && finishReason !== null) return readString(finishReason.unified);
|
|
1548
|
+
}
|
|
1549
|
+
function responseSummaryFromEvent(event) {
|
|
1550
|
+
const response = typeof event.response === "object" && event.response !== null ? event.response : void 0;
|
|
1551
|
+
const id = readString(event.responseId ?? response?.id);
|
|
1552
|
+
const model = readString(event.responseModel ?? response?.modelId ?? response?.model);
|
|
1553
|
+
if (id === void 0 && model === void 0) return;
|
|
1554
|
+
return {
|
|
1555
|
+
...id !== void 0 ? { id } : {},
|
|
1556
|
+
...model !== void 0 ? { model } : {}
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
function tokenUsageFromEvent(event) {
|
|
1560
|
+
const raw = event.totalUsage ?? event.usage;
|
|
1561
|
+
if (typeof raw !== "object" || raw === null) return;
|
|
1562
|
+
const usage = raw;
|
|
1563
|
+
const inputTokens = readTokenCount(usage.inputTokens);
|
|
1564
|
+
const outputTokens = readTokenCount(usage.outputTokens);
|
|
1565
|
+
const cacheReadInputTokens = readNestedTokenField(usage.inputTokenDetails, "cacheReadTokens") ?? readNestedTokenField(usage.inputTokens, "cacheRead") ?? readNumber(usage.cachedInputTokens);
|
|
1566
|
+
const cacheCreationInputTokens = readNestedTokenField(usage.inputTokenDetails, "cacheWriteTokens") ?? readNestedTokenField(usage.inputTokens, "cacheWrite");
|
|
1567
|
+
const reasoningTokens = readNestedTokenField(usage.outputTokenDetails, "reasoningTokens") ?? readNestedTokenField(usage.outputTokens, "reasoning") ?? readNumber(usage.reasoningTokens);
|
|
1568
|
+
if (inputTokens === void 0 && outputTokens === void 0 && cacheReadInputTokens === void 0 && cacheCreationInputTokens === void 0 && reasoningTokens === void 0) return;
|
|
1569
|
+
return {
|
|
1570
|
+
...cacheCreationInputTokens !== void 0 ? { cacheCreationInputTokens } : {},
|
|
1571
|
+
...cacheReadInputTokens !== void 0 ? { cacheReadInputTokens } : {},
|
|
1572
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
1573
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
1574
|
+
...reasoningTokens !== void 0 ? { reasoningTokens } : {}
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
function timeToFirstChunkSeconds(event) {
|
|
1578
|
+
const milliseconds = readNumber((typeof event.performance === "object" && event.performance !== null ? event.performance : void 0)?.timeToFirstOutputMs);
|
|
1579
|
+
return milliseconds === void 0 ? void 0 : milliseconds / 1e3;
|
|
1580
|
+
}
|
|
1581
|
+
//#endregion
|
|
1582
|
+
//#region src/observability/ai/v7/telemetry.ts
|
|
1583
|
+
/**
|
|
1584
|
+
* Creates an AI SDK v7 `Telemetry` object that projects callback events into
|
|
1585
|
+
* Cloudflare-compatible GenAI spans without recording raw prompts or outputs.
|
|
1586
|
+
*/
|
|
1587
|
+
function createAISDKV7Telemetry(instrumentation) {
|
|
1588
|
+
const storeMessages = instrumentation.options?.storeMessages === true;
|
|
1589
|
+
const storeTools = instrumentation.options?.storeTools === true;
|
|
1590
|
+
const operations = /* @__PURE__ */ new Map();
|
|
1591
|
+
const modelSpans = /* @__PURE__ */ new Map();
|
|
1592
|
+
const toolSpans = /* @__PURE__ */ new Map();
|
|
1593
|
+
const toolSpanKey = (callId, toolCallId) => `${callId}:${toolCallId}`;
|
|
1594
|
+
const finishOperation = (event) => {
|
|
1595
|
+
const state = operations.get(event.callId);
|
|
1596
|
+
if (!state) return;
|
|
1597
|
+
finishOpenModelSpans(event.callId, void 0, modelSpans, instrumentation.tracer);
|
|
1598
|
+
finishOpenToolSpans(event.callId, void 0, toolSpans, instrumentation.tracer);
|
|
1599
|
+
state.span.finish(finishAttributesFromEvent(event));
|
|
1600
|
+
operations.delete(event.callId);
|
|
1601
|
+
};
|
|
1602
|
+
return {
|
|
1603
|
+
onStart(event) {
|
|
1604
|
+
const operationName = supportedOperationName(operationNameFromId(event.operationId));
|
|
1605
|
+
if (!operationName) return;
|
|
1606
|
+
const span = operationSpan({
|
|
1607
|
+
attributes: {
|
|
1608
|
+
...correlationAttributes({ callId: event.callId }),
|
|
1609
|
+
...runtimeContextAttributes(event.runtimeContext)
|
|
1610
|
+
},
|
|
1611
|
+
context: semanticContextFromEvent(event),
|
|
1612
|
+
integration: "ai-sdk",
|
|
1613
|
+
model: readString(event.modelId),
|
|
1614
|
+
operation: operationName,
|
|
1615
|
+
provider: readString(event.provider),
|
|
1616
|
+
request: requestSummaryFromEvent(event, operationName)
|
|
1617
|
+
});
|
|
1618
|
+
const operation = instrumentation.tracer.openSpan(span.name, span.attributes, (activeSpan) => activeSpan);
|
|
1619
|
+
operations.set(event.callId, {
|
|
1620
|
+
callId: event.callId,
|
|
1621
|
+
operationName,
|
|
1622
|
+
span: operation
|
|
1623
|
+
});
|
|
1624
|
+
},
|
|
1625
|
+
onLanguageModelCallStart(event) {
|
|
1626
|
+
const state = operations.get(event.callId);
|
|
1627
|
+
if (!state) return;
|
|
1628
|
+
const span = modelCallSpan({
|
|
1629
|
+
attributes: {
|
|
1630
|
+
...correlationAttributes({ callId: event.callId }),
|
|
1631
|
+
...inputMessageAttributes(event, storeMessages)
|
|
1632
|
+
},
|
|
1633
|
+
integration: "ai-sdk",
|
|
1634
|
+
model: readString(event.modelId),
|
|
1635
|
+
operation: isStreamOperation(state.operationName) ? "doStream" : "doGenerate",
|
|
1636
|
+
provider: readString(event.provider),
|
|
1637
|
+
request: requestSummaryFromEvent(event, state.operationName)
|
|
1638
|
+
});
|
|
1639
|
+
const spans = modelSpans.get(event.callId) ?? [];
|
|
1640
|
+
spans.push({ spanSpec: span });
|
|
1641
|
+
modelSpans.set(event.callId, spans);
|
|
1642
|
+
},
|
|
1643
|
+
onLanguageModelCallEnd(event) {
|
|
1644
|
+
const state = shiftModelSpan(modelSpans, event.callId);
|
|
1645
|
+
if (!state) return;
|
|
1646
|
+
(state.span ?? instrumentation.tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (activeSpan) => activeSpan)).finish({
|
|
1647
|
+
...finishAttributesFromEvent(event, {
|
|
1648
|
+
includeAIGatewayLog: true,
|
|
1649
|
+
includePerformance: true,
|
|
1650
|
+
includeResponse: true
|
|
1651
|
+
}),
|
|
1652
|
+
...outputMessageAttributes(event, storeMessages)
|
|
1653
|
+
});
|
|
1654
|
+
},
|
|
1655
|
+
onToolExecutionStart(event) {
|
|
1656
|
+
const toolCallId = readString(event.toolCall.toolCallId);
|
|
1657
|
+
if (toolCallId === void 0 || !operations.has(event.callId)) return;
|
|
1658
|
+
const toolName = readString(event.toolCall.toolName) ?? "tool";
|
|
1659
|
+
const span = toolCallSpan({
|
|
1660
|
+
integration: "ai-sdk",
|
|
1661
|
+
operation: "tool.execute",
|
|
1662
|
+
toolName
|
|
1663
|
+
});
|
|
1664
|
+
toolSpans.set(toolSpanKey(event.callId, toolCallId), {
|
|
1665
|
+
callId: event.callId,
|
|
1666
|
+
spanSpec: {
|
|
1667
|
+
name: span.name,
|
|
1668
|
+
attributes: {
|
|
1669
|
+
...span.attributes,
|
|
1670
|
+
...correlationAttributes({
|
|
1671
|
+
callId: event.callId,
|
|
1672
|
+
toolCallId
|
|
1673
|
+
}),
|
|
1674
|
+
...toolInputAttributes(event.toolCall.input, storeTools),
|
|
1675
|
+
...toolContextAttributes(toolName, event.toolContext)
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
},
|
|
1680
|
+
onToolExecutionEnd(event) {
|
|
1681
|
+
const toolCallId = readString(event.toolCall.toolCallId);
|
|
1682
|
+
if (toolCallId === void 0) return;
|
|
1683
|
+
const state = toolSpans.get(toolSpanKey(event.callId, toolCallId));
|
|
1684
|
+
if (!state) return;
|
|
1685
|
+
const span = state.span ?? instrumentation.tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (activeSpan) => activeSpan);
|
|
1686
|
+
if (event.toolOutput?.type === "tool-error") span.fail(event.toolOutput.error);
|
|
1687
|
+
else span.finish(toolOutputAttributes(event.toolOutput?.output, storeTools));
|
|
1688
|
+
toolSpans.delete(toolSpanKey(event.callId, toolCallId));
|
|
1689
|
+
},
|
|
1690
|
+
onAbort(event) {
|
|
1691
|
+
const cause = { name: "AbortError" };
|
|
1692
|
+
finishOpenModelSpans(event.callId, cause, modelSpans, instrumentation.tracer);
|
|
1693
|
+
finishOpenToolSpans(event.callId, cause, toolSpans, instrumentation.tracer);
|
|
1694
|
+
const state = operations.get(event.callId);
|
|
1695
|
+
if (!state) return;
|
|
1696
|
+
state.span.fail(cause);
|
|
1697
|
+
operations.delete(event.callId);
|
|
1698
|
+
},
|
|
1699
|
+
onEnd: finishOperation,
|
|
1700
|
+
onError(event) {
|
|
1701
|
+
const errorEvent = eventObject(event);
|
|
1702
|
+
const callId = readString(errorEvent.callId);
|
|
1703
|
+
if (callId === void 0) return;
|
|
1704
|
+
const cause = errorEvent.error ?? event;
|
|
1705
|
+
finishOpenModelSpans(callId, cause, modelSpans, instrumentation.tracer);
|
|
1706
|
+
finishOpenToolSpans(callId, cause, toolSpans, instrumentation.tracer);
|
|
1707
|
+
const state = operations.get(callId);
|
|
1708
|
+
if (!state) return;
|
|
1709
|
+
state.span.fail(cause);
|
|
1710
|
+
operations.delete(callId);
|
|
1711
|
+
},
|
|
1712
|
+
executeLanguageModelCall(options) {
|
|
1713
|
+
const state = modelSpans.get(options.callId)?.find((candidate) => candidate.span === void 0);
|
|
1714
|
+
if (!state) return options.execute();
|
|
1715
|
+
return instrumentation.tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (span) => {
|
|
1716
|
+
state.span = span;
|
|
1717
|
+
try {
|
|
1718
|
+
return Promise.resolve(options.execute()).catch((cause) => {
|
|
1719
|
+
writeSpanAttributes(span, aiGatewayLogAttributes(extractAIGatewayLogId(cause)));
|
|
1720
|
+
span.fail(cause);
|
|
1721
|
+
removeModelState(modelSpans, options.callId, state);
|
|
1722
|
+
throw cause;
|
|
1723
|
+
});
|
|
1724
|
+
} catch (cause) {
|
|
1725
|
+
writeSpanAttributes(span, aiGatewayLogAttributes(extractAIGatewayLogId(cause)));
|
|
1726
|
+
span.fail(cause);
|
|
1727
|
+
removeModelState(modelSpans, options.callId, state);
|
|
1728
|
+
throw cause;
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
},
|
|
1732
|
+
executeTool(options) {
|
|
1733
|
+
const state = toolSpans.get(toolSpanKey(options.callId, options.toolCallId));
|
|
1734
|
+
if (!state) return options.execute();
|
|
1735
|
+
return instrumentation.tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (span) => {
|
|
1736
|
+
state.span = span;
|
|
1737
|
+
try {
|
|
1738
|
+
return Promise.resolve(options.execute()).catch((cause) => {
|
|
1739
|
+
span.fail(cause);
|
|
1740
|
+
toolSpans.delete(toolSpanKey(options.callId, options.toolCallId));
|
|
1741
|
+
throw cause;
|
|
1742
|
+
});
|
|
1743
|
+
} catch (cause) {
|
|
1744
|
+
span.fail(cause);
|
|
1745
|
+
toolSpans.delete(toolSpanKey(options.callId, options.toolCallId));
|
|
1746
|
+
throw cause;
|
|
1747
|
+
}
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
function supportedOperationName(operationName) {
|
|
1753
|
+
if (operationName === "generateObject" || operationName === "generateText" || operationName === "streamObject" || operationName === "streamText") return operationName;
|
|
1754
|
+
}
|
|
1755
|
+
function isStreamOperation(operationName) {
|
|
1756
|
+
return operationName === "streamObject" || operationName === "streamText";
|
|
1757
|
+
}
|
|
1758
|
+
function shiftModelSpan(spansByCallId, callId) {
|
|
1759
|
+
const spans = spansByCallId.get(callId);
|
|
1760
|
+
const span = spans?.shift();
|
|
1761
|
+
if (spans && spans.length === 0) spansByCallId.delete(callId);
|
|
1762
|
+
return span;
|
|
1763
|
+
}
|
|
1764
|
+
function removeModelState(statesByCallId, callId, state) {
|
|
1765
|
+
const states = statesByCallId.get(callId);
|
|
1766
|
+
if (!states) return;
|
|
1767
|
+
const index = states.indexOf(state);
|
|
1768
|
+
if (index !== -1) states.splice(index, 1);
|
|
1769
|
+
if (states.length === 0) statesByCallId.delete(callId);
|
|
1770
|
+
}
|
|
1771
|
+
function finishOpenModelSpans(callId, cause, spansByCallId, tracer) {
|
|
1772
|
+
const states = spansByCallId.get(callId);
|
|
1773
|
+
if (!states) return;
|
|
1774
|
+
for (const state of states) {
|
|
1775
|
+
const span = state.span ?? tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (activeSpan) => activeSpan);
|
|
1776
|
+
if (cause === void 0) span.finish();
|
|
1777
|
+
else span.fail(cause);
|
|
1778
|
+
}
|
|
1779
|
+
spansByCallId.delete(callId);
|
|
1780
|
+
}
|
|
1781
|
+
function finishOpenToolSpans(callId, cause, spansByToolCallId, tracer) {
|
|
1782
|
+
for (const [toolCallId, state] of spansByToolCallId) {
|
|
1783
|
+
if (state.callId !== callId) continue;
|
|
1784
|
+
const span = state.span ?? tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (activeSpan) => activeSpan);
|
|
1785
|
+
if (cause === void 0) span.finish();
|
|
1786
|
+
else span.fail(cause);
|
|
1787
|
+
spansByToolCallId.delete(toolCallId);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
function eventObject(event) {
|
|
1791
|
+
return typeof event === "object" && event !== null ? event : {};
|
|
1792
|
+
}
|
|
1793
|
+
const SEMANTIC_CONTEXT_KEYS = /* @__PURE__ */ new Set([
|
|
1794
|
+
"agentId",
|
|
1795
|
+
"agentName",
|
|
1796
|
+
"agentVersion",
|
|
1797
|
+
"conversationId",
|
|
1798
|
+
"gen_ai.agent.id",
|
|
1799
|
+
"gen_ai.agent.name",
|
|
1800
|
+
"gen_ai.agent.version",
|
|
1801
|
+
"gen_ai.conversation.id"
|
|
1802
|
+
]);
|
|
1803
|
+
function runtimeContextAttributes(runtimeContextValue) {
|
|
1804
|
+
const attributes = {};
|
|
1805
|
+
const runtimeContext = recordValue(runtimeContextValue);
|
|
1806
|
+
for (const [key, value] of Object.entries(runtimeContext ?? {})) if (!SEMANTIC_CONTEXT_KEYS.has(key) && isScalarAttributeValue(value)) attributes[`cloudflare.agents.runtime_context.${key}`] = value;
|
|
1807
|
+
return attributes;
|
|
1808
|
+
}
|
|
1809
|
+
function toolContextAttributes(toolName, toolContextValue) {
|
|
1810
|
+
const attributes = {};
|
|
1811
|
+
const toolContext = recordValue(toolContextValue);
|
|
1812
|
+
for (const [key, value] of Object.entries(toolContext ?? {})) if (isScalarAttributeValue(value)) attributes[`cloudflare.agents.tool_context.${toolName}.${key}`] = value;
|
|
1813
|
+
return attributes;
|
|
1814
|
+
}
|
|
1815
|
+
function recordValue(value) {
|
|
1816
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
1817
|
+
}
|
|
1818
|
+
function isScalarAttributeValue(value) {
|
|
1819
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
1820
|
+
}
|
|
1821
|
+
//#endregion
|
|
1822
|
+
//#region src/observability/ai/index.ts
|
|
1823
|
+
/**
|
|
1824
|
+
* Wraps an AI SDK namespace with tracing.
|
|
1825
|
+
*/
|
|
1826
|
+
function wrapAISDK(ai, options = {}) {
|
|
1827
|
+
return createAISDKV6Wrapper(ai, {
|
|
1828
|
+
options,
|
|
1829
|
+
tracer
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Creates an AI SDK v7 telemetry adapter for use with `registerTelemetry` or
|
|
1834
|
+
* per-call telemetry configuration.
|
|
1835
|
+
*/
|
|
1836
|
+
function createAISDKTelemetry(options = {}) {
|
|
1837
|
+
return createAISDKV7Telemetry({
|
|
1838
|
+
options,
|
|
1839
|
+
tracer
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
//#endregion
|
|
1843
|
+
export { createAISDKTelemetry, wrapAISDK };
|
|
1844
|
+
|
|
1845
|
+
//# sourceMappingURL=index.js.map
|