@tangle-network/agent-core 0.3.8 → 0.4.1

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.
@@ -1 +0,0 @@
1
- {"version":3,"file":"telemetry-D2XGH5UL.js","names":[],"sources":["../src/telemetry/content-attributes.ts","../src/telemetry/genai-attributes.ts","../src/telemetry/spine-attributes.ts","../src/telemetry/token-usage.ts"],"sourcesContent":["/**\n * Content-key alias vocabulary — the single source of truth for \"where does a\n * trace producer put the human-readable CONTENT of a turn\": the task, the\n * prompt, the model completion, and a tool call's name / args / result.\n *\n * This is the content sibling of {@link ./genai-attributes.ts} (model / token /\n * cost vocabulary) and follows the identical pattern: writers emit a producer's\n * own keys; the ingest READER accepts a wider candidate list per normalized\n * field because every coding agent and SDK spells the same field differently\n * (OTel GenAI, OpenInference, our own `tangle.*`, Claude Code's `claude_code.*`,\n * Codex's `codex.*`, OpenCode's `message.*` / `tool.*`). Co-locating the per-field\n * candidate lists and the resolver here is the drift guard: a producer's emitted\n * key and the reader's candidate list can never silently disagree, and adding an\n * agent is a DATA change (one alias row) rather than a code change in every\n * reader.\n *\n * Both consumers — the Intelligence ingest (span + log-record content recovery,\n * onboarding content check) and the standalone OpenCode exporter (which WRITES\n * under these keys) — import this layer, so \"the producer shipped content\" and\n * \"the product can read it\" mean the same thing by construction.\n *\n * Coding agents frequently carry this content on OTLP LOG events rather than\n * spans; {@link logRecordContentBag} is the one place that decides how a log row\n * becomes a bag the resolver can read.\n *\n * Fail-loud: a key whose value isn't a usable non-empty string (or, for\n * `messages`, a parseable array) is NOT recorded — {@link extractContent}\n * returns only the fields it actually found, never a fabricated default. An\n * empty bag returns an empty object, never sentinels.\n */\n\n/** The normalized content a single attribute bag can carry. Every field is\n * optional: a bag carries only the slices the emitter put on it. */\nexport interface NormalizedContent {\n task?: string;\n prompt?: string;\n completion?: string;\n toolName?: string;\n toolArgs?: string;\n toolResult?: string;\n messages?: unknown[];\n}\n\n/** The normalized field a content key maps to. `messages` is parsed as an\n * array (or a JSON string that decodes to one); every other field is a\n * non-empty string. */\nexport type ContentField = keyof NormalizedContent;\n\n// ── Writer PRIMARY keys (the key a producer emits per field) ──────────────\n// Named constants so a writer (e.g. the OpenCode exporter) emits under the\n// canonical alias rather than a magic string, and the writer/reader can never\n// drift on the spelling. Each is the canonical WRITE key for its field — the\n// alias a Tangle producer emits under. It is NOT necessarily the first entry in\n// that field's reader priority list (FIELD_KEY_PRIORITY may rank a `tangle.*` /\n// `gen_ai.*` alias ahead of it when a richer source is also present); the writer\n// key and a reader's top candidate are independent by design.\n\n/** Prompt / user message — the writer key OpenCode's user-turn export uses. */\nexport const CONTENT_PROMPT_KEY = \"gen_ai.prompt\";\n/** Model completion — the writer key OpenCode's assistant-text export uses. */\nexport const CONTENT_COMPLETION_KEY = \"message.part.text\";\n/** Invoked tool's name. */\nexport const CONTENT_TOOL_NAME_KEY = \"tool.name\";\n/** Tool call arguments. */\nexport const CONTENT_TOOL_ARGS_KEY = \"tool.input\";\n/** Tool call output / result. */\nexport const CONTENT_TOOL_RESULT_KEY = \"tool.output\";\n\n/**\n * The single alias table: content key → normalized field. Ordered by emitter\n * for auditability; resolution order within a field is \"first matching key in\n * priority order wins\" (see {@link FIELD_KEY_PRIORITY}), NOT table order.\n *\n * Coverage:\n * - Standard OTel GenAI / OpenInference / our own `tangle.*` conventions.\n * - Claude Code: the `claude_code.*` event attributes.\n * - Codex: the `codex.*` event attributes (inner prompt key `prompt`,\n * verified from codex-rs/otel; looser shim spellings folded in as\n * low-priority fallbacks).\n * - OpenCode: the `message.*` / `tool.*` event attributes.\n *\n * Adding an agent = add its rows here. No reader changes.\n */\nconst CONTENT_KEY_ALIASES: Readonly<Record<string, ContentField>> = {\n // ── Standard conventions (OTel GenAI, OpenInference, our own) ──────────\n \"gen_ai.prompt\": \"prompt\",\n \"gen_ai.request.prompt\": \"prompt\",\n \"gen_ai.completion\": \"completion\",\n \"gen_ai.response.completion\": \"completion\",\n \"input.value\": \"prompt\",\n \"output.value\": \"completion\",\n \"llm.input_messages\": \"messages\",\n \"llm.output_messages\": \"messages\",\n \"user.message\": \"prompt\",\n prompt: \"prompt\",\n \"tangle.task\": \"task\",\n \"tangle.declared_intent\": \"task\",\n \"tangle.tool.args\": \"toolArgs\",\n \"tangle.tool.result\": \"toolResult\",\n \"tangle.tool.name\": \"toolName\",\n \"tool.name\": \"toolName\",\n \"tool.args\": \"toolArgs\",\n\n // ── Claude Code (claude_code.* OTLP log events) ───────────────────────\n // claude_code.user_prompt event carries the user's prompt under 'prompt'.\n \"claude_code.user_prompt.prompt\": \"prompt\",\n // Bare 'prompt' on a claude_code.user_prompt record is covered by the\n // standard `prompt` row above; the dotted form is the same content when the\n // emitter namespaces the attribute under the event name.\n // claude_code.tool_result attrs: the input that was passed to the tool and\n // the tool's output.\n \"claude_code.tool_result.tool_input\": \"toolArgs\",\n \"claude_code.tool_result.tool_parameters\": \"toolArgs\",\n \"claude_code.tool_result.tool_output\": \"toolResult\",\n // claude_code.tool event: the tool's output.\n \"claude_code.tool.tool.output\": \"toolResult\",\n \"claude_code.tool.output\": \"toolResult\",\n // claude_code.api_request_body / api_response_body carry the FULL prompt +\n // completion JSON under 'body' — the richest content source. Routed to\n // `messages` and parsed (a JSON object/array body decodes to a messages\n // array or a single-element wrap).\n \"claude_code.api_request_body.body\": \"messages\",\n \"claude_code.api_response_body.body\": \"messages\",\n\n // ── Codex (codex.* OTLP log events) ───────────────────────────────────\n // codex.user_prompt event: the prompt text rides under the inner `prompt`\n // attribute. Verified prompt (codex-rs codex-rs/otel/src/events/session_telemetry.rs):\n // `log_event!(event.name = \"codex.user_prompt\", prompt_length = …, prompt = %prompt_to_log)`.\n // The namespaced dotted form mirrors claude_code.user_prompt.prompt.\n \"codex.user_prompt.prompt\": \"prompt\",\n // Looser shim spellings for emitters that rename the inner attribute away from\n // the upstream `prompt` macro. They are ranked below the verified\n // `codex.user_prompt.prompt` in FIELD_KEY_PRIORITY, so merge order here is\n // behaviorally irrelevant; a form that never matches is a no-op, never a\n // fabricated read.\n \"codex.user_prompt.text\": \"prompt\",\n \"codex.user_prompt.message\": \"prompt\",\n \"codex.user_prompt.input\": \"prompt\",\n \"codex.tool_result.output\": \"toolResult\",\n \"codex.tool_result.result\": \"toolResult\",\n\n // ── OpenCode (message.* / tool.* OTLP log events) ─────────────────────\n // The OpenCode exporter routes a USER-turn part to a prompt-aliased key\n // (gen_ai.prompt / user.message, above) and an ASSISTANT-turn part to\n // message.part.text → completion. message.text mirrors message.part.text for\n // emitters that flatten the part.\n \"message.part.text\": \"completion\",\n \"message.text\": \"completion\",\n \"tool.input\": \"toolArgs\",\n \"tool.output\": \"toolResult\",\n};\n\n/**\n * Reader candidate keys per normalized field, highest-confidence first.\n * When several aliased keys for the same field are present on one bag, the\n * first key in this list that is present wins — so `tangle.task` beats a bare\n * `prompt` for `task`, and the richest source (a full `messages` body) is\n * preferred where it exists. A key not listed here for its field still\n * resolves (it is appended after the explicit order), so adding an alias row\n * without touching this list is safe — it just lands at lowest priority.\n *\n * The `user_request` / `task` / `input` span-only prompt spellings the\n * intent-audit span path historically read are folded in here so the span and\n * log recovery paths resolve intent through ONE vocabulary.\n */\nconst FIELD_KEY_PRIORITY: Readonly<Record<ContentField, readonly string[]>> = {\n task: [\"tangle.task\", \"tangle.declared_intent\", \"task\"],\n prompt: [\n \"gen_ai.prompt\",\n \"gen_ai.request.prompt\",\n \"claude_code.user_prompt.prompt\",\n \"codex.user_prompt.prompt\",\n \"codex.user_prompt.text\",\n \"codex.user_prompt.message\",\n \"codex.user_prompt.input\",\n \"input.value\",\n \"user.message\",\n \"user_request\",\n \"llm.input\",\n \"input\",\n \"prompt\",\n ],\n completion: [\n \"gen_ai.completion\",\n \"gen_ai.response.completion\",\n \"output.value\",\n \"message.part.text\",\n \"message.text\",\n ],\n toolName: [\"tangle.tool.name\", \"tool.name\"],\n toolArgs: [\n \"tangle.tool.args\",\n \"claude_code.tool_result.tool_input\",\n \"claude_code.tool_result.tool_parameters\",\n \"tool.input\",\n \"tool.args\",\n ],\n toolResult: [\n \"tangle.tool.result\",\n \"claude_code.tool_result.tool_output\",\n \"claude_code.tool.tool.output\",\n \"claude_code.tool.output\",\n \"codex.tool_result.output\",\n \"codex.tool_result.result\",\n \"tool.output\",\n ],\n messages: [\n \"claude_code.api_request_body.body\",\n \"claude_code.api_response_body.body\",\n \"llm.input_messages\",\n \"llm.output_messages\",\n ],\n};\n\n/**\n * Every content key the alias layer knows about. Callers use this to detect\n * \"is there any content on this bag at all\" without re-deriving the set.\n * Frozen so a caller can't mutate the shared table. Excludes the span-only\n * prompt spellings, which are intent-recovery candidates rather than\n * content-presence signals.\n */\nexport const CONTENT_KEYS: readonly string[] = Object.freeze(\n Object.keys(CONTENT_KEY_ALIASES),\n);\n\n/** Map of every known content key → its normalized field, exposed read-only\n * for callers that want to inspect or extend the mapping (e.g. enrichment\n * joins that need to know which field a matched key feeds). */\nexport const CONTENT_KEY_FIELD: Readonly<Record<string, ContentField>> =\n Object.freeze({ ...CONTENT_KEY_ALIASES });\n\n/** A non-empty trimmed string, or null. Absent/blank/non-string values are an\n * honest miss, never \"\". */\nexport function asContentField(value: unknown): string | null {\n if (typeof value === \"string\") {\n const t = value.trim();\n return t.length > 0 ? t : null;\n }\n return null;\n}\n\n/** Flatten a tool-args / tool-result value to a string. Strings pass through;\n * objects/arrays are JSON-stringified so a structured tool payload is still\n * readable content. Null/empty → null (never \"\" or \"null\"). */\nexport function asContentString(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n const s = asContentField(value);\n if (s !== null) return s;\n if (typeof value === \"object\") {\n try {\n const json = JSON.stringify(value);\n return json && json !== \"{}\" && json !== \"[]\" ? json : null;\n } catch {\n return null;\n }\n }\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n return null;\n}\n\n/**\n * Coerce a `messages`-aliased value into an array. Accepts an already-decoded\n * array, a JSON string that parses to an array, or a JSON object (a\n * Claude/Codex api_request_body whose top level is `{ messages: [...] }` or a\n * single message object). Returns null when nothing array-shaped is\n * recoverable — never a fabricated `[]`.\n */\nfunction asMessages(value: unknown): unknown[] | null {\n let v = value;\n if (typeof v === \"string\") {\n const t = v.trim();\n if (t.length === 0) return null;\n try {\n v = JSON.parse(t);\n } catch {\n return null;\n }\n }\n if (Array.isArray(v)) return v.length > 0 ? v : null;\n if (v && typeof v === \"object\") {\n const inner = (v as Record<string, unknown>).messages;\n if (Array.isArray(inner)) return inner.length > 0 ? inner : null;\n // A single message object (api_request_body bodies sometimes wrap one turn)\n // — wrap it ONLY when it actually carries readable message text. An empty or\n // arbitrary object ({} or { foo: \"bar\" }) is NOT content; wrapping it would\n // make hasContent return a false positive on a metadata-only body.\n if (messageHasText(v as Record<string, unknown>)) return [v];\n return null;\n }\n return null;\n}\n\n/**\n * Recover the human-readable text from a single message-shaped object. The one\n * reader every message-text caller goes through, in priority order:\n * 1. `content` as a plain string;\n * 2. `content` as a LIST OF BLOCKS — the Anthropic / Claude Code message shape\n * (`content: [{ type: \"text\", text }, { type: \"tool_use\", … }]`): the text\n * of every text-bearing block is concatenated (newline-joined), tool/other\n * blocks contribute nothing. Without this branch a Claude Code message\n * whose content is an array is silently invisible to content recovery;\n * 3. a top-level `text` string;\n * 4. a nested `content.text` string (object-wrapped content).\n * Returns the first non-empty result, or null when no usable text exists — a\n * bare/arbitrary object yields null and is not treated as a usable message.\n */\nfunction readMessageText(msg: Record<string, unknown>): string | null {\n const direct = asContentField(msg.content);\n if (direct !== null) return direct;\n\n const blocks = msg.content;\n if (Array.isArray(blocks)) {\n const parts: string[] = [];\n for (const block of blocks) {\n if (!block || typeof block !== \"object\") continue;\n const text = asContentField((block as Record<string, unknown>).text);\n if (text !== null) parts.push(text);\n }\n if (parts.length > 0) return parts.join(\"\\n\");\n }\n\n const top = asContentField(msg.text);\n if (top !== null) return top;\n\n return asContentField((msg.content as Record<string, unknown> | null)?.text);\n}\n\n/** True when a single message-shaped object carries recoverable text. A\n * bare/arbitrary object has none, so it is not treated as a usable message. */\nfunction messageHasText(msg: Record<string, unknown>): boolean {\n return readMessageText(msg) !== null;\n}\n\n/**\n * Precomputed resolution order per field: the explicit priority list, then any\n * remaining aliased keys for that field appended at lowest priority. Built once\n * at module load (frozen) so the content-presence hot path (onboarding scans\n * every row) never re-runs an `Object.keys().filter()` over the alias table per\n * row — it reads a fixed array instead.\n */\nconst FIELD_KEY_ORDER: Readonly<Record<ContentField, readonly string[]>> =\n Object.freeze(\n Object.fromEntries(\n (Object.keys(FIELD_KEY_PRIORITY) as ContentField[]).map((field) => {\n const priority = FIELD_KEY_PRIORITY[field];\n const extras = Object.keys(CONTENT_KEY_ALIASES).filter(\n (k) => CONTENT_KEY_ALIASES[k] === field && !priority.includes(k),\n );\n return [field, Object.freeze([...priority, ...extras])];\n }),\n ) as Record<ContentField, readonly string[]>,\n );\n\n/** The resolution order for one field: explicit priority list, then any\n * remaining aliased keys for that field appended at lowest priority. */\nfunction fieldKeyOrder(field: ContentField): readonly string[] {\n return FIELD_KEY_ORDER[field];\n}\n\n/** Resolve one normalized field from a bag: walk its priority list, then any\n * remaining aliased keys for that field, returning the first usable value. */\nfunction resolveField(\n bag: Record<string, unknown>,\n field: ContentField,\n): NormalizedContent[ContentField] {\n for (const key of fieldKeyOrder(field)) {\n if (!(key in bag)) continue;\n const raw = bag[key];\n if (field === \"messages\") {\n const msgs = asMessages(raw);\n if (msgs) return msgs;\n continue;\n }\n if (field === \"toolArgs\" || field === \"toolResult\") {\n const s = asContentString(raw);\n if (s) return s;\n continue;\n }\n const s = asContentField(raw);\n if (s) return s;\n }\n return undefined;\n}\n\nconst ALL_FIELDS: readonly ContentField[] = [\n \"task\",\n \"prompt\",\n \"completion\",\n \"toolName\",\n \"toolArgs\",\n \"toolResult\",\n \"messages\",\n];\n\n/**\n * Extract normalized content from a flattened attribute bag (span attributes\n * OR a log record's merged attribute bag). Pure: same input → same output, no\n * I/O, no env reads. Returns only the fields actually present; an empty/absent\n * bag returns `{}`.\n */\nexport function extractContent(\n attributes: Record<string, unknown> | undefined | null,\n): NormalizedContent {\n const out: NormalizedContent = {};\n if (!attributes) return out;\n for (const field of ALL_FIELDS) {\n const v = resolveField(attributes, field);\n if (v !== undefined) {\n // Index assignment is safe: `field` is the matching key of the union.\n (out as Record<ContentField, unknown>)[field] = v;\n }\n }\n return out;\n}\n\n/**\n * The keys whose presence (with a usable value) counts as \"this bag carries\n * content\". The alias-table keys only — the span-only prompt spellings are\n * deliberately excluded so a metadata row that happens to carry a bare `input`\n * is not mislabelled content-bearing.\n */\nconst PRESENCE_KEYS: readonly string[] = Object.keys(CONTENT_KEY_ALIASES);\n\n/** True when the bag carries ANY known content key with a usable value. A cheap\n * presence check that SHORT-CIRCUITS on the first usable key — it does not\n * build the NormalizedContent object or JSON-parse `messages` bodies, so a hot\n * scan path (onboarding) pays per-row only until the first hit. */\nexport function hasContent(\n attributes: Record<string, unknown> | undefined | null,\n): boolean {\n if (!attributes) return false;\n for (const key of PRESENCE_KEYS) {\n if (!(key in attributes)) continue;\n const field = CONTENT_KEY_ALIASES[key];\n const raw = attributes[key];\n if (field === \"messages\") {\n if (asMessages(raw)) return true;\n continue;\n }\n if (field === \"toolArgs\" || field === \"toolResult\") {\n if (asContentString(raw)) return true;\n continue;\n }\n if (asContentField(raw)) return true;\n }\n return false;\n}\n\n/**\n * Build the content-detection bag for a single OTLP log record. A log carries\n * its content either as flattened attributes (keyed by a content key) OR as the\n * record `body` for a prompt event whose text has no attribute key (a\n * `*.user_prompt` event whose prompt IS the body). This is the ONE place that\n * decides \"how a log row becomes a bag `extractContent` can read\", so the\n * intent-audit recovery, the trace-analyst log enrichment, and the onboarding\n * content check can never drift on it.\n *\n * The body is surfaced under the standard `prompt` alias ONLY for `*.user_prompt`\n * events (a generic log body is not content); an attribute-carried `prompt`\n * already present is never overwritten.\n */\nexport function logRecordContentBag(\n attributes: Record<string, unknown> | null | undefined,\n body: string | null | undefined,\n): Record<string, unknown> {\n const bag: Record<string, unknown> = { ...(attributes ?? {}) };\n if (typeof body === \"string\" && body.trim().length > 0) {\n const eventName = asContentField(bag[\"event.name\"]);\n if (\n eventName &&\n /(?:^|\\.)user_prompt$/.test(eventName) &&\n !(\"prompt\" in bag)\n ) {\n bag.prompt = body;\n }\n }\n return bag;\n}\n\n/** Known coding-agent service names, mapped to their canonical agent id. An\n * EXACT match only — a substring guess ('claude-helper-bot') would mislabel a\n * customer service into a coding-agent bucket and drive a wrong \"you missed\n * THIS gate\" onboarding hint. The namespaced-key / gen_ai.system paths above\n * this remain the reliable identity; this allowlist is the last resort for a\n * content-less metadata row carrying only service.name. */\nconst KNOWN_AGENT_SERVICE_NAMES: Readonly<Record<string, string>> = {\n \"claude-code\": \"claude-code\",\n \"claude code\": \"claude-code\",\n claudecode: \"claude-code\",\n codex: \"codex\",\n \"codex-cli\": \"codex\",\n opencode: \"opencode\",\n};\n\n/**\n * Deterministic coding-agent label for an attribute bag, by the namespace of\n * the content/event keys it carries, then the standard `gen_ai.system` /\n * `service.name` identity. Returns a stable lowercase id\n * (`claude-code` | `codex` | `opencode` | the gen_ai.system name) or\n * `\"unknown\"` when nothing reliably identifies the emitter — never a fabricated\n * default that pretends to know which agent produced the row.\n *\n * `service.name` is matched against a KNOWN-AGENT ALLOWLIST (exact), not a\n * substring guess, so an arbitrary customer service named e.g. `claude-helper`\n * stays `unknown` rather than being mislabelled `claude-code` and driving a\n * wrong onboarding hint.\n */\nexport function classifyAgent(\n bag: Record<string, unknown> | null | undefined,\n): string {\n if (!bag) return \"unknown\";\n const keys = Object.keys(bag);\n if (keys.some((k) => k.startsWith(\"claude_code.\"))) return \"claude-code\";\n if (keys.some((k) => k.startsWith(\"codex.\"))) return \"codex\";\n const eventName = asContentField(bag[\"event.name\"]);\n if (eventName?.startsWith(\"claude_code.\")) return \"claude-code\";\n if (eventName?.startsWith(\"codex.\")) return \"codex\";\n // gen_ai.system is a producer-declared identity (the OpenCode exporter stamps\n // \"opencode\"); trust it as the emitter's own claim.\n const system = asContentField(bag[\"gen_ai.system\"]);\n if (system) return system.toLowerCase();\n // OpenCode emits message.*/tool.* events with no agent-namespaced prefix; the\n // message.* key shape is the reliable tell before the lossy service.name.\n if (keys.some((k) => k.startsWith(\"message.\"))) return \"opencode\";\n const service = asContentField(bag[\"service.name\"]);\n if (service) {\n const mapped = KNOWN_AGENT_SERVICE_NAMES[service.toLowerCase()];\n if (mapped) return mapped;\n }\n return \"unknown\";\n}\n\n/** A declared-intent recovery from a bag: the recovered text plus the source\n * it came from — either the exact attribute key that matched, or `\"messages\"`\n * when it came from the first user message of a parsed messages array. Lets a\n * caller record traceable evidence (which key carried the intent) while still\n * resolving through the ONE shared vocabulary. */\nexport interface DeclaredIntentMatch {\n text: string;\n /** The matched attribute key, or `\"messages\"` for a messages-array source. */\n source: string;\n}\n\n/**\n * Recover the declared intent from a flattened attribute bag through the shared\n * task/prompt/messages vocabulary, reporting WHICH key matched. The span path\n * and the log-record path both call this, so they resolve intent through one\n * key source and a key added to the shared layer is read by both. Walks the\n * `task` field's keys, then the `prompt` field's keys (which include the\n * span-only `user_request` / `input` / `llm.input` spellings folded into the\n * priority list), then the first user message of a messages body. Returns null\n * when nothing usable is present — never a fabricated intent.\n */\nexport function resolveDeclaredIntent(\n bag: Record<string, unknown> | null | undefined,\n): DeclaredIntentMatch | null {\n if (!bag) return null;\n for (const field of [\"task\", \"prompt\"] as const) {\n for (const key of fieldKeyOrder(field)) {\n if (!(key in bag)) continue;\n const text = asContentField(bag[key]);\n if (text) return { text, source: key };\n }\n }\n for (const key of fieldKeyOrder(\"messages\")) {\n if (!(key in bag)) continue;\n const msgs = asMessages(bag[key]);\n if (!msgs) continue;\n const text = declaredTaskText({ messages: msgs });\n if (text) return { text, source: \"messages\" };\n }\n return null;\n}\n\n/**\n * Best-effort declared-task text from normalized content, in the order a\n * declared intent is most likely to live: an explicit task, then the prompt,\n * then the first user message in a messages array. Deliberately does NOT\n * consult `completion` — an assistant completion is the agent's OUTPUT, not the\n * declared task; treating it as the task would fabricate intent. Returns null\n * when no genuine declared-task source is present — the caller skips, never\n * fabricates.\n */\nexport function declaredTaskText(content: NormalizedContent): string | null {\n if (content.task) return content.task;\n if (content.prompt) return content.prompt;\n if (content.messages) {\n for (const m of content.messages) {\n if (!m || typeof m !== \"object\") continue;\n const msg = m as Record<string, unknown>;\n const role = asContentField(msg.role);\n if (role && role !== \"user\") continue;\n const text = readMessageText(msg);\n if (text) return text;\n }\n }\n return null;\n}\n","/**\n * OpenTelemetry GenAI semantic-convention span attributes — the single\n * vocabulary every trace producer in the stack emits and the intelligence\n * ingest reads, so model / token / cost attribution is identical regardless of\n * which producer lowered the span (workflow emitter, SDK trace sink, …).\n *\n * Writers emit the PRIMARY keys (`GEN_AI_*`). The ingest reader accepts the\n * wider candidate lists (`GEN_AI_*_KEYS`) because foreign SDKs spell the same\n * field differently (Langfuse, raw OTLP GenAI, our own legacy `model`).\n * Co-locating writer keys and reader candidates here is the drift guard: a\n * writer's key and the reader's first candidate can never silently disagree.\n *\n * Spec: OpenTelemetry GenAI semantic conventions (`gen_ai.*`).\n */\n\n/** Model that served a request — the primary key writers emit. */\nexport const GEN_AI_REQUEST_MODEL = \"gen_ai.request.model\";\n/** Model named on a response — a reader fallback; writers prefer request.model. */\nexport const GEN_AI_RESPONSE_MODEL = \"gen_ai.response.model\";\n/** Prompt / input token count. */\nexport const GEN_AI_USAGE_INPUT_TOKENS = \"gen_ai.usage.input_tokens\";\n/** Completion / output token count. */\nexport const GEN_AI_USAGE_OUTPUT_TOKENS = \"gen_ai.usage.output_tokens\";\n/** Operation name (e.g. `\"invoke_agent\"`). */\nexport const GEN_AI_OPERATION_NAME = \"gen_ai.operation.name\";\n/** Conversation / session id grouping a multi-turn agent run. */\nexport const GEN_AI_CONVERSATION_ID = \"gen_ai.conversation.id\";\n\n/**\n * Reader candidate keys for the model, highest priority first. The first entry\n * is the writers' primary key; the rest accept foreign-SDK spellings.\n */\nexport const GEN_AI_MODEL_KEYS: readonly string[] = Object.freeze([\n GEN_AI_REQUEST_MODEL,\n GEN_AI_RESPONSE_MODEL,\n \"llm.model_name\",\n \"model\",\n]);\n\n/** Reader candidate keys for input tokens, highest priority first. These name\n * SPAN ATTRIBUTES on a lowered span — for the raw producer `tokenUsage` OBJECT\n * field names (a distinct layer) see `TOKEN_USAGE_INPUT_KEYS` in\n * `token-usage.ts`; a new producer shape may need an entry in both. */\nexport const GEN_AI_INPUT_TOKEN_KEYS: readonly string[] = Object.freeze([\n GEN_AI_USAGE_INPUT_TOKENS,\n \"gen_ai.usage.prompt_tokens\",\n \"llm.token_count.prompt\",\n]);\n\n/** Reader candidate keys for output tokens, highest priority first. */\nexport const GEN_AI_OUTPUT_TOKEN_KEYS: readonly string[] = Object.freeze([\n GEN_AI_USAGE_OUTPUT_TOKENS,\n \"gen_ai.usage.completion_tokens\",\n \"llm.token_count.completion\",\n]);\n\n/** A model/token usage record to lower into the GenAI attribute bag. */\nexport interface GenAiUsage {\n /** Model slug; omitted from the attribute bag when absent or empty. */\n model?: string;\n /** Prompt tokens; omitted unless a finite, non-negative number. */\n inputTokens?: number;\n /** Completion tokens; omitted unless a finite, non-negative number. */\n outputTokens?: number;\n}\n\nfunction isNonNegativeFinite(n: number | undefined): n is number {\n return typeof n === \"number\" && Number.isFinite(n) && n >= 0;\n}\n\n/**\n * Lower a usage record to the GenAI semantic-convention attribute bag, OMITTING\n * any unknown field. A synthesized zero token count or empty model would\n * corrupt every downstream token/cost aggregate, so absent stays absent — the\n * ingest then reads \"cost not computed\", never \"free\". This is the writer-side\n * mirror of {@link GEN_AI_MODEL_KEYS} / the `*_TOKEN_KEYS` reader candidates.\n */\nexport function genAiUsageAttributes(\n usage: GenAiUsage,\n): Record<string, string | number> {\n const attrs: Record<string, string | number> = {};\n if (typeof usage.model === \"string\" && usage.model.length > 0) {\n attrs[GEN_AI_REQUEST_MODEL] = usage.model;\n }\n if (isNonNegativeFinite(usage.inputTokens)) {\n attrs[GEN_AI_USAGE_INPUT_TOKENS] = usage.inputTokens;\n }\n if (isNonNegativeFinite(usage.outputTokens)) {\n attrs[GEN_AI_USAGE_OUTPUT_TOKENS] = usage.outputTokens;\n }\n return attrs;\n}\n","/**\n * Spine run/subject/tree + trigger span-attribute vocabulary — the single set of\n * `tangle.*` keys that name a run, its parent, its kind, its trigger, and the\n * durable subject it worked on. Co-located here so the WRITER (the platform\n * workflow-trace-emitter) and the READERS (intelligence-api's run-attrs derivers\n * + reconstruct) key off one source and can never silently drift.\n *\n * Writers emit the PRIMARY keys (`*_KEY`). The ingest reader accepts the wider\n * CANDIDATE lists (`*_ATTRS`) because the emit sites disagree on spelling: the\n * workflow emitter writes camel `tangle.runId`, the agent setup prompt documents\n * dotted `tangle.run.id`. Each candidate list's first entry is a writer primary\n * key, so the drift guard (`spine-attributes.test.ts`) can assert writer/reader\n * agreement.\n *\n * The DERIVATION LOGIC (priority resolution, parent!=own-run guard, run-kind\n * inference, subject `'default'` fallback, `source:id` formatting) lives in\n * intelligence-api's `run-attrs.ts`; only the key constants and candidate lists\n * live here. The foreign subject candidates (`symphony.issue.identifier`,\n * `git.repository`, `service.name`) are members of {@link SUBJECT_KEY_ATTRS}.\n */\n\n/** Primary run-id key writers stamp; first entry of {@link RUN_KEY_ATTRS}. */\nexport const TANGLE_RUN_ID_KEY = \"tangle.run.id\";\n/** Camel run-id key the workflow emitter actually writes on every span. */\nexport const TANGLE_RUN_ID_CAMEL_KEY = \"tangle.runId\";\n/** Primary parent-run-id key; first entry of {@link PARENT_RUN_KEY_ATTRS}. */\nexport const TANGLE_RUN_PARENT_ID_KEY = \"tangle.run.parent_id\";\n/** Primary run-kind key; the sole entry of {@link RUN_KIND_ATTRS}. */\nexport const TANGLE_RUN_KIND_KEY = \"tangle.run.kind\";\n/** Primary subject key; first entry of {@link SUBJECT_KEY_ATTRS}. */\nexport const TANGLE_SUBJECT_KEY = \"tangle.subject.key\";\n/** Primary trigger-source key; first entry of {@link TRIGGER_SOURCE_ATTRS}. */\nexport const TANGLE_TRIGGER_SOURCE_KEY = \"tangle.workflow.trigger.source\";\n/** Trigger-kind key the workflow emitter writes on the root span. */\nexport const TANGLE_TRIGGER_KIND_KEY = \"tangle.trigger.kind\";\n/** Primary trigger-id key; first entry of {@link TRIGGER_ID_ATTRS}. */\nexport const TANGLE_TRIGGER_ID_KEY = \"tangle.workflow.trigger.id\";\n/** Workflow-id key the workflow emitter writes on the root span; a\n * {@link WORKFLOW_MARKER_ATTRS} member. */\nexport const TANGLE_WORKFLOW_ID_KEY = \"tangle.workflowId\";\n\n/** Run-id reader candidates, highest priority first. */\nexport const RUN_KEY_ATTRS: readonly string[] = Object.freeze([\n TANGLE_RUN_ID_KEY,\n TANGLE_RUN_ID_CAMEL_KEY,\n]);\n\n/**\n * Parent-run-id reader candidates, highest priority first. After the explicit\n * parent keys come the workflow-run ids, which name a parent only when this span\n * is a distinct child run (the deriver's own-run guard enforces that).\n */\nexport const PARENT_RUN_KEY_ATTRS: readonly string[] = Object.freeze([\n TANGLE_RUN_PARENT_ID_KEY,\n \"tangle.run.parentId\",\n \"tangle.workflow.parent.run.id\",\n \"tangle.workflow.run.id\",\n \"tangle.workflow.runId\",\n]);\n\n/** Trigger-source reader candidates, highest priority first. */\nexport const TRIGGER_SOURCE_ATTRS: readonly string[] = Object.freeze([\n TANGLE_TRIGGER_SOURCE_KEY,\n \"tangle.trigger.source\",\n TANGLE_TRIGGER_KIND_KEY,\n]);\n\n/** Trigger-id reader candidates, highest priority first. */\nexport const TRIGGER_ID_ATTRS: readonly string[] = Object.freeze([\n TANGLE_TRIGGER_ID_KEY,\n \"tangle.trigger.id\",\n]);\n\n/**\n * Subject-key reader candidates, highest priority first: declared subject key /\n * project, else the agent's own work-item identity, else repo, else service. The\n * deriver appends a `'default'` fallback so no run is orphaned.\n */\nexport const SUBJECT_KEY_ATTRS: readonly string[] = Object.freeze([\n TANGLE_SUBJECT_KEY,\n \"tangle.project.name\",\n \"symphony.issue.identifier\",\n \"git.repository\",\n \"service.name\",\n]);\n\n/** Explicit run-kind reader candidates (exact `workflow`/`consultant`/`session`\n * wins in the deriver). */\nexport const RUN_KIND_ATTRS: readonly string[] = Object.freeze([\n TANGLE_RUN_KIND_KEY,\n]);\n\n/** Workflow-presence markers — any one present implies a `workflow` run kind. */\nexport const WORKFLOW_MARKER_ATTRS: readonly string[] = Object.freeze([\n \"tangle.workflow.id\",\n TANGLE_WORKFLOW_ID_KEY,\n \"tangle.workflow.name\",\n \"tangle.workflow.run.id\",\n \"tangle.workflow.runId\",\n]);\n","/**\n * Candidate field names for a producer-supplied `tokenUsage` bag on a\n * `message.updated` trace event. Agents and SDK layers disagree on the\n * spelling: `StreamTokenUsage` emits `inputTokens`/`outputTokens`, the eval and\n * workflow agent.run paths emit `input`/`output`, and persisted / foreign\n * shapes use snake_case or prompt/completion naming. A reader that wants the\n * token counts must try all of them, highest-priority first, or it silently\n * drops usage for the producers it does not name.\n *\n * Shared so every `tokenUsage` reader (the trace sink's root-span aggregation,\n * the signal extractor) keys off the identical set and cannot drift.\n *\n * Distinct layer from `genai-attributes.ts`: those keys (`GEN_AI_*_TOKEN_KEYS`)\n * name OTel SPAN ATTRIBUTES on an already-lowered span; these name the fields of\n * the raw producer `tokenUsage` OBJECT before lowering. A new producer that\n * spells token usage differently may need an entry in BOTH places.\n */\nexport const TOKEN_USAGE_INPUT_KEYS: readonly string[] = Object.freeze([\n \"inputTokens\",\n \"input\",\n \"input_tokens\",\n \"promptTokens\",\n \"prompt_tokens\",\n]);\n\nexport const TOKEN_USAGE_OUTPUT_KEYS: readonly string[] = Object.freeze([\n \"outputTokens\",\n \"output\",\n \"output_tokens\",\n \"completionTokens\",\n \"completion_tokens\",\n]);\n\nexport const TOKEN_USAGE_COST_KEYS: readonly string[] = Object.freeze([\n \"totalCostUsd\",\n \"costUsd\",\n \"total_cost_usd\",\n \"cost_usd\",\n \"cost\",\n]);\n\nexport interface TokenUsageCounts {\n inputTokens: number;\n outputTokens: number;\n}\n\nexport function tokenCount(value: unknown): number | undefined {\n if (typeof value === \"boolean\") return undefined;\n let n: number;\n if (typeof value === \"number\") {\n n = value;\n } else if (typeof value === \"string\") {\n const trimmed = value.trim();\n n = trimmed === \"\" ? Number.NaN : Number(trimmed);\n } else {\n n = Number.NaN;\n }\n if (!Number.isFinite(n) || n < 0) return undefined;\n return Math.trunc(n);\n}\n\nexport function firstTokenCount(\n source: Record<string, unknown> | undefined,\n keys: readonly string[],\n): number | undefined {\n if (!source) return undefined;\n for (const key of keys) {\n const count = tokenCount(source[key]);\n if (count !== undefined) return count;\n }\n return undefined;\n}\n\nfunction finiteNonNegativeNumber(value: unknown): number | undefined {\n if (typeof value === \"boolean\") return undefined;\n let n: number;\n if (typeof value === \"number\") {\n n = value;\n } else if (typeof value === \"string\") {\n const trimmed = value.trim();\n n = trimmed === \"\" ? Number.NaN : Number(trimmed);\n } else {\n n = Number.NaN;\n }\n return Number.isFinite(n) && n >= 0 ? n : undefined;\n}\n\nexport function firstUsageCostUsd(\n source: Record<string, unknown> | undefined,\n keys: readonly string[] = TOKEN_USAGE_COST_KEYS,\n): number | undefined {\n if (!source) return undefined;\n for (const key of keys) {\n const cost = finiteNonNegativeNumber(source[key]);\n if (cost !== undefined) return cost;\n }\n return undefined;\n}\n\nexport function tokenUsageSource(\n data: Record<string, unknown>,\n): Record<string, unknown> {\n if (data.tokenUsage && typeof data.tokenUsage === \"object\") {\n return data.tokenUsage as Record<string, unknown>;\n }\n if (data.usage && typeof data.usage === \"object\") {\n return data.usage as Record<string, unknown>;\n }\n return data;\n}\n\nexport function readTokenUsage(\n data: Record<string, unknown>,\n): TokenUsageCounts | undefined {\n const source = tokenUsageSource(data);\n const inputTokens = firstTokenCount(source, TOKEN_USAGE_INPUT_KEYS);\n const outputTokens = firstTokenCount(source, TOKEN_USAGE_OUTPUT_KEYS);\n if (inputTokens === undefined && outputTokens === undefined) {\n return undefined;\n }\n return {\n inputTokens: inputTokens ?? 0,\n outputTokens: outputTokens ?? 0,\n };\n}\n\nexport function readTokenCostUsd(\n data: Record<string, unknown>,\n): number | undefined {\n const direct = firstUsageCostUsd(data);\n if (direct !== undefined) return direct;\n return firstUsageCostUsd(tokenUsageSource(data));\n}\n\nexport function addTokenUsage(\n current: TokenUsageCounts | undefined,\n next: TokenUsageCounts,\n): TokenUsageCounts {\n return current\n ? {\n inputTokens: current.inputTokens + next.inputTokens,\n outputTokens: current.outputTokens + next.outputTokens,\n }\n : next;\n}\n"],"mappings":"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"}