agents
Version:
A home for your AI agents
1,973 lines • 80.6 kB
JavaScript
import { __DO_NOT_USE_WILL_BREAK__agentContext } from "../../internal_context.js";
import { r as writeSpanAttributes, t as tracer } from "../../cloudflare-BduZwmYK.js";
import { AsyncLocalStorage } from "node:async_hooks";
//#region src/observability/ai/read.ts
/** Narrows an unknown value to a string. */
function readString(value) {
return typeof value === "string" ? value : void 0;
}
/** Narrows an unknown value to a number. */
function readNumber(value) {
return typeof value === "number" ? value : void 0;
}
/** AI SDK token counts are either a plain number or `{ total?: number, ... }`. */
function readTokenCount(value) {
if (typeof value === "number") return value;
if (typeof value === "object" && value !== null) {
const total = value.total;
return typeof total === "number" ? total : void 0;
}
}
/** Reads a numeric sub-field from a nested AI SDK token count object. */
function readNestedTokenField(value, key) {
if (typeof value !== "object" || value === null) return;
const nested = value[key];
return typeof nested === "number" ? nested : void 0;
}
//#endregion
//#region src/observability/genai/attributes.ts
/**
* Local trace attribute keys used by the Cloudflare GenAI projection.
*
* `gen_ai.*` keys follow OpenTelemetry GenAI semantic conventions where they
* exist (Development status — tracked so spec churn stays an internal edit;
* none of these constants are exported from the package). Keys with no
* semconv home live under the `cloudflare.agents.*` vendor namespace — never
* bare top-level keys, never `ai.*` (the Vercel AI SDK's de-facto namespace).
*/
const TraceAttribute = {
Cloudflare: {
AIGatewayLogID: "cloudflare.ai_gateway.log.id",
CallID: "cloudflare.agents.call.id",
IntegrationName: "cloudflare.agents.integration.name",
MetadataPrefix: "cloudflare.agents.metadata.",
OperationName: "cloudflare.agents.operation.name",
ResponseFinishReason: "cloudflare.agents.response.finish_reason",
RuntimeContextPrefix: "cloudflare.agents.runtime_context.",
ToolApprovalState: "cloudflare.agents.tool.approval.state",
ToolCount: "cloudflare.agents.tool.count",
TurnAdmission: "cloudflare.agents.turn.admission",
TurnChannel: "cloudflare.agents.turn.channel",
TurnContinuation: "cloudflare.agents.turn.continuation",
TurnGeneration: "cloudflare.agents.turn.generation",
TurnRequestID: "cloudflare.agents.turn.request_id",
TurnTrigger: "cloudflare.agents.turn.trigger",
UsageTotalTokens: "cloudflare.agents.usage.total_tokens"
},
General: { UserID: "user.id" },
GenAI: {
AgentID: "gen_ai.agent.id",
AgentName: "gen_ai.agent.name",
AgentVersion: "gen_ai.agent.version",
ConversationID: "gen_ai.conversation.id",
InputMessages: "gen_ai.input.messages",
OperationName: "gen_ai.operation.name",
OperationNameValueChat: "chat",
OperationNameValueExecuteTool: "execute_tool",
OperationNameValueInvokeAgent: "invoke_agent",
OutputMessages: "gen_ai.output.messages",
OutputType: "gen_ai.output.type",
ProviderName: "gen_ai.provider.name",
RequestFrequencyPenalty: "gen_ai.request.frequency_penalty",
RequestMaxTokens: "gen_ai.request.max_tokens",
RequestModel: "gen_ai.request.model",
RequestPresencePenalty: "gen_ai.request.presence_penalty",
RequestSeed: "gen_ai.request.seed",
RequestStream: "gen_ai.request.stream",
RequestTemperature: "gen_ai.request.temperature",
RequestTopK: "gen_ai.request.top_k",
RequestTopP: "gen_ai.request.top_p",
ResponseID: "gen_ai.response.id",
ResponseModel: "gen_ai.response.model",
ResponseTimeToFirstChunk: "gen_ai.response.time_to_first_chunk",
ToolCallArguments: "gen_ai.tool.call.arguments",
ToolCallID: "gen_ai.tool.call.id",
ToolCallResult: "gen_ai.tool.call.result",
ToolName: "gen_ai.tool.name",
ToolType: "gen_ai.tool.type",
UsageCacheCreationInputTokens: "gen_ai.usage.cache_creation.input_tokens",
UsageCacheReadInputTokens: "gen_ai.usage.cache_read.input_tokens",
UsageInputTokens: "gen_ai.usage.input_tokens",
UsageOutputTokens: "gen_ai.usage.output_tokens",
UsageReasoningOutputTokens: "gen_ai.usage.reasoning.output_tokens"
}
};
//#endregion
//#region src/observability/genai/telemetry.ts
/**
* Builds a semconv-formula span name (`"{operation} {target}"`), falling back
* to the bare operation when the target is unavailable or the combined name
* exceeds the Workers Observability 64 UTF-8-byte budget. The full target
* remains available as an attribute; the stable query key is always
* `gen_ai.operation.name`, never the span name.
*/
function spanName(operation, target) {
if (!target) return operation;
const name = `${operation} ${target}`;
return new TextEncoder().encode(name).length <= 64 ? name : operation;
}
/**
* Normalizes an AI SDK provider identifier to the semconv
* `gen_ai.provider.name` enum where a member exists: sub-provider suffixes
* are stripped (`anthropic.messages` → `anthropic`) and known aliases mapped.
* Unknown providers pass through verbatim (semconv sanctions custom values).
*/
function normalizeProviderName(provider) {
if (provider === void 0) return;
const lower = provider.toLowerCase();
for (const [prefix, value] of [
["google.vertex", "gcp.vertex_ai"],
["google.generative-ai", "gcp.gemini"],
["google-vertex", "gcp.vertex_ai"],
["amazon-bedrock", "aws.bedrock"],
["azure-openai", "azure.ai.openai"],
["anthropic", "anthropic"],
["openai", "openai"],
["azure", "azure.ai.inference"],
["google", "gcp.gemini"],
["mistral", "mistral_ai"],
["cohere", "cohere"],
["bedrock", "aws.bedrock"],
["groq", "groq"],
["deepseek", "deepseek"],
["perplexity", "perplexity"],
["xai", "x_ai"]
]) if (lower === prefix || lower.startsWith(`${prefix}.`) || lower.startsWith(`${prefix}-`)) return value;
return provider;
}
/**
* Reserved telemetry-metadata keys that map to dedicated attributes on the
* operation root span. Everything else scalar passes through under
* `cloudflare.agents.metadata.{key}`; identity keys are consumed by
* SemanticContext extraction and never passed through.
*/
const RESERVED_METADATA_ATTRIBUTES = {
[TraceAttribute.Cloudflare.TurnAdmission]: TraceAttribute.Cloudflare.TurnAdmission,
[TraceAttribute.Cloudflare.TurnChannel]: TraceAttribute.Cloudflare.TurnChannel,
[TraceAttribute.Cloudflare.TurnContinuation]: TraceAttribute.Cloudflare.TurnContinuation,
[TraceAttribute.Cloudflare.TurnGeneration]: TraceAttribute.Cloudflare.TurnGeneration,
[TraceAttribute.Cloudflare.TurnRequestID]: TraceAttribute.Cloudflare.TurnRequestID,
[TraceAttribute.Cloudflare.TurnTrigger]: TraceAttribute.Cloudflare.TurnTrigger,
[TraceAttribute.General.UserID]: TraceAttribute.General.UserID
};
const CONSUMED_METADATA_KEYS = /* @__PURE__ */ new Set([
"agentId",
"agentName",
"agentVersion",
"conversationId",
"gen_ai.agent.id",
"gen_ai.agent.name",
"gen_ai.agent.version",
"gen_ai.conversation.id"
]);
/**
* Projects a per-call telemetry record onto root span attributes: reserved
* keys map to their dedicated attributes, any other SCALAR entry passes
* through under `passthroughPrefix`, and object/array values are dropped
* (scalar-only attribute rule).
*
* v6 passes `experimental_telemetry.metadata` and keeps the default prefix;
* v7 has no metadata option and passes the included subset of `runtimeContext`
* under its own prefix. Reserved keys land on the same attribute either way,
* so turn identity does not move namespace between SDK majors.
*/
function metadataAttributes(metadata, passthroughPrefix = TraceAttribute.Cloudflare.MetadataPrefix) {
if (metadata === void 0) return {};
const attributes = {};
for (const [key, value] of Object.entries(metadata)) {
if (CONSUMED_METADATA_KEYS.has(key)) continue;
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") continue;
const reserved = Object.hasOwn(RESERVED_METADATA_ATTRIBUTES, key) ? RESERVED_METADATA_ATTRIBUTES[key] : void 0;
attributes[reserved ?? `${passthroughPrefix}${key}`] = value;
}
return attributes;
}
/**
* Cheap root-span name for an agent operation: needs only the agent name (the
* one value instrumentation may read before the sampling check), never the
* full attribute spec.
*/
function operationSpanName(agentName) {
return spanName(TraceAttribute.GenAI.OperationNameValueInvokeAgent, agentName);
}
/** Builds the root span for an SDK operation such as generateText or streamText. */
function operationSpan(input) {
return {
attributes: {
...input.attributes,
[TraceAttribute.Cloudflare.IntegrationName]: input.integration,
[TraceAttribute.Cloudflare.OperationName]: input.operation,
[TraceAttribute.GenAI.AgentID]: input.context?.agentId,
[TraceAttribute.GenAI.AgentName]: input.context?.agentName,
[TraceAttribute.GenAI.AgentVersion]: input.context?.agentVersion,
[TraceAttribute.GenAI.ConversationID]: input.context?.conversationId,
[TraceAttribute.GenAI.OperationName]: TraceAttribute.GenAI.OperationNameValueInvokeAgent,
[TraceAttribute.GenAI.ProviderName]: normalizeProviderName(input.provider),
...requestAttributes(input.request, input.model)
},
name: spanName(TraceAttribute.GenAI.OperationNameValueInvokeAgent, input.context?.agentName)
};
}
/** Builds the child span for an underlying model call. */
function modelCallSpan(input) {
return {
attributes: {
...input.attributes,
[TraceAttribute.Cloudflare.IntegrationName]: input.integration,
[TraceAttribute.Cloudflare.OperationName]: input.operation,
[TraceAttribute.GenAI.OperationName]: TraceAttribute.GenAI.OperationNameValueChat,
[TraceAttribute.GenAI.ProviderName]: normalizeProviderName(input.provider),
...requestAttributes(input.request, input.model)
},
name: spanName(TraceAttribute.GenAI.OperationNameValueChat, input.model)
};
}
function requestAttributes(request, model) {
return {
[TraceAttribute.GenAI.OutputType]: request?.outputType,
[TraceAttribute.GenAI.RequestFrequencyPenalty]: request?.frequencyPenalty,
[TraceAttribute.GenAI.RequestMaxTokens]: request?.maxTokens,
[TraceAttribute.GenAI.RequestModel]: model,
[TraceAttribute.GenAI.RequestPresencePenalty]: request?.presencePenalty,
[TraceAttribute.GenAI.RequestSeed]: request?.seed,
[TraceAttribute.GenAI.RequestStream]: request?.stream === true ? true : void 0,
[TraceAttribute.GenAI.RequestTemperature]: request?.temperature,
[TraceAttribute.GenAI.RequestTopK]: request?.topK,
[TraceAttribute.GenAI.RequestTopP]: request?.topP
};
}
/** Builds the child span for a tool execution. */
function toolCallSpan(input) {
return {
attributes: {
[TraceAttribute.Cloudflare.IntegrationName]: input.integration,
[TraceAttribute.Cloudflare.OperationName]: input.operation,
[TraceAttribute.GenAI.OperationName]: TraceAttribute.GenAI.OperationNameValueExecuteTool,
[TraceAttribute.GenAI.ToolCallID]: input.toolCallId,
[TraceAttribute.GenAI.ToolName]: input.toolName,
[TraceAttribute.GenAI.ToolType]: "function"
},
name: spanName(TraceAttribute.GenAI.OperationNameValueExecuteTool, input.toolName)
};
}
/** Builds a bounded child span for one tool-approval lifecycle segment. */
function toolApprovalSpan(input) {
return {
attributes: {
[TraceAttribute.Cloudflare.IntegrationName]: "ai-sdk",
[TraceAttribute.Cloudflare.OperationName]: "tool.approval",
[TraceAttribute.Cloudflare.ToolApprovalState]: input.state,
[TraceAttribute.GenAI.OperationName]: TraceAttribute.GenAI.OperationNameValueExecuteTool,
[TraceAttribute.GenAI.ToolCallID]: input.toolCallId,
[TraceAttribute.GenAI.ToolName]: input.toolName,
[TraceAttribute.GenAI.ToolType]: "function"
},
name: spanName("tool_approval", input.toolName)
};
}
/** Projects a completed model operation into canonical finish attributes. */
function finishAttributes(input) {
return {
[TraceAttribute.Cloudflare.AIGatewayLogID]: input.aiGatewayLogId,
[TraceAttribute.Cloudflare.ResponseFinishReason]: input.finishReason,
[TraceAttribute.Cloudflare.ToolCount]: input.toolCallCount,
[TraceAttribute.Cloudflare.UsageTotalTokens]: totalTokens(input.usage),
[TraceAttribute.GenAI.ResponseID]: input.response?.id,
[TraceAttribute.GenAI.ResponseModel]: input.response?.model,
[TraceAttribute.GenAI.ResponseTimeToFirstChunk]: input.timeToFirstChunkSeconds,
[TraceAttribute.GenAI.UsageCacheCreationInputTokens]: input.usage?.cacheCreationInputTokens,
[TraceAttribute.GenAI.UsageCacheReadInputTokens]: input.usage?.cacheReadInputTokens,
[TraceAttribute.GenAI.UsageInputTokens]: input.usage?.inputTokens,
[TraceAttribute.GenAI.UsageOutputTokens]: input.usage?.outputTokens,
[TraceAttribute.GenAI.UsageReasoningOutputTokens]: input.usage?.reasoningTokens
};
}
/** Attribute projection for an AI Gateway log reference discovered on error. */
function aiGatewayLogAttributes(aiGatewayLogId) {
return { [TraceAttribute.Cloudflare.AIGatewayLogID]: aiGatewayLogId };
}
function totalTokens(usage) {
if (usage?.totalTokens !== void 0) return usage.totalTokens;
return usage?.inputTokens !== void 0 && usage.outputTokens !== void 0 ? usage.inputTokens + usage.outputTokens : void 0;
}
//#endregion
//#region src/observability/ai/v6/extract.ts
function finishAttributesFromResult(result, options = {}) {
return finishAttributes({
aiGatewayLogId: options.aiGatewayLogId,
finishReason: extractFinishReason$1(result),
response: options.includeResponse ? extractResponseInfo(result) : void 0,
toolCallCount: extractToolCallCount(result),
usage: extractAISDKv6TokenUsage(result)
});
}
function extractRequestSummary(params, operation) {
return {
frequencyPenalty: readNumber(params.frequencyPenalty),
maxTokens: readNumber(params.maxOutputTokens ?? params.maxTokens),
outputType: operation === "generateObject" || operation === "streamObject" ? "json" : "text",
presencePenalty: readNumber(params.presencePenalty),
seed: readNumber(params.seed),
stream: operation === "streamText" || operation === "streamObject",
temperature: readNumber(params.temperature),
topK: readNumber(params.topK),
topP: readNumber(params.topP)
};
}
/** Extracts model identity from an AI SDK v6 model object. */
function extractModelInfo(value) {
if (typeof value === "string") return value.length > 0 ? {
modelId: value,
provider: void 0
} : void 0;
if (typeof value !== "object" || value === null) return;
const record = value;
const modelId = typeof record.modelId === "string" ? record.modelId : void 0;
const provider = typeof record.provider === "string" ? record.provider : void 0;
if (modelId === void 0 && provider === void 0) return;
return {
modelId,
provider
};
}
/**
* Extracts token usage from an AI SDK v6 result or stream chunk.
*
* AI SDK v6 exposes usage as `{ inputTokens, outputTokens, totalTokens }`
* where `inputTokens`/`outputTokens` may be plain numbers or nested objects
* like `{ total, cacheRead, cacheWrite }` / `{ total, reasoning }`.
*/
function extractAISDKv6TokenUsage(value) {
if (typeof value !== "object" || value === null) return;
const record = value;
const raw = record.totalUsage ?? record.usage;
if (typeof raw !== "object" || raw === null) return;
const usage = raw;
const inputTokens = readTokenCount(usage.inputTokens);
const outputTokens = readTokenCount(usage.outputTokens);
const totalTokens = readNumber(usage.totalTokens);
const cacheReadInputTokens = readNestedTokenField(usage.inputTokenDetails, "cacheReadTokens") ?? readNestedTokenField(usage.inputTokens, "cacheRead") ?? readNumber(usage.cachedInputTokens);
const cacheCreationInputTokens = readNestedTokenField(usage.inputTokenDetails, "cacheWriteTokens") ?? readNestedTokenField(usage.inputTokens, "cacheWrite");
const reasoningTokens = readNestedTokenField(usage.outputTokenDetails, "reasoningTokens") ?? readNestedTokenField(usage.outputTokens, "reasoning") ?? readNumber(usage.reasoningTokens);
if (inputTokens === void 0 && outputTokens === void 0 && totalTokens === void 0 && cacheReadInputTokens === void 0 && cacheCreationInputTokens === void 0 && reasoningTokens === void 0) return;
return {
...cacheCreationInputTokens !== void 0 ? { cacheCreationInputTokens } : {},
...cacheReadInputTokens !== void 0 ? { cacheReadInputTokens } : {},
...inputTokens !== void 0 ? { inputTokens } : {},
...outputTokens !== void 0 ? { outputTokens } : {},
...reasoningTokens !== void 0 ? { reasoningTokens } : {},
...totalTokens !== void 0 ? { totalTokens } : {}
};
}
function extractToolCallCount(value) {
if (typeof value !== "object" || value === null) return;
const toolCalls = value.toolCalls;
return Array.isArray(toolCalls) && toolCalls.length > 0 ? toolCalls.length : void 0;
}
/**
* Reads a finish reason from an AI SDK v6 result or `finish`-type stream chunk.
*/
function extractFinishReason$1(value) {
if (typeof value !== "object" || value === null) return;
const finishReason = value.finishReason;
if (typeof finishReason === "string") return finishReason;
if (typeof finishReason === "object" && finishReason !== null) {
const unified = finishReason.unified;
return typeof unified === "string" ? unified : void 0;
}
}
function extractResponseInfo(value) {
if (typeof value !== "object" || value === null) return;
const record = value;
if (record.type === "response-metadata") {
const id = readString(record.id);
const model = readString(record.modelId);
if (id === void 0 && model === void 0) return;
return {
...id !== void 0 ? { id } : {},
...model !== void 0 ? { model } : {}
};
}
const response = typeof record.response === "object" && record.response !== null ? record.response : void 0;
const id = readString(record.responseId ?? response?.id);
const model = readString(record.responseModel ?? response?.modelId ?? response?.model);
if (id === void 0 && model === void 0) return;
return {
...id !== void 0 ? { id } : {},
...model !== void 0 ? { model } : {}
};
}
//#endregion
//#region src/observability/ai/ai-gateway.ts
const MAX_AI_GATEWAY_LOG_ID_BYTES = 256;
const AI_GATEWAY_CONTAINER_KEYS = /* @__PURE__ */ new Set([
"aigateway",
"binding",
"cause",
"cloudflare",
"config",
"context",
"error",
"gateway",
"providermetadata",
"rawresponse",
"response",
"workersai"
]);
/**
* Reads an AI Gateway log id from explicit provider surfaces only: response
* headers, provider metadata, gateway errors, or a Workers AI binding. The
* walk is bounded and uses data-property descriptors, so telemetry cannot get
* stuck on cycles or invoke arbitrary application getters. Unknown shapes fail
* open and simply omit the attribute.
*/
function extractAIGatewayLogId(value) {
const seen = /* @__PURE__ */ new Set();
let visited = 0;
const visit = (candidate, depth, gatewayScoped, providerMetadata, responseScoped) => {
if (candidate === null || candidate === void 0 || depth > 6 || typeof candidate !== "object" && typeof candidate !== "function" || seen.has(candidate) || visited >= 200) return;
seen.add(candidate);
visited += 1;
if (typeof Response !== "undefined" && candidate instanceof Response) return readHeaderLogId(candidate.headers);
let descriptors;
try {
descriptors = Object.getOwnPropertyDescriptors(candidate);
} catch {
return;
}
const objectName = dataString(descriptors.name);
const scopedHere = gatewayScoped || objectName !== void 0 && isGatewayKey(normalizeKey(objectName));
for (const [key, descriptor] of Object.entries(descriptors)) {
if (!("value" in descriptor)) continue;
const normalizedKey = normalizeKey(key);
if (normalizedKey === "cfaiglogid" || normalizedKey === "aigatewaylogid") {
const logId = boundedAIGatewayLogId(descriptor.value);
if (logId !== void 0) return logId;
}
if (normalizedKey === "responseheaders" || normalizedKey === "headers" && responseScoped) {
const logId = readHeaderLogId(descriptor.value);
if (logId !== void 0) return logId;
}
if (normalizedKey === "logid" && scopedHere) {
const logId = boundedAIGatewayLogId(descriptor.value);
if (logId !== void 0) return logId;
}
}
for (const [key, descriptor] of Object.entries(descriptors)) {
if (!("value" in descriptor)) continue;
const normalizedKey = normalizeKey(key);
if (!providerMetadata && !AI_GATEWAY_CONTAINER_KEYS.has(normalizedKey)) continue;
const nested = visit(descriptor.value, depth + 1, scopedHere || isGatewayKey(normalizedKey), providerMetadata || normalizedKey === "providermetadata", normalizedKey === "response" || normalizedKey === "rawresponse");
if (nested !== void 0) return nested;
}
};
return visit(value, 0, false, false, false);
}
/**
* workers-ai-provider currently exposes the gateway ID on its `Ai` binding,
* not in the LanguageModel result. Clone only that known runtime shape and
* proxy `binding.run` so the ID is captured as that call settles instead of
* reading the binding's mutable latest value later when the span ends.
*/
function captureAIGatewayLogFromModel(model, provider) {
let logId;
const capture = {
model,
get: () => logId,
reset: () => {
logId = void 0;
}
};
if (!provider?.toLowerCase().startsWith("workersai")) return capture;
try {
const modelDescriptors = Object.getOwnPropertyDescriptors(model);
const configDescriptor = modelDescriptors.config;
if (!configDescriptor || !("value" in configDescriptor)) return capture;
const config = configDescriptor.value;
if (typeof config !== "object" || config === null) return capture;
const configDescriptors = Object.getOwnPropertyDescriptors(config);
const bindingDescriptor = configDescriptors.binding;
if (!bindingDescriptor || !("value" in bindingDescriptor)) return capture;
const binding = bindingDescriptor.value;
if (typeof binding !== "object" || binding === null || !("aiGatewayLogId" in binding) || typeof binding.run !== "function") return capture;
const bindingProxy = new Proxy(binding, { get(target, property, receiver) {
if (property !== "run") return Reflect.get(target, property, receiver);
return (...args) => {
const run = Reflect.get(target, property, target);
const previousLogId = extractAIGatewayLogId(target);
logId = void 0;
const captureResult = (result) => {
const resultLogId = extractAIGatewayLogId(result);
const currentLogId = extractAIGatewayLogId(target);
logId = resultLogId ?? (currentLogId !== previousLogId ? currentLogId : void 0);
};
try {
return Promise.resolve(Reflect.apply(run, target, args)).then((result) => {
captureResult(result);
return result;
}, (cause) => {
captureResult(cause);
throw cause;
});
} catch (cause) {
captureResult(cause);
throw cause;
}
};
} });
const configClone = Object.create(Object.getPrototypeOf(config), {
...configDescriptors,
binding: {
...bindingDescriptor,
value: bindingProxy
}
});
capture.model = Object.create(Object.getPrototypeOf(model), {
...modelDescriptors,
config: {
...configDescriptor,
value: configClone
}
});
} catch {}
return capture;
}
function readHeaderLogId(value) {
if (isHeadersLike(value)) try {
return boundedAIGatewayLogId(value.get("cf-aig-log-id"));
} catch {
return;
}
if (typeof value !== "object" || value === null) return;
let descriptors;
try {
descriptors = Object.getOwnPropertyDescriptors(value);
} catch {
return;
}
for (const [key, descriptor] of Object.entries(descriptors)) if ("value" in descriptor && key.toLowerCase() === "cf-aig-log-id") return boundedAIGatewayLogId(descriptor.value);
}
function isHeadersLike(value) {
if (typeof value !== "object" || value === null) return false;
try {
return "get" in value && typeof value.get === "function";
} catch {
return false;
}
}
function isGatewayKey(value) {
return value.includes("gateway") || value.includes("aig") || value.includes("workersai") || value === "cloudflare";
}
function normalizeKey(value) {
return value.toLowerCase().replaceAll(/[-_.]/g, "");
}
function dataString(descriptor) {
return descriptor && "value" in descriptor ? nonEmptyString(descriptor.value) : void 0;
}
function nonEmptyString(value) {
return typeof value === "string" && value.length > 0 ? value : void 0;
}
function boundedAIGatewayLogId(value) {
const id = nonEmptyString(value);
return id !== void 0 && new TextEncoder().encode(id).length <= MAX_AI_GATEWAY_LOG_ID_BYTES ? id : void 0;
}
//#endregion
//#region src/observability/ai/content.ts
const MAX_ATTRIBUTE_BYTES = 28 * 1024;
const PROTECTED_HEAD_MESSAGES = 2;
function inputMessageAttributes(value, enabled) {
if (!enabled || typeof value !== "object" || value === null) return {};
const record = value;
const messages = Array.isArray(record.prompt) ? record.prompt : Array.isArray(record.messages) ? record.messages : typeof record.prompt === "string" ? [{
role: "user",
content: record.prompt
}] : void 0;
return { [TraceAttribute.GenAI.InputMessages]: serializeMessages(messages === void 0 ? void 0 : formatInputMessages(messages)) };
}
function outputMessageAttributes(value, enabled) {
if (!enabled || typeof value !== "object" || value === null) return {};
const record = value;
const parts = outputParts(record);
const finishReason = readFinishReason(record);
return outputMessageAttributesFrom(parts.length > 0 || finishReason !== void 0 ? [outputMessage(parts, finishReason)] : void 0);
}
function outputMessageAttributesFrom(messages) {
return { [TraceAttribute.GenAI.OutputMessages]: serializeMessages(messages) };
}
function toolInputAttributes(value, enabled) {
return enabled ? { [TraceAttribute.GenAI.ToolCallArguments]: serialize(value) } : {};
}
function toolOutputAttributes(value, enabled) {
return enabled ? { [TraceAttribute.GenAI.ToolCallResult]: serialize(value) } : {};
}
function createStreamMessages() {
let text = "";
let reasoning = "";
const toolParts = [];
return {
messages(finishReason) {
const parts = [
...reasoning ? [{
type: "reasoning",
content: reasoning
}] : [],
...text ? [{
type: "text",
content: text
}] : [],
...toolParts
];
return parts.length > 0 || finishReason !== void 0 ? [outputMessage(parts, finishReason)] : void 0;
},
observe(chunk) {
if (typeof chunk !== "object" || chunk === null) return;
const record = chunk;
const delta = record.text ?? record.delta;
if (record.type === "text-delta" && typeof delta === "string") text += delta;
else if (record.type === "reasoning-delta" && typeof delta === "string") reasoning += delta;
else if (record.type === "tool-call" || record.type === "tool-result") {
const part = formatMessagePart(record);
if (part !== void 0) toolParts.push(part);
}
}
};
}
function formatInputMessages(messages) {
const formatted = [];
for (const message of messages) {
const next = formatInputMessage(message);
if (next !== void 0) formatted.push(next);
}
return formatted;
}
function formatInputMessage(value) {
if (typeof value !== "object" || value === null) return void 0;
const record = value;
if (typeof record.role !== "string") return void 0;
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);
const name = typeof record.name === "string" ? record.name : void 0;
return {
role: record.role,
parts,
...name !== void 0 ? { name } : {}
};
}
function outputParts(record) {
if (Array.isArray(record.content)) return record.content.map(formatMessagePart).filter((part) => part !== void 0);
const parts = [];
appendReasoningParts(parts, record.reasoning);
if (typeof record.text === "string" && record.text.length > 0) parts.push({
type: "text",
content: record.text
});
if (Array.isArray(record.toolCalls)) for (const toolCall of record.toolCalls) {
const part = formatMessagePart(toolCall);
if (part !== void 0) parts.push(part);
}
return parts;
}
function appendReasoningParts(parts, reasoning) {
if (typeof reasoning === "string" && reasoning.length > 0) {
parts.push({
type: "reasoning",
content: reasoning
});
return;
}
if (!Array.isArray(reasoning)) return;
for (const entry of reasoning) {
if (typeof entry !== "object" || entry === null) continue;
const text = entry.text;
if (typeof text === "string" && text.length > 0) parts.push({
type: "reasoning",
content: text
});
}
}
function formatMessagePart(value) {
if (typeof value === "string") return {
type: "text",
content: value
};
if (typeof value !== "object" || value === null) return void 0;
const record = value;
if (typeof record.type !== "string") return void 0;
switch (record.type) {
case "text":
case "reasoning": {
const content = record.content ?? record.text;
return typeof content === "string" ? {
type: record.type,
content
} : void 0;
}
case "tool-call":
case "tool_call": return formatToolCall(record);
case "tool-result":
case "tool_result":
case "tool_call_response": return formatToolCallResponse(record);
default: return { type: record.type.replaceAll("-", "_") };
}
}
function formatToolCall(record) {
const name = record.name ?? record.toolName;
if (typeof name !== "string") return void 0;
const id = record.id ?? record.toolCallId;
const args = record.arguments ?? record.input;
return {
type: "tool_call",
...typeof id === "string" ? { id } : {},
name,
...args !== void 0 ? { arguments: parseJson(args) } : {}
};
}
function formatToolCallResponse(record) {
const id = record.id ?? record.toolCallId;
const rawResponse = record.response ?? record.output ?? record.result ?? null;
return {
type: "tool_call_response",
...typeof id === "string" ? { id } : {},
response: toolResponse(rawResponse)
};
}
function toolResponse(value) {
if (typeof value !== "object" || value === null) return value;
const record = value;
switch (record.type) {
case "text":
case "error-text":
case "json":
case "error-json":
case "content": return record.value ?? null;
case "execution-denied": return {
denied: true,
...typeof record.reason === "string" ? { reason: record.reason } : {}
};
default: return value;
}
}
function outputMessage(parts, finishReason) {
return {
role: "assistant",
parts,
finish_reason: normalizeFinishReason(finishReason ?? "unknown")
};
}
function readFinishReason(record) {
const value = record.finishReason ?? record.finish_reason;
if (typeof value === "string") return value;
if (typeof value !== "object" || value === null) return void 0;
const unified = value.unified;
return typeof unified === "string" ? unified : void 0;
}
function normalizeFinishReason(value) {
switch (value) {
case "content-filter": return "content_filter";
case "tool-calls":
case "tool_calls": return "tool_call";
case "other":
case "unknown": return "stop";
default: return value;
}
}
function parseJson(value) {
if (typeof value !== "string") return value;
try {
return JSON.parse(value);
} catch {
return value;
}
}
function serializeMessages(messages) {
if (messages === void 0) return void 0;
const kept = [...messages];
while (true) {
const json = stringify(kept);
if (json === void 0) return void 0;
if (byteLength(json) <= MAX_ATTRIBUTE_BYTES) return json;
if (kept.length <= PROTECTED_HEAD_MESSAGES) return void 0;
kept.splice(PROTECTED_HEAD_MESSAGES, 1);
}
}
function serialize(value) {
const json = stringify(value);
return json !== void 0 && byteLength(json) <= MAX_ATTRIBUTE_BYTES ? json : void 0;
}
function stringify(value) {
if (value === void 0) return void 0;
try {
return JSON.stringify(value);
} catch {
return;
}
}
function byteLength(value) {
return new TextEncoder().encode(value).length;
}
//#endregion
//#region src/observability/ai/v6/streams.ts
function finishWhenStreamCompletes(result, span, options = {}) {
return patchStreamFields(result, {
onComplete: (summary) => {
span.finish(finishAttributesFromStreamSummary(summary, options.includeResponse === true, options.includeAIGatewayLog === true, options.aiGatewayLogId, options.storeMessages === true));
},
onError: (cause, observedAIGatewayLogId, observed) => {
if (options.includeAIGatewayLog) writeSpanAttributes(span, aiGatewayLogAttributes(observedAIGatewayLogId ?? extractAIGatewayLogId(cause) ?? options.aiGatewayLogId));
if (observed !== void 0) writeSpanAttributes(span, finishAttributesFromStreamSummary(observed, options.includeResponse === true, options.includeAIGatewayLog === true, options.aiGatewayLogId, options.storeMessages === true));
span.fail(cause);
}
}, options.startedAtMs, options.includeAIGatewayLog ? options.aiGatewayLogId : void 0, options.storeMessages === true);
}
function finishAttributesFromStreamSummary(summary, includeResponse, includeAIGatewayLog, initialAIGatewayLogId, storeMessages) {
return {
...finishAttributes({
aiGatewayLogId: includeAIGatewayLog ? summary?.aiGatewayLogId ?? initialAIGatewayLogId : void 0,
finishReason: summary?.finishReason,
response: includeResponse ? summary?.response : void 0,
timeToFirstChunkSeconds: summary?.timeToFirstChunkSeconds,
toolCallCount: summary?.toolCallCount,
usage: summary?.usage
}),
...outputMessageAttributesFrom(storeMessages ? summary?.outputMessages : void 0)
};
}
function patchStreamFields(result, hooks, startedAtMs, aiGatewayLogId, storeMessages) {
if (typeof result !== "object" || result === null) {
hooks.onComplete(void 0);
return result;
}
const record = result;
let patchedAny = false;
let closed = false;
const completeOnce = (summary) => {
if (closed) return;
closed = true;
hooks.onComplete(summary);
};
const errorOnce = (cause, observedAIGatewayLogId, observed) => {
if (closed) return;
closed = true;
hooks.onError(cause, observedAIGatewayLogId, observed);
};
try {
if (isReadableStream(record.baseStream)) {
Object.defineProperty(record, "baseStream", {
configurable: true,
enumerable: true,
value: wrapReadableStream(record.baseStream, {
onComplete: completeOnce,
onError: errorOnce
}, startedAtMs, aiGatewayLogId, storeMessages),
writable: true
});
return result;
}
const streamField = findStreamField(record, [
"partialObjectStream",
"textStream",
"fullStream",
"stream"
]);
if (streamField) {
Object.defineProperty(record, streamField.field, {
configurable: true,
enumerable: true,
value: streamField.kind === "readable" ? wrapReadableStream(streamField.stream, {
onComplete: completeOnce,
onError: errorOnce
}, startedAtMs, aiGatewayLogId, storeMessages) : wrapAsyncIterable(streamField.stream, {
onComplete: completeOnce,
onError: errorOnce
}, startedAtMs, aiGatewayLogId, storeMessages),
writable: true
});
patchedAny = true;
}
} catch {
patchedAny = false;
}
if (!patchedAny) {
hooks.onComplete(void 0);
return result;
}
return result;
}
function findStreamField(result, candidateFields) {
for (const field of candidateFields) try {
const stream = result[field];
if (isReadableStream(stream)) return {
field,
kind: "readable",
stream
};
if (isAsyncIterable$1(stream)) return {
field,
kind: "asyncIterable",
stream
};
} catch {}
}
function wrapReadableStream(stream, hooks, startedAtMs, aiGatewayLogId, storeMessages) {
let reader;
const state = createStreamState(hooks, startedAtMs, aiGatewayLogId, storeMessages);
return new ReadableStream({
async pull(controller) {
reader ??= stream.getReader();
try {
const result = await reader.read();
if (state.closed) return;
if (result.done) {
state.complete();
controller.close();
releaseReader();
return;
}
state.observeChunk(result.value);
controller.enqueue(result.value);
} catch (cause) {
if (!state.closed) {
state.fail(cause);
controller.error(cause);
}
releaseReader();
}
},
async cancel(reason) {
state.cancel();
try {
if (reader) {
await reader.cancel(reason);
return;
}
await stream.cancel(reason);
} catch (cause) {
state.fail(cause);
throw cause;
} finally {
releaseReader();
}
}
});
function releaseReader() {
if (!reader) return;
try {
reader.releaseLock();
} catch {} finally {
reader = void 0;
}
}
}
function wrapAsyncIterable(stream, hooks, startedAtMs, aiGatewayLogId, storeMessages) {
return { async *[Symbol.asyncIterator]() {
const state = createStreamState(hooks, startedAtMs, aiGatewayLogId, storeMessages);
try {
for await (const chunk of stream) {
state.observeChunk(chunk);
yield chunk;
}
state.complete();
} catch (cause) {
state.fail(cause);
throw cause;
} finally {
state.cancel();
}
} };
}
function createStreamState(hooks, startedAtMs, initialAIGatewayLogId, storeMessages) {
let closed = false;
let aiGatewayLogId = initialAIGatewayLogId;
let finishReason;
let toolCallCount = 0;
let usage;
let response;
let observedError;
let observedAbort = false;
let firstChunkAtMs;
const output = createStreamMessages();
/** What the stream reported before it stopped, complete or not. */
const observedSummary = () => streamSummaryFromParts({
aiGatewayLogId,
finishReason,
outputMessages: storeMessages ? output.messages(finishReason) : void 0,
response,
timeToFirstChunkSeconds: firstChunkAtMs === void 0 || startedAtMs === void 0 ? void 0 : (firstChunkAtMs - startedAtMs) / 1e3,
toolCallCount,
usage
});
const settleObserved = () => {
if (observedError) {
hooks.onError(observedError.cause, aiGatewayLogId, observedSummary());
return true;
}
if (observedAbort) {
hooks.onError({ name: "AbortError" }, aiGatewayLogId, observedSummary());
return true;
}
return false;
};
return {
get closed() {
return closed;
},
cancel() {
if (closed) return;
closed = true;
if (settleObserved()) return;
hooks.onComplete(void 0);
},
complete() {
if (closed) return;
closed = true;
if (settleObserved()) return;
hooks.onComplete(streamSummaryFromParts({
aiGatewayLogId,
finishReason,
outputMessages: storeMessages ? output.messages(finishReason) : void 0,
response,
timeToFirstChunkSeconds: firstChunkAtMs === void 0 || startedAtMs === void 0 ? void 0 : (firstChunkAtMs - startedAtMs) / 1e3,
toolCallCount,
usage
}));
},
fail(cause) {
if (closed) return;
closed = true;
hooks.onError(cause, aiGatewayLogId);
},
observeChunk(rawChunk) {
firstChunkAtMs ??= Date.now();
const chunk = unwrapChunkEnvelope(rawChunk);
aiGatewayLogId = extractAIGatewayLogId(chunk) ?? aiGatewayLogId;
if (storeMessages) output.observe(chunk);
if (isErrorChunk(chunk)) observedError = { cause: chunk.error };
if (isAbortChunk(chunk)) observedAbort = true;
if (isToolCallChunk(chunk)) toolCallCount += 1;
finishReason = extractFinishReason$1(chunk) ?? finishReason;
usage = extractAISDKv6TokenUsage(chunk) ?? usage;
response = extractResponseInfo(chunk) ?? response;
}
};
}
/**
* The streamText result's private `baseStream` carries `{ part, partialOutput }`
* envelopes rather than bare stream parts; `fullStream` and provider-level
* streams carry bare parts. Unwrap the envelope when present so chunk
* inspection sees the actual part in both cases.
*/
function unwrapChunkEnvelope(chunk) {
if (typeof chunk !== "object" || chunk === null) return chunk;
const part = chunk.part;
return typeof part === "object" && part !== null && "type" in part ? part : chunk;
}
function isErrorChunk(chunk) {
return typeof chunk === "object" && chunk !== null && chunk.type === "error";
}
function isAbortChunk(chunk) {
return typeof chunk === "object" && chunk !== null && chunk.type === "abort";
}
function streamSummaryFromParts(input) {
return {
...input.aiGatewayLogId !== void 0 ? { aiGatewayLogId: input.aiGatewayLogId } : {},
...input.finishReason !== void 0 ? { finishReason: input.finishReason } : {},
...input.outputMessages !== void 0 ? { outputMessages: input.outputMessages } : {},
...input.response ? { response: input.response } : {},
...input.timeToFirstChunkSeconds !== void 0 ? { timeToFirstChunkSeconds: input.timeToFirstChunkSeconds } : {},
...input.toolCallCount > 0 ? { toolCallCount: input.toolCallCount } : {},
...input.usage ? { usage: input.usage } : {}
};
}
function isReadableStream(value) {
return typeof value === "object" && value !== null && "pipeThrough" in value && typeof value.pipeThrough === "function" && "getReader" in value && typeof value.getReader === "function";
}
function isToolCallChunk(chunk) {
return typeof chunk === "object" && chunk !== null && chunk.type === "tool-call";
}
function isAsyncIterable$1(value) {
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
}
//#endregion
//#region src/observability/ai/v6/model.ts
function wrapModel(tracer, wrapLanguageModel, model, parentOperation, storeMessages, boundToInvocation = false) {
if (!wrapLanguageModel) return model;
if (typeof model !== "object" || model === null) return model;
const modelInfo = extractModelInfo(model);
const aiGatewayLog = captureAIGatewayLogFromModel(model, modelInfo?.provider);
return wrapLanguageModel({
model: aiGatewayLog.model,
middleware: {
wrapGenerate: async ({ doGenerate, params }) => {
const span = modelCallSpanForModel("doGenerate", modelInfo, params, parentOperation, storeMessages);
return tracer.withSpan(span.name, span.attributes, async (modelCall) => {
aiGatewayLog.reset();
try {
const result = await doGenerate();
modelCall.finish({
...finishAttributesFromResult(result, {
aiGatewayLogId: extractAIGatewayLogId(result) ?? aiGatewayLog.get(),
includeResponse: true
}),
...outputMessageAttributes(result, storeMessages)
});
return result;
} catch (cause) {
recordAIGatewayLogOnError(modelCall, cause, aiGatewayLog.get());
throw cause;
}
}, boundToInvocation ? { boundToInvocation: true } : void 0);
},
wrapStream: async ({ doStream, params }) => {
const span = modelCallSpanForModel("doStream", modelInfo, params, parentOperation, storeMessages);
return tracer.openSpan(span.name, span.attributes, async (modelCall) => {
aiGatewayLog.reset();
try {
const startedAtMs = Date.now();
const result = await doStream();
return finishWhenStreamCompletes(result, modelCall, {
aiGatewayLogId: extractAIGatewayLogId(result) ?? aiGatewayLog.get(),
includeAIGatewayLog: true,
includeResponse: true,
storeMessages,
startedAtMs
});
} catch (cause) {
recordAIGatewayLogOnError(modelCall, cause, aiGatewayLog.get());
modelCall.fail(cause);
throw cause;
}
}, boundToInvocation ? { boundToInvocation: true } : void 0);
}
}
});
}
function recordAIGatewayLogOnError(span, cause, capturedLogId) {
writeSpanAttributes(span, aiGatewayLogAttributes(extractAIGatewayLogId(cause) ?? capturedLogId));
}
function modelCallSpanForModel(operation, model, params, parentOperation, storeMessages) {
const record = typeof params === "object" && params !== null ? params : {};
const span = modelCallSpan({
integration: "ai-sdk",
model: model?.modelId,
operation,
provider: model?.provider,
request: extractRequestSummary(record, parentOperation)
});
return {
...span,
attributes: {
...span.attributes,
...inputMessageAttributes(record, storeMessages)
}
};
}
//#endregion
//#region src/observability/ai/v6/tools.ts
function wrapTools(tracer, tools, storeTools, boundToInvocation = false, approvedToolCalls) {
if (typeof tools !== "object" || tools === null) return tools;
const toolRecord = tools;
const wrappedTools = {};
for (const [toolName, tool] of Object.entries(toolRecord)) wrappedTools[toolName] = wrapTool(tracer, toolName, tool, storeTools, boundToInvocation, approvedToolCalls);
return wrappedTools;
}
function wrapTool(tracer, toolName, tool, storeTools, boundToInvocation, approvedToolCalls) {
if (typeof tool !== "object" || tool === null) return tool;
const toolRecord = tool;
const hasExecute = typeof toolRecord.execute === "function";
const hasApproval = typeof toolRecord.needsApproval === "boolean" || typeof toolRecord.needsApproval === "function";
if (!hasExecute && !hasApproval) return tool;
const wrappedTool = Object.assign(Object.create(Object.getPrototypeOf(tool)), tool);
if (hasApproval) wrapApprovalCheck(tracer, wrappedTool, toolRecord, tool, toolName, boundToInvocation);
if (!hasExecute) return wrappedTool;
const execute = toolRecord.execute;
if (typeof execute !== "function") return wrappedTool;
const originalExecute = execute.bind(tool);
wrappedTool.execute = (...args) => {
const span = toolCallSpan({
integration: "ai-sdk",
operation: "tool.execute",
toolCallId: extractToolCallId(args[1]),
toolName
});
const attributes = {
...span.attributes,
...toolInputAttributes(args[0], storeTools)
};
return tracer.openSpan(span.name, attributes, (toolSpan) => {
const inSpanContext = AsyncLocalStorage.snapshot();
const toolCallId = extractToolCallId(args[1]);
if (approvalResponseForOptions(args[1], toolCallId)?.approved === true || toolCallId !== void 0 && approvedToolCalls?.get(toolCallId) === toolName) {
recordApprovalChild(tracer, toolName, toolCallId, "approved", boundToInvocation);
if (toolCallId !== void 0) approvedToolCalls?.delete(toolCallId);
}
const result = originalExecute(...args);
if (isPromiseLike(result)) return Promise.resolve(result).then((resolved) => settleToolResult(resolved, toolSpan, inSpanContext, storeTools), (cause) => {
toolSpan.fail(cause);
throw cause;
});
return settleToolResult(result, toolSpan, inSpanContext, storeTools);
}, boundToInvocation ? { boundToInvocation: true } : void 0);
};
return wrappedTool;
}
function wrapApprovalCheck(tracer, wrappedTool, toolRecord, tool, toolName, boundToInvocation) {
const approval = toolRecord.needsApproval;
const original = typeof approval === "function" ? approval.bind(tool) : void 0;
wrappedTool.needsApproval = (...args) => {
const result = original ? original(...args) : approval;
const recordRequested = (needed) => {
const toolCallId = extractToolCallId(args[1]);
if (needed === true && !hasApprovalResponse(args[1], toolCallId)) recordApprovalSegment(tracer, toolName, toolCallId, "requested", boundToInvocation);
return needed;
};
return isPromiseLike(result) ? Promise.resolve(result).then(recordRequested) : recordRequested(result);
};
}
/** Instruments AI SDK v7's top-level tool approval policy. */
function wrapToolApprovalPolicy(tracer, policy, approvedToolCalls, boundToInvocation = false) {
if (typeof policy === "function") return (...args) => {
const toolCall = recordValue$1(recordValue$1(args[0])?.toolCall);
return observePolicyResult(policy(...args), readString(toolCall?.toolName) ?? "tool", readString(toolCall?.toolCallId), tracer, approvedToolCalls, boundToInvocation);
};
if (typeof policy !== "object" || policy === null) return policy;
return Object.fromEntries(Object.entries(policy).map(([toolName, setting]) => [toolName, (...args) => observePolicyResult(typeof setting === "function" ? setting(...args) : setting, toolName, extractToolCallId(args[1]), tracer, approvedToolCalls, boundToInvocation)]));
}
function observePolicyResult(result, toolName, toolCallId, tracer, approvedToolCalls, boundToInvocation = false) {
const observe = (status) => {
if (toolCallId === void 0) return status;
const type = typeof status === "string" ? status : readString(recordValue$1(status)?.type);
if (type === "approved") approvedToolCalls.set(toolCallId, toolName);
else if (type === "user-approval" || type === "denied") recordApprovalSegment(tracer, toolName, toolCallId, type === "denied" ? "denied" : "requested", boundToInvocation);
return status;
};
return isPromiseLike(result) ? Promise.resolve(result).then(observe) : observe(result);
}
/** Records denied responses, whose tool never reaches execute(). */
function recordDeniedApprovalResponses(tracer, messages) {
for (const response of approvalResponses(messages)) if (!response.approved) recordApprovalSegment(tracer, response.toolName, response.toolCallId, "denied");
}
function recordApprovalSegment(tracer, toolName, toolCallId, state, boundToInvocation = false) {
const tool = toolCallSpan({
integration: "ai-sdk",
operation: "tool.approval",
toolCallId,
toolName
});
tracer.withSpan(tool.name, tool.attributes, () => {
recordApprovalChild(tracer, toolName, toolCallId, state, boundToInvocation);
}, boundToInvocation ? { boundToInvocation: true } : void 0);
}
function recordApprovalChild(tracer, toolName, toolCallId, state, boundToInvocation = false) {
const approval = toolApprovalSpan({
state,
toolCallId,
toolName
});
tracer.withSpan(approval.name, approval.attributes, () => void 0, boundToInvocation ? { boundToInvocation: true } : void 0);
}
function hasApprovalResponse(options, toolCallId) {
return approvalResponseForOptions(options, toolCallId) !== void 0;
}
function approvalResponseForOptions(options, toolCallId) {
if (toolCallId === void 0 || typeof options !== "object" || options === null) return;
return approvalResponses(options.messages).find((response) => response.toolCallId === toolCallId);
}
function approvalResponses(messagesValue) {
if (!Array.isArray(messagesValue)) return [];
const approvalToTool = /* @__PURE__ */ new Map();
const toolNames = /* @__PURE__ */ new Map();
for (const message of messagesValue) {
if (typeof message !== "object" || message === null) continue;
const content = message.content;
if (!Array.isArray(content)) continue;
for (const part of content) {
if (typeof part !== "object" || part === null) continue;
const record = part;
const type = readString(record.type);
if (type === "tool-call") {
const toolCallId = readString(record.toolCallId);
const toolName = readString(record.toolName);
if (toolCallId && toolName) toolNames.set(toolCallId, toolName);
} else if (type === "tool-approval-request") {
const approvalId = readString(record.approvalId);
const toolCallId = readString(record.toolCallId);
if (approvalId && toolCallId) approvalToTool.set(approvalId, toolCallId);
}
}
}
const lastMessage = messagesValue.at(-1);
const lastContent = typeof lastMessage === "object" && lastMessage !== null ? lastMessage.content : void 0;
const decisions = [];
if (Array.isArray(lastContent)) for (const part of lastContent) {
if (typeof part !== "object" || part === null) continue;
const record = part;
if (record.type !== "tool-approval-response") continue;
const approvalId = readString(record.approvalId);
if (approvalId && typeof record.approved === "boolean") decisions.push({
approvalId,
approved: record.approved
});
}
return decisions.flatMap(({ approvalId, approved }) => {
const toolCallId = approvalToTool.get(approvalId);
if (!toolCallId) return [];
return [{
approved,
toolCallId,
toolName: toolNames.get(toolCallId) ?? "tool"
}];
});
}
/**
* Finishes the tool span for a settled result. Streaming tools (async
* generators) return an iterable whose consumption is the tool's real
* duration, so the span closes when iteration ends instead of at creation.
*/
function settleToolResult(result, span, inSpanContext, storeTools) {
if (isAsyncIterable(result)) return finishWhenIterableCompletes(result, span, inSpanContext, storeTools);
span.finish(toolOutputAttributes(result, storeTools));
return result;
}
function finishWhenIterableCompletes(iterable, span, inSpanContext, storeTools) {
return { async *[Symbol.asyncIterator]() {
const iterator = inSpanContext(() => iterable[Symbol.asyncIterator]());
let exhausted = false;
let returnValue;
try {
while (true) {
const step = await inSpanContext(() => iterator.next());
if (step.done) {
exhausted = true;
returnValue = step.value;
return step.value;
}
yield step.value;
}
} catch (cause) {
span.fail(cause);
throw cause;
} finally {
if (!exhausted) try {
await inSpanContext(() => iterator.return?.(void 0));
} catch {}
span.finish(toolOutputAttributes(returnValue, storeTools));
}
} };
}
/** Reads the AI SDK tool-call id from the execute options argument. */
function extractToolCallId(options) {
if (typeof options !== "object" || options === null) return;
return readString(options.toolCallId);
}
function isAsyncIterable(value) {
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
}
function recordValue$1(value) {
return typeof value === "object" && value !== null ? value : void 0;
}
function isPromiseLike(value) {
return value !== null && value !== void 0 && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
}
//#endregion
//#region src/observability/ai/v6/wrap.ts
/**
* Wraps an AI SDK namespace object with v6 tracing while preserving its public
* shape and overloaded call signatures.
*/
function createAISDKV6Wrapper(ai, instrumentation) {
const target = isModuleNamespace(ai) ? Object.setPrototypeOf({}, ai) : ai;
const wrapperCache = /* @__PURE__ */ new Map();
return new Proxy(target, { get(proxyTarget, property, receiver) {
const original = Reflect.get(proxyTarget, property, receiver);
if (isWrappedOperationName(property) && typeof original === "function") {
let wrapper = wrapperCache.get(property);
if (!wrapper) {
wrapper = createOperationWrapper(property, toAISDKV6Operation(original), readWrapLanguageModel(ai), instrumentation);
wrapperCache.set(property, wrapper);
}
return wrapper;
}
return original;
} });
}
function readWrapLanguageModel(ai) {
const value = ai.wrapLanguageModel;
if (typeof value !== "function") return;
return value;
}
function toAISDKV6Operation(value) {
return value;
}
function isModuleNamespace(value) {
if (typeof value !== "object" || value === null) return false;
if (value.constructor?.name === "Module") return true;
try {
const firstKey = Object.keys(value)[0];
if (firstKey === void 0) return false;
const descriptor = Object.getOwnPropertyDescriptor(value, firstKey);
return descriptor ? !descriptor.configurable && !descriptor.writable : false;
} catch {
return false;
}
}
function createOperationWrapper(operationName, operation, wrapLanguageModel, instrumentation) {
const storage = {
storeMessages: instrumentation.options?.storeMessages === true,
storeTools: instrumentation.options?.storeTools === true
};
if (isStreamOperation$1(operationName)) return (params, ...args) => {
const boundToInvocation = isAISDKInvocationBounded(params);
return instrumentation.tracer.openSpan(operationSpanName(agentNameForCall(params)), {}, (operationSpan) => {
if (!operationSpan.isTraced) {
operationSpan.finish();
return operation(params, ...args);
}
writeSpanAttributes(operationSpan, operationSpanForCall(operationName, extractModelInfo(params.model), params, instrumentation.options).attributes);
recordDeniedApprovalResponses(instrumentation.tracer, params.messages);
const startedAtMs = Date.now();
const result = operation(operationParamsForCall(params, operationName, wrapLanguageModel, instrumentation.tracer, storage, boundToInvocation), ...args);
const hasModelSpan = canWrapModel(wrapLanguageModel, params.model);
return finishWhenStreamCompletes(result, operationSpan, {
includeResponse: !hasModelSpan,
startedAtMs: hasModelSpan ? void 0 : startedAtMs
});
}, boundToInvocation ? { boundToInvocation: true } : void 0);
};
return async (params, ...args) => {
const boundToInvocation = isAISDKInvocationBounded(params);
return instrumentation.tracer.withSpan(operationSpanName(agentNameForCall(params)), {}, async (operationSpan) => {
if (!operationSpan.isTraced) return operation(params, ...args);
writeSpanAttributes(operationSpan, operationSpanForCall(operationName, extractModelInfo(params.model), params, instrumentation.options).attributes);
recordDeniedApprovalResponses(instrumentation.tracer, params.messages);
const result = await operation(operationParamsForCall(params, operationName, wrapLanguageModel, instrumentation.tracer, storage, boundToInvocation), ...args);
operationSpan.finish(finishAttributesFromResult(result, { includeResponse: !canWrapModel(wrapLanguageModel, params.model) }));
return result;
}, boundToInvocation ? { boundToInvocation: true } : void 0);
};
}
/**
* Reads only the agent name for the span name, from the same sources and in
* the same order as {@link semanticContext}, so the span name and
* `gen_ai.agent.name` never disagree. `functionId` is the AI SDK's canonical
* projection; an explicit name from v6 metadata or v7 runtime context wins.
*/
function agentNameForCall(params) {
const telemetry = telemetryOptions(params);
const metadata = telemetryMetadata(params);
const runtimeContext = runtimeContextRecord(params);
return metadataValue$1(metadata, "agentName", "gen_ai.agent.name") ?? metadataValue$1(runtimeContext, "agentName", "gen_ai.agent.name") ?? readString(telemetry?.functionId);
}
function operationParamsForCall(params, operationName, wrapLanguageModel, tracer, storage, boundToInvocation = false) {
const approvedToolCalls = /* @__PURE__ */ new Map();
return {
...params,
...shouldWrapTools(operationName) && params.tools !== void 0 ? { tools: wrapTools(tracer, params.tools, storage.storeTools, boundToInvocation, approvedToolCalls) } : {},
...params.toolApproval !== void 0 ? { toolApproval: wrapToolApprovalPolicy(tracer, params.toolApproval, approvedToolCalls) } : {},
...params.model !== void 0 ? { model: wrapModel(tracer, wrapLanguageModel, params.model, operationName, storage.storeMessages, boundToInvocation) } : {}
};
}
function canWrapModel(wrapLanguageModel, model) {
return wrapLanguageModel !== void 0 && typeof model === "object" && model !== null;
}
function isStreamOperation$1(operationName) {
return operationName === "streamObject" || operationName === "streamText";
}
function shouldWrapTools(operationName) {
return operationName === "generateText" || operationName === "streamText";
}
function isWrappedOperationName(value) {
return value === "generateObject" || value === "generateText" || value === "streamObject" || value === "streamText";
}
function operationSpanForCall(operation, model, params, options) {
return operationSpan({
attributes: {
...metadataAttributes(includedRuntimeContext(params), TraceAttribute.Cloudflare.RuntimeContextPrefix),
...metadataAttributes(telemetryMetadata(params)),
...contextAttributes(params, options)
},
context: semanticContext(params),
integration: "ai-sdk",
model: model?.modelId,
operation,
provider: model?.provider,
request: extractRequestSummary(params, operation)
});
}
/** Reads the per-call `experimental_telemetry.metadata` record, if present. */
function telemetryMetadata(params) {
const telemetry = telemetryOptions(params);
return typeof telemetry?.metadata === "object" && telemetry.metadata !== null ? telemetry.metadata : void 0;
}
/**
* The caller's application-data channel: `runtimeContext` on v7, the
* `experimental_context` it replaced on v6. Read through one accessor so the
* two majors cannot drift on which one identity and metadata come from.
*/
function runtimeContextRecord(params) {
const value = params.runtimeContext ?? params.experimental_context;
return typeof value === "object" && value !== null ? value : void 0;
}
function telemetryOptions(params) {
const telemetryValue = params.telemetry ?? params.experimental_telemetry;
return typeof telemetryValue === "object" && telemetryValue !== null ? telemetryValue : void 0;
}
/**
* The v7 stand-in for `experimental_telemetry.metadata`.
*
* v7 dropped `metadata` from its telemetry options; callers put the same
* values in `runtimeContext` and mark the telemetry-visible ones through the
* SDK's own `telemetry.includeRuntimeContext`. Running the included subset
* through {@link metadataAttributes} is what keeps reserved keys — Think's
* `cloudflare.agents.turn.*` above all — on the attribute names v6 emits, so
* a query written against v6 traces still matches v7 ones. Everything else
* passes through as documented, under `cloudflare.agents.runtime_context.*`.
*
* Runtime context the caller did not mark as included stays off the span as a
* passthrough attribute: on v7 this is a general application-data channel, not
* a telemetry bag. Identity (`agentId`, `agentName`, `agentVersion`,
* `conversationId`) is separate — {@link semanticContext} reads it regardless,
* because it names the operation rather than describing it, and v7 left
* callers nowhere else to put it.
*/
function includedRuntimeContext(params) {
const runtimeContext = runtimeContextRecord(params);
if (runtimeContext === void 0) return;
const included = includedContextKeys(telemetryOptions(params));
if (included === void 0) return;
const projected = {};
for (const key of included) if (Object.hasOwn(runtimeContext, key)) projected[key] = runtimeContext[key];
return projected;
}
/**
* The keys a caller marked as telemetry-visible.
*
* The SDK's own shape is `{ [key]: boolean }`, included only when explicitly
* true; a plain array of key names is accepted too, since that is the shape of
* the wrapper's option of the same name and silently ignoring it would be
* worse than honouring it. There is deliberately no "include everything"
* shorthand — runtime context routinely carries credentials and user data that
* no one asked to put on a span.
*/
function includedContextKeys(telemetry) {
const included = telemetry?.includeRuntimeContext;
if (Array.isArray(included)) return included.filter((key) => typeof key === "string");
if (typeof included !== "object" || included === null) return;
return Object.entries(included).filter(([, enabled]) => enabled === true).map(([key]) => key);
}
/**
* Whether the caller explicitly opted a key OUT. Identity is read from runtime
* context without an opt-in — on v7 there is nowhere else to put it, and
* requiring one would silently cost every caller `gen_ai.agent.id` — but an
* explicit `false` is a stated intention and is honoured.
*/
function isExcludedFromContext(params, key) {
const included = telemetryOptions(params)?.includeRuntimeContext;
return typeof included === "object" && included !== null && !Array.isArray(included) && included[key] === false;
}
/**
* Reads agent/conversation semantic context from the AI SDK's own telemetry
* fields. The AI SDK maps `functionId` to `gen_ai.agent.name`; an explicit
* name takes priority. Each field comes from v6 `telemetry.metadata` or, on
* v7 where that option no longer exists, from `runtimeContext`.
*/
function semanticContext(params) {
const telemetry = telemetryOptions(params);
const metadata = telemetryMetadata(params);
const runtimeContext = runtimeContextRecord(params);
const fromContext = (key, semanticKey) => isExcludedFromContext(params, key) ? void 0 : metadataValue$1(runtimeContext, key, semanticKey);
return {
agentId: metadataValue$1(metadata, "agentId", "gen_ai.agent.id") ?? fromContext("agentId", "gen_ai.agent.id"),
agentName: metadataValue$1(metadata, "agentName", "gen_ai.agent.name") ?? fromContext("agentName", "gen_ai.agent.name") ?? readString(telemetry?.functionId),
agentVersion: metadataValue$1(metadata, "agentVersion", "gen_ai.agent.version") ?? fromContext("agentVersion", "gen_ai.agent.version"),
conversationId: metadataValue$1(metadata, "conversationId", "gen_ai.conversation.id") ?? fromContext("conversationId", "gen_ai.conversation.id")
};
}
function metadataValue$1(metadata, key, semanticKey) {
return readString(metadata?.[key] ?? metadata?.[semanticKey]);
}
/**
* The wrapper-level allowlist, set once for the instrumentation rather than
* per call. It selects from the same context and lands on the same attributes
* as the SDK's per-call allowlist: selecting a key through both must produce
* one attribute, not a canonical one and a `runtime_context.*` near-duplicate.
*/
function contextAttributes(params, options) {
const included = options?.includeRuntimeContext;
if (included === void 0 || included.length === 0) return;
const runtimeContext = runtimeContextRecord(params);
if (runtimeContext === void 0) return;
const projected = {};
for (const key of included) if (Object.hasOwn(runtimeContext, key)) projected[key] = runtimeContext[key];
return metadataAttributes(projected, TraceAttribute.Cloudflare.RuntimeContextPrefix);
}
const invocationBounded = Symbol.for("cloudflare.agents.ai-sdk.invocation-bounded");
function isAISDKInvocationBounded(params) {
return params[invocationBounded] === true || __DO_NOT_USE_WILL_BREAK__agentContext.getStore()?.connection !== void 0;
}
//#endregion
//#region src/observability/ai/v7/extract.ts
/** Extracts the safe operation name from an AI SDK v7 operation id. */
function operationNameFromId(operationId) {
const value = readString(operationId);
if (value === void 0) return "ai-sdk";
return value.startsWith("ai.") ? value.slice(3) : value;
}
/**
* Extracts safe GenAI semantic context from an AI SDK v7 event. The AI SDK's
* canonical OpenTelemetry projection maps `functionId` to agent name. v7 has
* no telemetry metadata bag, so other identity fields come from the SDK-
* filtered runtime context only when the caller explicitly includes them.
*/
function semanticContextFromEvent(event) {
const record = eventRecord(event);
const runtimeContext = typeof record.runtimeContext === "object" && record.runtimeContext !== null ? record.runtimeContext : void 0;
return {
agentId: metadataValue(runtimeContext, "agentId", "gen_ai.agent.id"),
agentName: metadataValue(runtimeContext, "agentName", "gen_ai.agent.name") ?? readString(record.functionId),
agentVersion: metadataValue(runtimeContext, "agentVersion", "gen_ai.agent.version"),
conversationId: metadataValue(runtimeContext, "conversationId", "gen_ai.conversation.id")
};
}
/** Extracts safe request settings from an AI SDK v7 event. */
function requestSummaryFromEvent(event, operationName) {
const record = eventRecord(event);
return {
frequencyPenalty: readNumber(record.frequencyPenalty),
maxTokens: readNumber(record.maxOutputTokens ?? record.maxTokens),
outputType: operationName === "generateObject" || operationName === "streamObject" ? "json" : "text",
presencePenalty: readNumber(record.presencePenalty),
seed: readNumber(record.seed),
stream: operationName === "streamText" || operationName === "streamObject",
temperature: readNumber(record.temperature),
topK: readNumber(record.topK),
topP: readNumber(record.topP)
};
}
/** Extracts safe finish attributes from an AI SDK v7 result-like event. */
function finishAttributesFromEvent(event, options = {}) {
const record = eventRecord(event);
return finishAttributes({
aiGatewayLogId: options.includeAIGatewayLog ? extractAIGatewayLogId(record) : void 0,
finishReason: extractFinishReason(record),
response: options.includeResponse ? responseSummaryFromEvent(record) : void 0,
timeToFirstChunkSeconds: options.includePerformance ? timeToFirstChunkSeconds(record) : void 0,
usage: tokenUsageFromEvent(record)
});
}
/** Builds correlation attributes for AI SDK v7 callback ids. */
function correlationAttributes(input) {
return {
[TraceAttribute.Cloudflare.CallID]: input.callId,
[TraceAttribute.GenAI.ToolCallID]: input.toolCallId
};
}
function metadataValue(metadata, key, semanticKey) {
return readString(metadata?.[key] ?? metadata?.[semanticKey]);
}
function eventRecord(event) {
return event;
}
function extractFinishReason(event) {
const finishReason = event.finishReason;
if (typeof finishReason === "string") return finishReason;
if (typeof finishReason === "object" && finishReason !== null) return readString(finishReason.unified);
}
function responseSummaryFromEvent(event) {
const response = typeof event.response === "object" && event.response !== null ? event.response : void 0;
const id = readString(event.responseId ?? response?.id);
const model = readString(event.responseModel ?? response?.modelId ?? response?.model);
if (id === void 0 && model === void 0) return;
return {
...id !== void 0 ? { id } : {},
...model !== void 0 ? { model } : {}
};
}
function tokenUsageFromEvent(event) {
const raw = event.totalUsage ?? event.usage;
if (typeof raw !== "object" || raw === null) return;
const usage = raw;
const inputTokens = readTokenCount(usage.inputTokens);
const outputTokens = readTokenCount(usage.outputTokens);
const cacheReadInputTokens = readNestedTokenField(usage.inputTokenDetails, "cacheReadTokens") ?? readNestedTokenField(usage.inputTokens, "cacheRead") ?? readNumber(usage.cachedInputTokens);
const cacheCreationInputTokens = readNestedTokenField(usage.inputTokenDetails, "cacheWriteTokens") ?? readNestedTokenField(usage.inputTokens, "cacheWrite");
const reasoningTokens = readNestedTokenField(usage.outputTokenDetails, "reasoningTokens") ?? readNestedTokenField(usage.outputTokens, "reasoning") ?? readNumber(usage.reasoningTokens);
if (inputTokens === void 0 && outputTokens === void 0 && cacheReadInputTokens === void 0 && cacheCreationInputTokens === void 0 && reasoningTokens === void 0) return;
return {
...cacheCreationInputTokens !== void 0 ? { cacheCreationInputTokens } : {},
...cacheReadInputTokens !== void 0 ? { cacheReadInputTokens } : {},
...inputTokens !== void 0 ? { inputTokens } : {},
...outputTokens !== void 0 ? { outputTokens } : {},
...reasoningTokens !== void 0 ? { reasoningTokens } : {}
};
}
function timeToFirstChunkSeconds(event) {
const milliseconds = readNumber((typeof event.performance === "object" && event.performance !== null ? event.performance : void 0)?.timeToFirstOutputMs);
return milliseconds === void 0 ? void 0 : milliseconds / 1e3;
}
//#endregion
//#region src/observability/ai/v7/telemetry.ts
/**
* Creates an AI SDK v7 `Telemetry` object that projects callback events into
* Cloudflare-compatible GenAI spans without recording raw prompts or outputs.
*/
function createAISDKV7Telemetry(instrumentation) {
const storeMessages = instrumentation.options?.storeMessages === true;
const storeTools = instrumentation.options?.storeTools === true;
const operations = /* @__PURE__ */ new Map();
const modelSpans = /* @__PURE__ */ new Map();
const toolSpans = /* @__PURE__ */ new Map();
const toolSpanKey = (callId, toolCallId) => `${callId}:${toolCallId}`;
const finishOperation = (event) => {
const state = operations.get(event.callId);
if (!state) return;
finishOpenModelSpans(event.callId, void 0, modelSpans, instrumentation.tracer);
finishOpenToolSpans(event.callId, void 0, toolSpans, instrumentation.tracer);
state.span.finish(finishAttributesFromEvent(event));
operations.delete(event.callId);
};
return {
onStart(event) {
const operationName = supportedOperationName(operationNameFromId(event.operationId));
if (!operationName) return;
const span = operationSpan({
attributes: {
...correlationAttributes({ callId: event.callId }),
...runtimeContextAttributes(event.runtimeContext)
},
context: semanticContextFromEvent(event),
integration: "ai-sdk",
model: readString(event.modelId),
operation: operationName,
provider: readString(event.provider),
request: requestSummaryFromEvent(event, operationName)
});
const operation = instrumentation.tracer.openSpan(span.name, span.attributes, (activeSpan) => activeSpan);
operations.set(event.callId, {
callId: event.callId,
operationName,
span: operation
});
},
onLanguageModelCallStart(event) {
const state = operations.get(event.callId);
if (!state) return;
const span = modelCallSpan({
attributes: {
...correlationAttributes({ callId: event.callId }),
...inputMessageAttributes(event, storeMessages)
},
integration: "ai-sdk",
model: readString(event.modelId),
operation: isStreamOperation(state.operationName) ? "doStream" : "doGenerate",
provider: readString(event.provider),
request: requestSummaryFromEvent(event, state.operationName)
});
const spans = modelSpans.get(event.callId) ?? [];
spans.push({ spanSpec: span });
modelSpans.set(event.callId, spans);
},
onLanguageModelCallEnd(event) {
const state = shiftModelSpan(modelSpans, event.callId);
if (!state) return;
(state.span ?? instrumentation.tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (activeSpan) => activeSpan)).finish({
...finishAttributesFromEvent(event, {
includeAIGatewayLog: true,
includePerformance: true,
includeResponse: true
}),
...outputMessageAttributes(event, storeMessages)
});
},
onToolExecutionStart(event) {
const toolCallId = readString(event.toolCall.toolCallId);
if (toolCallId === void 0 || !operations.has(event.callId)) return;
const toolName = readString(event.toolCall.toolName) ?? "tool";
const span = toolCallSpan({
integration: "ai-sdk",
operation: "tool.execute",
toolName
});
toolSpans.set(toolSpanKey(event.callId, toolCallId), {
callId: event.callId,
spanSpec: {
name: span.name,
attributes: {
...span.attributes,
...correlationAttributes({
callId: event.callId,
toolCallId
}),
...toolInputAttributes(event.toolCall.input, storeTools),
...toolContextAttributes(toolName, event.toolContext)
}
}
});
},
onToolExecutionEnd(event) {
const toolCallId = readString(event.toolCall.toolCallId);
if (toolCallId === void 0) return;
const state = toolSpans.get(toolSpanKey(event.callId, toolCallId));
if (!state) return;
const span = state.span ?? instrumentation.tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (activeSpan) => activeSpan);
if (event.toolOutput?.type === "tool-error") span.fail(event.toolOutput.error);
else span.finish(toolOutputAttributes(event.toolOutput?.output, storeTools));
toolSpans.delete(toolSpanKey(event.callId, toolCallId));
},
onAbort(event) {
const cause = { name: "AbortError" };
finishOpenModelSpans(event.callId, cause, modelSpans, instrumentation.tracer);
finishOpenToolSpans(event.callId, cause, toolSpans, instrumentation.tracer);
const state = operations.get(event.callId);
if (!state) return;
state.span.fail(cause);
operations.delete(event.callId);
},
onEnd: finishOperation,
onError(event) {
const errorEvent = eventObject(event);
const callId = readString(errorEvent.callId);
if (callId === void 0) return;
const cause = errorEvent.error ?? event;
finishOpenModelSpans(callId, cause, modelSpans, instrumentation.tracer);
finishOpenToolSpans(callId, cause, toolSpans, instrumentation.tracer);
const state = operations.get(callId);
if (!state) return;
state.span.fail(cause);
operations.delete(callId);
},
executeLanguageModelCall(options) {
const state = modelSpans.get(options.callId)?.find((candidate) => candidate.span === void 0);
if (!state) return options.execute();
return instrumentation.tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (span) => {
state.span = span;
try {
return Promise.resolve(options.execute()).catch((cause) => {
writeSpanAttributes(span, aiGatewayLogAttributes(extractAIGatewayLogId(cause)));
span.fail(cause);
removeModelState(modelSpans, options.callId, state);
throw cause;
});
} catch (cause) {
writeSpanAttributes(span, aiGatewayLogAttributes(extractAIGatewayLogId(cause)));
span.fail(cause);
removeModelState(modelSpans, options.callId, state);
throw cause;
}
});
},
executeTool(options) {
const state = toolSpans.get(toolSpanKey(options.callId, options.toolCallId));
if (!state) return options.execute();
return instrumentation.tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (span) => {
state.span = span;
try {
return Promise.resolve(options.execute()).catch((cause) => {
span.fail(cause);
toolSpans.delete(toolSpanKey(options.callId, options.toolCallId));
throw cause;
});
} catch (cause) {
span.fail(cause);
toolSpans.delete(toolSpanKey(options.callId, options.toolCallId));
throw cause;
}
});
}
};
}
function supportedOperationName(operationName) {
if (operationName === "generateObject" || operationName === "generateText" || operationName === "streamObject" || operationName === "streamText") return operationName;
}
function isStreamOperation(operationName) {
return operationName === "streamObject" || operationName === "streamText";
}
function shiftModelSpan(spansByCallId, callId) {
const spans = spansByCallId.get(callId);
const span = spans?.shift();
if (spans && spans.length === 0) spansByCallId.delete(callId);
return span;
}
function removeModelState(statesByCallId, callId, state) {
const states = statesByCallId.get(callId);
if (!states) return;
const index = states.indexOf(state);
if (index !== -1) states.splice(index, 1);
if (states.length === 0) statesByCallId.delete(callId);
}
function finishOpenModelSpans(callId, cause, spansByCallId, tracer) {
const states = spansByCallId.get(callId);
if (!states) return;
for (const state of states) {
const span = state.span ?? tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (activeSpan) => activeSpan);
if (cause === void 0) span.finish();
else span.fail(cause);
}
spansByCallId.delete(callId);
}
function finishOpenToolSpans(callId, cause, spansByToolCallId, tracer) {
for (const [toolCallId, state] of spansByToolCallId) {
if (state.callId !== callId) continue;
const span = state.span ?? tracer.openSpan(state.spanSpec.name, state.spanSpec.attributes, (activeSpan) => activeSpan);
if (cause === void 0) span.finish();
else span.fail(cause);
spansByToolCallId.delete(toolCallId);
}
}
function eventObject(event) {
return typeof event === "object" && event !== null ? event : {};
}
const SEMANTIC_CONTEXT_KEYS = /* @__PURE__ */ new Set([
"agentId",
"agentName",
"agentVersion",
"conversationId",
"gen_ai.agent.id",
"gen_ai.agent.name",
"gen_ai.agent.version",
"gen_ai.conversation.id"
]);
function runtimeContextAttributes(runtimeContextValue) {
const attributes = {};
const runtimeContext = recordValue(runtimeContextValue);
for (const [key, value] of Object.entries(runtimeContext ?? {})) if (!SEMANTIC_CONTEXT_KEYS.has(key) && isScalarAttributeValue(value)) attributes[key.startsWith("cloudflare.agents.") ? key : `cloudflare.agents.runtime_context.${key}`] = value;
return attributes;
}
function toolContextAttributes(toolName, toolContextValue) {
const attributes = {};
const toolContext = recordValue(toolContextValue);
for (const [key, value] of Object.entries(toolContext ?? {})) if (isScalarAttributeValue(value)) attributes[`cloudflare.agents.tool_context.${toolName}.${key}`] = value;
return attributes;
}
function recordValue(value) {
return typeof value === "object" && value !== null ? value : void 0;
}
function isScalarAttributeValue(value) {
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
}
//#endregion
//#region src/observability/ai/index.ts
const agentsAISDKTelemetryBrand = Symbol.for("cloudflare.agents.ai-sdk-telemetry");
/**
* Wraps an AI SDK namespace with tracing.
*/
function wrapAISDK(ai, options = {}) {
return createAISDKV6Wrapper(ai, {
options,
tracer
});
}
/**
* Creates an AI SDK v7 telemetry adapter for use with `registerTelemetry` or
* per-call telemetry configuration.
*/
function createAISDKTelemetry(options = {}) {
const telemetry = createAISDKV7Telemetry({
options,
tracer
});
Object.defineProperty(telemetry, agentsAISDKTelemetryBrand, { value: true });
return telemetry;
}
//#endregion
export { createAISDKTelemetry, wrapAISDK };
//# sourceMappingURL=index.js.map