@tangle-network/agent-core 0.4.34 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index-Cm-DkFuR.d.ts → index-BJ31RMR3.d.ts} +84 -2
- package/dist/index-BJ31RMR3.d.ts.map +1 -0
- package/dist/{index-B3GE-P6Q.d.ts → index-Dz1ZLn3K.d.ts} +2 -2
- package/dist/{index-B3GE-P6Q.d.ts.map → index-Dz1ZLn3K.d.ts.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/telemetry/index.d.ts +2 -2
- package/dist/telemetry/index.js +2 -2
- package/dist/{telemetry-dTKTPI8R.js → telemetry-x1wNw1P4.js} +185 -9
- package/dist/{telemetry-dTKTPI8R.js.map → telemetry-x1wNw1P4.js.map} +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +2 -2
- package/dist/index-Cm-DkFuR.d.ts.map +0 -1
|
@@ -681,6 +681,12 @@ const GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
|
|
|
681
681
|
const GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
|
|
682
682
|
/** Completion / output token count. */
|
|
683
683
|
const GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
|
|
684
|
+
/** Prompt tokens served from the provider's prompt cache. Emitted separately
|
|
685
|
+
* from {@link GEN_AI_USAGE_INPUT_TOKENS} because the two bill at rates ~50x
|
|
686
|
+
* apart, so an aggregate that cannot tell them apart cannot compute cost. */
|
|
687
|
+
const GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read_input_tokens";
|
|
688
|
+
/** Prompt tokens written INTO the provider's prompt cache. */
|
|
689
|
+
const GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = "gen_ai.usage.cache_creation_input_tokens";
|
|
684
690
|
/** Operation name (e.g. `"invoke_agent"`). */
|
|
685
691
|
const GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
|
|
686
692
|
/** Conversation / session id grouping a multi-turn agent run. */
|
|
@@ -725,6 +731,8 @@ function genAiUsageAttributes(usage) {
|
|
|
725
731
|
if (typeof usage.model === "string" && usage.model.length > 0) attrs[GEN_AI_REQUEST_MODEL] = usage.model;
|
|
726
732
|
if (isNonNegativeFinite(usage.inputTokens)) attrs[GEN_AI_USAGE_INPUT_TOKENS] = usage.inputTokens;
|
|
727
733
|
if (isNonNegativeFinite(usage.outputTokens)) attrs[GEN_AI_USAGE_OUTPUT_TOKENS] = usage.outputTokens;
|
|
734
|
+
if (isNonNegativeFinite(usage.cacheReadTokens)) attrs[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS] = usage.cacheReadTokens;
|
|
735
|
+
if (isNonNegativeFinite(usage.cacheWriteTokens)) attrs[GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS] = usage.cacheWriteTokens;
|
|
728
736
|
return attrs;
|
|
729
737
|
}
|
|
730
738
|
//#endregion
|
|
@@ -853,6 +861,87 @@ const TOKEN_USAGE_COST_KEYS = Object.freeze([
|
|
|
853
861
|
"cost_usd",
|
|
854
862
|
"cost"
|
|
855
863
|
]);
|
|
864
|
+
/**
|
|
865
|
+
* Prompt-cache counters, split by whether the provider ALREADY counted them
|
|
866
|
+
* inside its input/prompt token field. The split is not cosmetic: the two
|
|
867
|
+
* families disagree, and a reader that ignores the disagreement reports a
|
|
868
|
+
* number that is wrong in one direction or the other on every call.
|
|
869
|
+
*
|
|
870
|
+
* INCLUSIVE (OpenAI-compatible): `prompt_tokens` is the WHOLE prompt and
|
|
871
|
+
* `prompt_tokens_details.cached_tokens` names the cached share of it. Measured
|
|
872
|
+
* on router.tangle.tools against `glm-5.2` (provider `zai`):
|
|
873
|
+
* `prompt_tokens=8656, cached_tokens=8576, completion=42, total=8698` —
|
|
874
|
+
* `8656 + 42 === 8698`, so the cached tokens are inside `prompt_tokens`.
|
|
875
|
+
*
|
|
876
|
+
* EXCLUSIVE (Anthropic-native): `input_tokens` is only the UNCACHED tail and
|
|
877
|
+
* `cache_read_input_tokens` sits beside it. Measured on the same router,
|
|
878
|
+
* same request shape: `prompt_tokens=39, cache_read_input_tokens=9924,
|
|
879
|
+
* completion=10, total=49` — `39 + 10 === 49`, so the 9,924 cached tokens are
|
|
880
|
+
* NOT in `prompt_tokens`.
|
|
881
|
+
*
|
|
882
|
+
* Both shapes reach a caller through the same OpenAI-compatible endpoint, so
|
|
883
|
+
* the convention cannot be inferred from the transport — only from which key
|
|
884
|
+
* carried the count. That is why these are two lists and not one.
|
|
885
|
+
*/
|
|
886
|
+
const TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS = Object.freeze([
|
|
887
|
+
"cached_tokens",
|
|
888
|
+
"cachedTokens",
|
|
889
|
+
"cached_input_tokens",
|
|
890
|
+
"cachedInputTokens"
|
|
891
|
+
]);
|
|
892
|
+
const TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS = Object.freeze([
|
|
893
|
+
"cache_read_input_tokens",
|
|
894
|
+
"cacheReadInputTokens",
|
|
895
|
+
"cache_read_tokens",
|
|
896
|
+
"cacheReadTokens",
|
|
897
|
+
"cache_read"
|
|
898
|
+
]);
|
|
899
|
+
const TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS = Object.freeze(["cache_write_tokens", "cacheWriteTokens"]);
|
|
900
|
+
const TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS = Object.freeze([
|
|
901
|
+
"cache_creation_input_tokens",
|
|
902
|
+
"cacheCreationInputTokens",
|
|
903
|
+
"cache_creation_tokens",
|
|
904
|
+
"cacheCreationTokens",
|
|
905
|
+
"cache_write"
|
|
906
|
+
]);
|
|
907
|
+
/** Nested bags that spell cache counters with the SAME vocabulary as the flat
|
|
908
|
+
* usage object — the OpenAI-compatible `*_details` shape, whose
|
|
909
|
+
* `cached_tokens` is inclusive. Searched after the flat keys, in this order. */
|
|
910
|
+
const TOKEN_USAGE_DETAIL_KEYS = Object.freeze([
|
|
911
|
+
"prompt_tokens_details",
|
|
912
|
+
"input_tokens_details",
|
|
913
|
+
"promptTokensDetails",
|
|
914
|
+
"inputTokensDetails"
|
|
915
|
+
]);
|
|
916
|
+
/**
|
|
917
|
+
* Dedicated prompt-cache bags, which spell their counters with BARE names
|
|
918
|
+
* (`read` / `write`) that only mean "cache" because of the bag they sit in.
|
|
919
|
+
* `cache` is opencode's `step_finish.tokens.cache`; `prompt_cache` is the
|
|
920
|
+
* Tangle router's provider-normalized bag.
|
|
921
|
+
*
|
|
922
|
+
* Counters found here are treated as EXCLUSIVE of the reported input total.
|
|
923
|
+
* Measured for opencode on 2026-08-03, `glm-5.2` via the router:
|
|
924
|
+
* `{total: 17007, input: 16940, output: 3, reasoning: 0, cache: {read: 64, write: 0}}`
|
|
925
|
+
* — `16940 + 64 + 0 + 3 === 17007`, so `input` is the uncached tail. The
|
|
926
|
+
* router's `prompt_cache` only ever reaches a caller ALONGSIDE the
|
|
927
|
+
* provider-native key, which wins over this fallback, so this rule decides
|
|
928
|
+
* only the opencode-shaped case it was measured on.
|
|
929
|
+
*/
|
|
930
|
+
const TOKEN_USAGE_CACHE_BAG_KEYS = Object.freeze([
|
|
931
|
+
"cache",
|
|
932
|
+
"prompt_cache",
|
|
933
|
+
"promptCache"
|
|
934
|
+
]);
|
|
935
|
+
const TOKEN_USAGE_CACHE_BAG_READ_KEYS = Object.freeze([
|
|
936
|
+
"read",
|
|
937
|
+
"read_tokens",
|
|
938
|
+
"readTokens"
|
|
939
|
+
]);
|
|
940
|
+
const TOKEN_USAGE_CACHE_BAG_WRITE_KEYS = Object.freeze([
|
|
941
|
+
"write",
|
|
942
|
+
"write_tokens",
|
|
943
|
+
"writeTokens"
|
|
944
|
+
]);
|
|
856
945
|
function tokenCount(value) {
|
|
857
946
|
if (typeof value === "boolean") return void 0;
|
|
858
947
|
let n;
|
|
@@ -893,14 +982,75 @@ function tokenUsageSource(data) {
|
|
|
893
982
|
if (data.usage && typeof data.usage === "object") return data.usage;
|
|
894
983
|
return data;
|
|
895
984
|
}
|
|
985
|
+
/** Every place a cache counter can hide for one call: the usage bag itself,
|
|
986
|
+
* then each nested detail bag, in `TOKEN_USAGE_DETAIL_KEYS` order. */
|
|
987
|
+
function usageBags(source) {
|
|
988
|
+
const bags = [source];
|
|
989
|
+
for (const key of TOKEN_USAGE_DETAIL_KEYS) {
|
|
990
|
+
const nested = source[key];
|
|
991
|
+
if (nested && typeof nested === "object" && !Array.isArray(nested)) bags.push(nested);
|
|
992
|
+
}
|
|
993
|
+
return bags;
|
|
994
|
+
}
|
|
995
|
+
/** First match for `keys` across the usage bag and its nested detail bags. */
|
|
996
|
+
function cacheCount(source, keys) {
|
|
997
|
+
for (const bag of usageBags(source)) {
|
|
998
|
+
const count = firstTokenCount(bag, keys);
|
|
999
|
+
if (count !== void 0) return count;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
/** First match for a bare-named counter inside a dedicated cache bag. */
|
|
1003
|
+
function cacheBagCount(source, keys) {
|
|
1004
|
+
for (const key of TOKEN_USAGE_CACHE_BAG_KEYS) {
|
|
1005
|
+
const bag = source[key];
|
|
1006
|
+
if (!bag || typeof bag !== "object" || Array.isArray(bag)) continue;
|
|
1007
|
+
const count = firstTokenCount(bag, keys);
|
|
1008
|
+
if (count !== void 0) return count;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* One provider's cache counters, normalized so the caller never has to know
|
|
1013
|
+
* which convention produced them. `inclusive` is the amount already counted
|
|
1014
|
+
* inside the reported input/prompt total and therefore must be SUBTRACTED from
|
|
1015
|
+
* it; `exclusive` sits beside that total and must not be.
|
|
1016
|
+
*/
|
|
1017
|
+
function readCacheSplit(source) {
|
|
1018
|
+
const resolve = (inclusiveKeys, exclusiveKeys, bagKeys) => {
|
|
1019
|
+
const nativeExclusive = cacheCount(source, exclusiveKeys);
|
|
1020
|
+
if (nativeExclusive !== void 0) return {
|
|
1021
|
+
count: nativeExclusive,
|
|
1022
|
+
inclusive: 0
|
|
1023
|
+
};
|
|
1024
|
+
const nativeInclusive = cacheCount(source, inclusiveKeys);
|
|
1025
|
+
if (nativeInclusive !== void 0) return {
|
|
1026
|
+
count: nativeInclusive,
|
|
1027
|
+
inclusive: nativeInclusive
|
|
1028
|
+
};
|
|
1029
|
+
const fromBag = cacheBagCount(source, bagKeys);
|
|
1030
|
+
return fromBag !== void 0 ? {
|
|
1031
|
+
count: fromBag,
|
|
1032
|
+
inclusive: 0
|
|
1033
|
+
} : { inclusive: 0 };
|
|
1034
|
+
};
|
|
1035
|
+
const read = resolve(TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS, TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS, TOKEN_USAGE_CACHE_BAG_READ_KEYS);
|
|
1036
|
+
const write = resolve(TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS, TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS, TOKEN_USAGE_CACHE_BAG_WRITE_KEYS);
|
|
1037
|
+
return {
|
|
1038
|
+
...read.count !== void 0 ? { read: read.count } : {},
|
|
1039
|
+
...write.count !== void 0 ? { write: write.count } : {},
|
|
1040
|
+
inclusive: read.inclusive + write.inclusive
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
896
1043
|
function readTokenUsage(data) {
|
|
897
1044
|
const source = tokenUsageSource(data);
|
|
898
|
-
const
|
|
1045
|
+
const reportedInput = firstTokenCount(source, TOKEN_USAGE_INPUT_KEYS);
|
|
899
1046
|
const outputTokens = firstTokenCount(source, TOKEN_USAGE_OUTPUT_KEYS);
|
|
900
|
-
|
|
1047
|
+
const cache = readCacheSplit(source);
|
|
1048
|
+
if (reportedInput === void 0 && outputTokens === void 0 && cache.read === void 0 && cache.write === void 0) return;
|
|
901
1049
|
return {
|
|
902
|
-
inputTokens:
|
|
903
|
-
outputTokens: outputTokens ?? 0
|
|
1050
|
+
inputTokens: Math.max(0, (reportedInput ?? 0) - cache.inclusive),
|
|
1051
|
+
outputTokens: outputTokens ?? 0,
|
|
1052
|
+
...cache.read !== void 0 ? { cacheReadTokens: cache.read } : {},
|
|
1053
|
+
...cache.write !== void 0 ? { cacheWriteTokens: cache.write } : {}
|
|
904
1054
|
};
|
|
905
1055
|
}
|
|
906
1056
|
function readTokenCostUsd(data) {
|
|
@@ -908,13 +1058,39 @@ function readTokenCostUsd(data) {
|
|
|
908
1058
|
if (direct !== void 0) return direct;
|
|
909
1059
|
return firstUsageCostUsd(tokenUsageSource(data));
|
|
910
1060
|
}
|
|
1061
|
+
/** Sum an optional counter across two calls, staying `undefined` only when
|
|
1062
|
+
* NEITHER side reported it — so "no producer ever mentioned the cache" stays
|
|
1063
|
+
* distinguishable from "the cache was measured and it was zero". */
|
|
1064
|
+
function addOptional(left, right) {
|
|
1065
|
+
if (left === void 0 && right === void 0) return void 0;
|
|
1066
|
+
return (left ?? 0) + (right ?? 0);
|
|
1067
|
+
}
|
|
911
1068
|
function addTokenUsage(current, next) {
|
|
912
|
-
|
|
1069
|
+
if (!current) return next;
|
|
1070
|
+
const cacheReadTokens = addOptional(current.cacheReadTokens, next.cacheReadTokens);
|
|
1071
|
+
const cacheWriteTokens = addOptional(current.cacheWriteTokens, next.cacheWriteTokens);
|
|
1072
|
+
return {
|
|
913
1073
|
inputTokens: current.inputTokens + next.inputTokens,
|
|
914
|
-
outputTokens: current.outputTokens + next.outputTokens
|
|
915
|
-
|
|
1074
|
+
outputTokens: current.outputTokens + next.outputTokens,
|
|
1075
|
+
...cacheReadTokens !== void 0 ? { cacheReadTokens } : {},
|
|
1076
|
+
...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
/** The size of the prompt the model actually saw: billed tail + cache read +
|
|
1080
|
+
* cache write. This — never `inputTokens` — is the number to read as "context
|
|
1081
|
+
* size", and the gap between them is the whole point of the cache. */
|
|
1082
|
+
function promptTokens(usage) {
|
|
1083
|
+
return usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
1084
|
+
}
|
|
1085
|
+
/** Share of the prompt served from cache, in `[0, 1]`. `undefined` when the
|
|
1086
|
+
* producer reported no cache information, so an unmetered path cannot be read
|
|
1087
|
+
* as a 0% hit rate. */
|
|
1088
|
+
function cacheHitRate(usage) {
|
|
1089
|
+
if (usage.cacheReadTokens === void 0 && usage.cacheWriteTokens === void 0) return;
|
|
1090
|
+
const total = promptTokens(usage);
|
|
1091
|
+
return total === 0 ? 0 : (usage.cacheReadTokens ?? 0) / total;
|
|
916
1092
|
}
|
|
917
1093
|
//#endregion
|
|
918
|
-
export {
|
|
1094
|
+
export { CONTENT_TOOL_ARGS_KEY as $, TANGLE_TRIGGER_ID_KEY as A, GEN_AI_OPERATION_NAME as B, RUN_KIND_ATTRS as C, TANGLE_RUN_KIND_KEY as D, TANGLE_RUN_ID_KEY as E, TRIGGER_SOURCE_ATTRS as F, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS as G, GEN_AI_REQUEST_MODEL as H, WORKFLOW_MARKER_ATTRS as I, genAiUsageAttributes as J, GEN_AI_USAGE_INPUT_TOKENS as K, GEN_AI_CONVERSATION_ID as L, TANGLE_TRIGGER_SOURCE_KEY as M, TANGLE_WORKFLOW_ID_KEY as N, TANGLE_RUN_PARENT_ID_KEY as O, TRIGGER_ID_ATTRS as P, CONTENT_PROMPT_KEY as Q, GEN_AI_INPUT_TOKEN_KEYS as R, RUN_KEY_ATTRS as S, TANGLE_RUN_ID_CAMEL_KEY as T, GEN_AI_RESPONSE_MODEL as U, GEN_AI_OUTPUT_TOKEN_KEYS as V, GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS as W, CONTENT_KEYS as X, CONTENT_COMPLETION_KEY as Y, CONTENT_KEY_FIELD as Z, readTokenCostUsd as _, TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS as a, classifyAgent as at, tokenUsageSource as b, TOKEN_USAGE_COST_KEYS as c, hasContent as ct, TOKEN_USAGE_OUTPUT_KEYS as d, resolveDeclaredIntent as dt, CONTENT_TOOL_NAME_KEY as et, addTokenUsage as f, promptTokens as g, firstUsageCostUsd as h, TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS as i, asContentString as it, TANGLE_TRIGGER_KIND_KEY as j, TANGLE_SUBJECT_KEY as k, TOKEN_USAGE_DETAIL_KEYS as l, logRecordContentBag as lt, firstTokenCount as m, TOKEN_USAGE_CACHE_BAG_READ_KEYS as n, INDEXED_CONTENT_KEY_LIKE_PATTERNS as nt, TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS as o, declaredTaskText as ot, cacheHitRate as p, GEN_AI_USAGE_OUTPUT_TOKENS as q, TOKEN_USAGE_CACHE_BAG_WRITE_KEYS as r, asContentField as rt, TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS as s, extractContent as st, TOKEN_USAGE_CACHE_BAG_KEYS as t, CONTENT_TOOL_RESULT_KEY as tt, TOKEN_USAGE_INPUT_KEYS as u, normalizeContentAttributes as ut, readTokenUsage as v, SUBJECT_KEY_ATTRS as w, PARENT_RUN_KEY_ATTRS as x, tokenCount as y, GEN_AI_MODEL_KEYS as z };
|
|
919
1095
|
|
|
920
|
-
//# sourceMappingURL=telemetry-
|
|
1096
|
+
//# sourceMappingURL=telemetry-x1wNw1P4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-dTKTPI8R.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 // NOTE: `llm.output_messages` is deliberately NOT a `messages` alias. Both\n // input and output message arrays would collapse to the single `messages`\n // field (resolved once), so the input would shadow the reply. The output side\n // is reconstructed into the SEPARATE `completion` field by\n // `normalizeContentAttributes` (see the reconstruction section below), so a\n // full turn carries BOTH the prompt (`messages`) and the reply (`completion`).\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 ],\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// ── Indexed / array / tool-call reconstruction ────────────────────────────────\n//\n// Push-OTLP providers spell a turn's content under INDEXED / ARRAY / nested keys\n// the flat alias table above cannot match:\n// OpenInference (Phoenix, LangGraph, CrewAI, LlamaIndex):\n// llm.input_messages.{i}.message.content\n// llm.output_messages.{i}.message.tool_calls.{j}.tool_call.function.arguments\n// OTel-GenAI flattened (LiteLLM, OpenAI Agents SDK, Pydantic, older OpenLLMetry):\n// gen_ai.prompt.{i}.content / gen_ai.completion.{i}.content\n// OTel-GenAI v1.28+ event arrays (Vercel AI SDK, current OpenLLMetry):\n// gen_ai.input.messages (a JSON array of { role, parts:[{ content|text }] })\n// gen_ai.output.messages\n// `extractContent({\"gen_ai.prompt.0.content\":\"hi\"})` would otherwise return {} —\n// the content silently invisible for the entire push population.\n//\n// `normalizeContentAttributes` reconstructs these into the canonical aliases the\n// reader already understands, run as a pre-pass by extractContent / hasContent /\n// resolveDeclaredIntent so every consumer reads indexed content by construction:\n// INPUT prompt → `llm.input_messages` array (the `messages` field)\n// OUTPUT reply → `gen_ai.completion` string (the SEPARATE `completion` field —\n// never a colliding message array, so a full turn keeps both)\n// tool calls → `tool.args` / `tool.name`\n// It is NON-DESTRUCTIVE (an already-present coarse key wins) and returns the SAME\n// reference when there is nothing to reconstruct, so the metadata-only hot path\n// pays only a single key scan.\n\n/** Indexed `{prefix}.{i}.…` message prefixes carrying the INPUT conversation. */\nconst INDEXED_INPUT_PREFIXES = [\n \"llm.input_messages\",\n \"gen_ai.prompt\",\n \"gen_ai.request.messages\",\n] as const;\n/** Indexed message prefixes carrying the OUTPUT reply. */\nconst INDEXED_OUTPUT_PREFIXES = [\n \"llm.output_messages\",\n \"gen_ai.completion\",\n \"gen_ai.response.messages\",\n] as const;\n/** Single-value keys holding a whole INPUT message array (JSON string or array). */\nconst ARRAY_INPUT_KEYS = [\n \"gen_ai.input.messages\",\n \"gen_ai.system_instructions\",\n] as const;\n/** Single-value keys holding a whole OUTPUT message array. */\nconst ARRAY_OUTPUT_KEYS = [\n \"llm.output_messages\",\n \"gen_ai.output.messages\",\n] as const;\n/** Completion-field alias keys — if any is present the provider sent its own\n * reply, so the output reconstruction is skipped (non-destructive). */\nconst COMPLETION_PRESENT_KEYS = [\n \"gen_ai.completion\",\n \"gen_ai.response.completion\",\n \"output.value\",\n \"message.part.text\",\n \"message.text\",\n] as const;\nconst TOOL_ARG_KEYS = [\n \"tool_call.function.arguments\",\n \"gen_ai.tool.call.arguments\",\n \"gen_ai.tool.arguments\",\n] as const;\nconst TOOL_NAME_KEYS = [\n \"tool_call.function.name\",\n \"gen_ai.tool.call.name\",\n \"gen_ai.tool.name\",\n] as const;\nconst ROLE_SUBKEYS = [\"role\", \"message.role\"];\nconst CONTENT_SUBKEYS = [\"content\", \"message.content\", \"message.text\", \"text\"];\n\n/**\n * SQL `LIKE` patterns (over `jsonb_object_keys`) that detect indexed / array /\n * tool-call content on a flattened bag — the DB-side complement to\n * `normalizeContentAttributes` for a reader (e.g. the onboarding \"content seen\"\n * check) that must decide \"does any content-bearing key exist\" in SQL without\n * materializing rows. Kept beside the reconstruction so the in-process reader and\n * the SQL predicate can never drift on which shapes count as content.\n */\nexport const INDEXED_CONTENT_KEY_LIKE_PATTERNS: readonly string[] =\n Object.freeze([\n \"llm.input_messages.%\",\n \"llm.output_messages.%\",\n \"gen_ai.prompt.%\",\n \"gen_ai.completion.%\",\n \"gen_ai.input.messages\",\n \"gen_ai.output.messages\",\n \"gen_ai.request.messages.%\",\n \"gen_ai.response.messages.%\",\n \"%.function.arguments\",\n \"gen_ai.tool.call.arguments\",\n ]);\n\nfunction maybeJson(v: unknown): unknown {\n if (typeof v !== \"string\") return v;\n const t = v.trim();\n if (!(t.startsWith(\"[\") || t.startsWith(\"{\"))) return v;\n try {\n return JSON.parse(t);\n } catch {\n return v;\n }\n}\n\n/** Reconstruct an ordered message array from a bag's `{prefix}.{i}.{sub}` keys.\n * Null when the prefix yields no text-bearing message. */\nfunction collectIndexedMessages(\n bag: Record<string, unknown>,\n prefix: string,\n): Record<string, unknown>[] | null {\n const dot = `${prefix}.`;\n const byIndex = new Map<number, Record<string, unknown>>();\n let sawContent = false;\n for (const key of Object.keys(bag)) {\n if (!key.startsWith(dot)) continue;\n const m = /^(\\d+)\\.(.+)$/.exec(key.slice(dot.length));\n if (!m || m[1] === undefined || m[2] === undefined) continue;\n const idx = Number(m[1]);\n const sub = m[2];\n const slot = byIndex.get(idx) ?? {};\n byIndex.set(idx, slot);\n if (ROLE_SUBKEYS.includes(sub) && slot.role === undefined) {\n const role = asContentField(bag[key]);\n if (role !== null) slot.role = role;\n continue;\n }\n // Multimodal content parts: {i}.…contents.{k}.…text — concatenate.\n if (\n /(?:^|\\.)contents\\.\\d+\\..*text$/.test(sub) ||\n /(?:^|\\.)parts\\.\\d+\\..*(?:text|content)$/.test(sub)\n ) {\n const part = asContentField(bag[key]);\n if (part !== null) {\n slot.content =\n slot.content === undefined\n ? part\n : `${String(slot.content)}\\n${part}`;\n sawContent = true;\n }\n continue;\n }\n if (CONTENT_SUBKEYS.includes(sub) && slot.content === undefined) {\n const c = asContentField(bag[key]);\n if (c !== null) {\n slot.content = c;\n sawContent = true;\n }\n }\n }\n if (!sawContent) return null;\n const out: Record<string, unknown>[] = [];\n for (const idx of [...byIndex.keys()].sort((a, b) => a - b)) {\n const slot = byIndex.get(idx);\n if (slot && slot.content !== undefined) out.push(slot);\n }\n return out.length > 0 ? out : null;\n}\n\n/** Coerce a single-key message-array value into `{role, content}[]`. */\nfunction coerceMessageArray(value: unknown): Record<string, unknown>[] | null {\n const v = maybeJson(value);\n const arr = Array.isArray(v)\n ? v\n : v && typeof v === \"object\"\n ? [v]\n : null;\n if (!arr) return null;\n const out: Record<string, unknown>[] = [];\n for (const el of arr) {\n if (!el || typeof el !== \"object\") {\n const s = asContentField(el);\n if (s !== null) out.push({ content: s });\n continue;\n }\n const obj = el as Record<string, unknown>;\n // OTel-GenAI part shape: { role, parts:[{ type, content|text }] }.\n const parts = obj.parts;\n if (Array.isArray(parts)) {\n const text = parts\n .map((p) =>\n p && typeof p === \"object\"\n ? asContentField(\n (p as Record<string, unknown>).content ??\n (p as Record<string, unknown>).text,\n )\n : null,\n )\n .filter((s): s is string => s !== null)\n .join(\"\\n\");\n if (text.length > 0) {\n out.push(\n obj.role !== undefined\n ? { role: obj.role, content: text }\n : { content: text },\n );\n continue;\n }\n }\n out.push(obj);\n }\n return out.length > 0 ? out : null;\n}\n\nfunction firstIndexedMessages(\n bag: Record<string, unknown>,\n prefixes: readonly string[],\n): Record<string, unknown>[] | null {\n for (const prefix of prefixes) {\n const msgs = collectIndexedMessages(bag, prefix);\n if (msgs) return msgs;\n }\n return null;\n}\n\nfunction firstArrayMessages(\n bag: Record<string, unknown>,\n keys: readonly string[],\n): Record<string, unknown>[] | null {\n for (const key of keys) {\n if (!(key in bag)) continue;\n const msgs = coerceMessageArray(bag[key]);\n if (msgs) return msgs;\n }\n return null;\n}\n\n/** Concatenate a reconstructed message array into one reply string. */\nfunction messagesToText(msgs: Record<string, unknown>[]): string {\n const parts: string[] = [];\n for (const m of msgs) {\n const t = readMessageText(m);\n if (t !== null) parts.push(t);\n }\n return parts.join(\"\\n\");\n}\n\nfunction findToolValue(\n bag: Record<string, unknown>,\n exactKeys: readonly string[],\n suffix: string,\n): string | null {\n for (const k of exactKeys) {\n if (k in bag) {\n const s = asContentString(bag[k]);\n if (s !== null) return s;\n }\n }\n for (const key of Object.keys(bag)) {\n if (key.endsWith(suffix)) {\n const s = asContentString(bag[key]);\n if (s !== null) return s;\n }\n }\n return null;\n}\n\n/** Cheap guard: does the bag carry any indexed/array/tool-call shape that needs\n * reconstruction? Lets the common (already-coarse / metadata) path return the\n * same reference with a single key scan and no allocation. */\nfunction needsNormalization(bag: Record<string, unknown>): boolean {\n for (const key of Object.keys(bag)) {\n if (\n /(?:^|\\.)(?:input_messages|output_messages|prompt|completion|messages)\\.\\d+\\./.test(\n key,\n )\n ) {\n return true;\n }\n if (\n key === \"gen_ai.input.messages\" ||\n key === \"gen_ai.output.messages\" ||\n key === \"llm.output_messages\" ||\n key === \"gen_ai.system_instructions\" ||\n key === \"gen_ai.tool.call.arguments\" ||\n key === \"gen_ai.tool.name\" ||\n key.endsWith(\".function.arguments\") ||\n key.endsWith(\".function.name\")\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Reconstruct a flattened attribute bag's indexed / array / tool-call content\n * keys into the canonical coarse aliases the reader understands. Pure and\n * NON-DESTRUCTIVE: an already-present coarse key wins, and the SAME reference is\n * returned when nothing needs reconstructing. Every content read path runs this\n * first, so a new provider's flattening is learned in ONE place.\n */\nexport function normalizeContentAttributes(\n bag: Record<string, unknown> | null | undefined,\n): Record<string, unknown> {\n if (!bag) return {};\n if (!needsNormalization(bag)) return bag;\n const out: Record<string, unknown> = { ...bag };\n\n if (!(\"llm.input_messages\" in bag)) {\n const input =\n firstIndexedMessages(bag, INDEXED_INPUT_PREFIXES) ??\n firstArrayMessages(bag, ARRAY_INPUT_KEYS);\n if (input) out[\"llm.input_messages\"] = input;\n }\n\n if (!COMPLETION_PRESENT_KEYS.some((k) => k in bag)) {\n const output =\n firstIndexedMessages(bag, INDEXED_OUTPUT_PREFIXES) ??\n firstArrayMessages(bag, ARRAY_OUTPUT_KEYS);\n if (output) {\n const text = messagesToText(output);\n if (text.length > 0) out[\"gen_ai.completion\"] = text;\n }\n }\n\n if (\n !(\"tool.args\" in bag) &&\n !(\"tool.input\" in bag) &&\n !(\"tangle.tool.args\" in bag)\n ) {\n const args = findToolValue(bag, TOOL_ARG_KEYS, \".function.arguments\");\n if (args !== null) out[\"tool.args\"] = args;\n }\n if (!(\"tool.name\" in bag) && !(\"tangle.tool.name\" in bag)) {\n const name = findToolValue(bag, TOOL_NAME_KEYS, \".function.name\");\n if (name !== null) out[\"tool.name\"] = name;\n }\n\n return out;\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 const bag = normalizeContentAttributes(attributes);\n for (const field of ALL_FIELDS) {\n const v = resolveField(bag, 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 const bag = normalizeContentAttributes(attributes);\n for (const key of PRESENCE_KEYS) {\n if (!(key in bag)) continue;\n const field = CONTENT_KEY_ALIASES[key];\n const raw = bag[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 bag0: Record<string, unknown> | null | undefined,\n): DeclaredIntentMatch | null {\n if (!bag0) return null;\n const bag = normalizeContentAttributes(bag0);\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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"}
|
|
1
|
+
{"version":3,"file":"telemetry-x1wNw1P4.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 // NOTE: `llm.output_messages` is deliberately NOT a `messages` alias. Both\n // input and output message arrays would collapse to the single `messages`\n // field (resolved once), so the input would shadow the reply. The output side\n // is reconstructed into the SEPARATE `completion` field by\n // `normalizeContentAttributes` (see the reconstruction section below), so a\n // full turn carries BOTH the prompt (`messages`) and the reply (`completion`).\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 ],\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// ── Indexed / array / tool-call reconstruction ────────────────────────────────\n//\n// Push-OTLP providers spell a turn's content under INDEXED / ARRAY / nested keys\n// the flat alias table above cannot match:\n// OpenInference (Phoenix, LangGraph, CrewAI, LlamaIndex):\n// llm.input_messages.{i}.message.content\n// llm.output_messages.{i}.message.tool_calls.{j}.tool_call.function.arguments\n// OTel-GenAI flattened (LiteLLM, OpenAI Agents SDK, Pydantic, older OpenLLMetry):\n// gen_ai.prompt.{i}.content / gen_ai.completion.{i}.content\n// OTel-GenAI v1.28+ event arrays (Vercel AI SDK, current OpenLLMetry):\n// gen_ai.input.messages (a JSON array of { role, parts:[{ content|text }] })\n// gen_ai.output.messages\n// `extractContent({\"gen_ai.prompt.0.content\":\"hi\"})` would otherwise return {} —\n// the content silently invisible for the entire push population.\n//\n// `normalizeContentAttributes` reconstructs these into the canonical aliases the\n// reader already understands, run as a pre-pass by extractContent / hasContent /\n// resolveDeclaredIntent so every consumer reads indexed content by construction:\n// INPUT prompt → `llm.input_messages` array (the `messages` field)\n// OUTPUT reply → `gen_ai.completion` string (the SEPARATE `completion` field —\n// never a colliding message array, so a full turn keeps both)\n// tool calls → `tool.args` / `tool.name`\n// It is NON-DESTRUCTIVE (an already-present coarse key wins) and returns the SAME\n// reference when there is nothing to reconstruct, so the metadata-only hot path\n// pays only a single key scan.\n\n/** Indexed `{prefix}.{i}.…` message prefixes carrying the INPUT conversation. */\nconst INDEXED_INPUT_PREFIXES = [\n \"llm.input_messages\",\n \"gen_ai.prompt\",\n \"gen_ai.request.messages\",\n] as const;\n/** Indexed message prefixes carrying the OUTPUT reply. */\nconst INDEXED_OUTPUT_PREFIXES = [\n \"llm.output_messages\",\n \"gen_ai.completion\",\n \"gen_ai.response.messages\",\n] as const;\n/** Single-value keys holding a whole INPUT message array (JSON string or array). */\nconst ARRAY_INPUT_KEYS = [\n \"gen_ai.input.messages\",\n \"gen_ai.system_instructions\",\n] as const;\n/** Single-value keys holding a whole OUTPUT message array. */\nconst ARRAY_OUTPUT_KEYS = [\n \"llm.output_messages\",\n \"gen_ai.output.messages\",\n] as const;\n/** Completion-field alias keys — if any is present the provider sent its own\n * reply, so the output reconstruction is skipped (non-destructive). */\nconst COMPLETION_PRESENT_KEYS = [\n \"gen_ai.completion\",\n \"gen_ai.response.completion\",\n \"output.value\",\n \"message.part.text\",\n \"message.text\",\n] as const;\nconst TOOL_ARG_KEYS = [\n \"tool_call.function.arguments\",\n \"gen_ai.tool.call.arguments\",\n \"gen_ai.tool.arguments\",\n] as const;\nconst TOOL_NAME_KEYS = [\n \"tool_call.function.name\",\n \"gen_ai.tool.call.name\",\n \"gen_ai.tool.name\",\n] as const;\nconst ROLE_SUBKEYS = [\"role\", \"message.role\"];\nconst CONTENT_SUBKEYS = [\"content\", \"message.content\", \"message.text\", \"text\"];\n\n/**\n * SQL `LIKE` patterns (over `jsonb_object_keys`) that detect indexed / array /\n * tool-call content on a flattened bag — the DB-side complement to\n * `normalizeContentAttributes` for a reader (e.g. the onboarding \"content seen\"\n * check) that must decide \"does any content-bearing key exist\" in SQL without\n * materializing rows. Kept beside the reconstruction so the in-process reader and\n * the SQL predicate can never drift on which shapes count as content.\n */\nexport const INDEXED_CONTENT_KEY_LIKE_PATTERNS: readonly string[] =\n Object.freeze([\n \"llm.input_messages.%\",\n \"llm.output_messages.%\",\n \"gen_ai.prompt.%\",\n \"gen_ai.completion.%\",\n \"gen_ai.input.messages\",\n \"gen_ai.output.messages\",\n \"gen_ai.request.messages.%\",\n \"gen_ai.response.messages.%\",\n \"%.function.arguments\",\n \"gen_ai.tool.call.arguments\",\n ]);\n\nfunction maybeJson(v: unknown): unknown {\n if (typeof v !== \"string\") return v;\n const t = v.trim();\n if (!(t.startsWith(\"[\") || t.startsWith(\"{\"))) return v;\n try {\n return JSON.parse(t);\n } catch {\n return v;\n }\n}\n\n/** Reconstruct an ordered message array from a bag's `{prefix}.{i}.{sub}` keys.\n * Null when the prefix yields no text-bearing message. */\nfunction collectIndexedMessages(\n bag: Record<string, unknown>,\n prefix: string,\n): Record<string, unknown>[] | null {\n const dot = `${prefix}.`;\n const byIndex = new Map<number, Record<string, unknown>>();\n let sawContent = false;\n for (const key of Object.keys(bag)) {\n if (!key.startsWith(dot)) continue;\n const m = /^(\\d+)\\.(.+)$/.exec(key.slice(dot.length));\n if (!m || m[1] === undefined || m[2] === undefined) continue;\n const idx = Number(m[1]);\n const sub = m[2];\n const slot = byIndex.get(idx) ?? {};\n byIndex.set(idx, slot);\n if (ROLE_SUBKEYS.includes(sub) && slot.role === undefined) {\n const role = asContentField(bag[key]);\n if (role !== null) slot.role = role;\n continue;\n }\n // Multimodal content parts: {i}.…contents.{k}.…text — concatenate.\n if (\n /(?:^|\\.)contents\\.\\d+\\..*text$/.test(sub) ||\n /(?:^|\\.)parts\\.\\d+\\..*(?:text|content)$/.test(sub)\n ) {\n const part = asContentField(bag[key]);\n if (part !== null) {\n slot.content =\n slot.content === undefined\n ? part\n : `${String(slot.content)}\\n${part}`;\n sawContent = true;\n }\n continue;\n }\n if (CONTENT_SUBKEYS.includes(sub) && slot.content === undefined) {\n const c = asContentField(bag[key]);\n if (c !== null) {\n slot.content = c;\n sawContent = true;\n }\n }\n }\n if (!sawContent) return null;\n const out: Record<string, unknown>[] = [];\n for (const idx of [...byIndex.keys()].sort((a, b) => a - b)) {\n const slot = byIndex.get(idx);\n if (slot && slot.content !== undefined) out.push(slot);\n }\n return out.length > 0 ? out : null;\n}\n\n/** Coerce a single-key message-array value into `{role, content}[]`. */\nfunction coerceMessageArray(value: unknown): Record<string, unknown>[] | null {\n const v = maybeJson(value);\n const arr = Array.isArray(v)\n ? v\n : v && typeof v === \"object\"\n ? [v]\n : null;\n if (!arr) return null;\n const out: Record<string, unknown>[] = [];\n for (const el of arr) {\n if (!el || typeof el !== \"object\") {\n const s = asContentField(el);\n if (s !== null) out.push({ content: s });\n continue;\n }\n const obj = el as Record<string, unknown>;\n // OTel-GenAI part shape: { role, parts:[{ type, content|text }] }.\n const parts = obj.parts;\n if (Array.isArray(parts)) {\n const text = parts\n .map((p) =>\n p && typeof p === \"object\"\n ? asContentField(\n (p as Record<string, unknown>).content ??\n (p as Record<string, unknown>).text,\n )\n : null,\n )\n .filter((s): s is string => s !== null)\n .join(\"\\n\");\n if (text.length > 0) {\n out.push(\n obj.role !== undefined\n ? { role: obj.role, content: text }\n : { content: text },\n );\n continue;\n }\n }\n out.push(obj);\n }\n return out.length > 0 ? out : null;\n}\n\nfunction firstIndexedMessages(\n bag: Record<string, unknown>,\n prefixes: readonly string[],\n): Record<string, unknown>[] | null {\n for (const prefix of prefixes) {\n const msgs = collectIndexedMessages(bag, prefix);\n if (msgs) return msgs;\n }\n return null;\n}\n\nfunction firstArrayMessages(\n bag: Record<string, unknown>,\n keys: readonly string[],\n): Record<string, unknown>[] | null {\n for (const key of keys) {\n if (!(key in bag)) continue;\n const msgs = coerceMessageArray(bag[key]);\n if (msgs) return msgs;\n }\n return null;\n}\n\n/** Concatenate a reconstructed message array into one reply string. */\nfunction messagesToText(msgs: Record<string, unknown>[]): string {\n const parts: string[] = [];\n for (const m of msgs) {\n const t = readMessageText(m);\n if (t !== null) parts.push(t);\n }\n return parts.join(\"\\n\");\n}\n\nfunction findToolValue(\n bag: Record<string, unknown>,\n exactKeys: readonly string[],\n suffix: string,\n): string | null {\n for (const k of exactKeys) {\n if (k in bag) {\n const s = asContentString(bag[k]);\n if (s !== null) return s;\n }\n }\n for (const key of Object.keys(bag)) {\n if (key.endsWith(suffix)) {\n const s = asContentString(bag[key]);\n if (s !== null) return s;\n }\n }\n return null;\n}\n\n/** Cheap guard: does the bag carry any indexed/array/tool-call shape that needs\n * reconstruction? Lets the common (already-coarse / metadata) path return the\n * same reference with a single key scan and no allocation. */\nfunction needsNormalization(bag: Record<string, unknown>): boolean {\n for (const key of Object.keys(bag)) {\n if (\n /(?:^|\\.)(?:input_messages|output_messages|prompt|completion|messages)\\.\\d+\\./.test(\n key,\n )\n ) {\n return true;\n }\n if (\n key === \"gen_ai.input.messages\" ||\n key === \"gen_ai.output.messages\" ||\n key === \"llm.output_messages\" ||\n key === \"gen_ai.system_instructions\" ||\n key === \"gen_ai.tool.call.arguments\" ||\n key === \"gen_ai.tool.name\" ||\n key.endsWith(\".function.arguments\") ||\n key.endsWith(\".function.name\")\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Reconstruct a flattened attribute bag's indexed / array / tool-call content\n * keys into the canonical coarse aliases the reader understands. Pure and\n * NON-DESTRUCTIVE: an already-present coarse key wins, and the SAME reference is\n * returned when nothing needs reconstructing. Every content read path runs this\n * first, so a new provider's flattening is learned in ONE place.\n */\nexport function normalizeContentAttributes(\n bag: Record<string, unknown> | null | undefined,\n): Record<string, unknown> {\n if (!bag) return {};\n if (!needsNormalization(bag)) return bag;\n const out: Record<string, unknown> = { ...bag };\n\n if (!(\"llm.input_messages\" in bag)) {\n const input =\n firstIndexedMessages(bag, INDEXED_INPUT_PREFIXES) ??\n firstArrayMessages(bag, ARRAY_INPUT_KEYS);\n if (input) out[\"llm.input_messages\"] = input;\n }\n\n if (!COMPLETION_PRESENT_KEYS.some((k) => k in bag)) {\n const output =\n firstIndexedMessages(bag, INDEXED_OUTPUT_PREFIXES) ??\n firstArrayMessages(bag, ARRAY_OUTPUT_KEYS);\n if (output) {\n const text = messagesToText(output);\n if (text.length > 0) out[\"gen_ai.completion\"] = text;\n }\n }\n\n if (\n !(\"tool.args\" in bag) &&\n !(\"tool.input\" in bag) &&\n !(\"tangle.tool.args\" in bag)\n ) {\n const args = findToolValue(bag, TOOL_ARG_KEYS, \".function.arguments\");\n if (args !== null) out[\"tool.args\"] = args;\n }\n if (!(\"tool.name\" in bag) && !(\"tangle.tool.name\" in bag)) {\n const name = findToolValue(bag, TOOL_NAME_KEYS, \".function.name\");\n if (name !== null) out[\"tool.name\"] = name;\n }\n\n return out;\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 const bag = normalizeContentAttributes(attributes);\n for (const field of ALL_FIELDS) {\n const v = resolveField(bag, 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 const bag = normalizeContentAttributes(attributes);\n for (const key of PRESENCE_KEYS) {\n if (!(key in bag)) continue;\n const field = CONTENT_KEY_ALIASES[key];\n const raw = bag[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 bag0: Record<string, unknown> | null | undefined,\n): DeclaredIntentMatch | null {\n if (!bag0) return null;\n const bag = normalizeContentAttributes(bag0);\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\n/** Prompt tokens served from the provider's prompt cache. Emitted separately\n * from {@link GEN_AI_USAGE_INPUT_TOKENS} because the two bill at rates ~50x\n * apart, so an aggregate that cannot tell them apart cannot compute cost. */\nexport const GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS =\n \"gen_ai.usage.cache_read_input_tokens\";\n\n/** Prompt tokens written INTO the provider's prompt cache. */\nexport const GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS =\n \"gen_ai.usage.cache_creation_input_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 /** Prompt tokens served from cache; omitted unless finite and non-negative. */\n cacheReadTokens?: number;\n /** Prompt tokens written into cache; omitted unless finite and non-negative. */\n cacheWriteTokens?: 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 // Emitted alongside, never folded into, `input_tokens`. `input_tokens` is the\n // freshly billed tail; a consumer that wants the whole prompt sums the three,\n // and one that wants cost prices them separately. Folding them together would\n // make a warm call indistinguishable from a cold one on the exact attribute\n // cost aggregates key off.\n if (isNonNegativeFinite(usage.cacheReadTokens)) {\n attrs[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS] = usage.cacheReadTokens;\n }\n if (isNonNegativeFinite(usage.cacheWriteTokens)) {\n attrs[GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS] = usage.cacheWriteTokens;\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\n/**\n * Prompt-cache counters, split by whether the provider ALREADY counted them\n * inside its input/prompt token field. The split is not cosmetic: the two\n * families disagree, and a reader that ignores the disagreement reports a\n * number that is wrong in one direction or the other on every call.\n *\n * INCLUSIVE (OpenAI-compatible): `prompt_tokens` is the WHOLE prompt and\n * `prompt_tokens_details.cached_tokens` names the cached share of it. Measured\n * on router.tangle.tools against `glm-5.2` (provider `zai`):\n * `prompt_tokens=8656, cached_tokens=8576, completion=42, total=8698` —\n * `8656 + 42 === 8698`, so the cached tokens are inside `prompt_tokens`.\n *\n * EXCLUSIVE (Anthropic-native): `input_tokens` is only the UNCACHED tail and\n * `cache_read_input_tokens` sits beside it. Measured on the same router,\n * same request shape: `prompt_tokens=39, cache_read_input_tokens=9924,\n * completion=10, total=49` — `39 + 10 === 49`, so the 9,924 cached tokens are\n * NOT in `prompt_tokens`.\n *\n * Both shapes reach a caller through the same OpenAI-compatible endpoint, so\n * the convention cannot be inferred from the transport — only from which key\n * carried the count. That is why these are two lists and not one.\n */\nexport const TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS: readonly string[] =\n Object.freeze([\n \"cached_tokens\",\n \"cachedTokens\",\n \"cached_input_tokens\",\n \"cachedInputTokens\",\n ]);\n\nexport const TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS: readonly string[] =\n Object.freeze([\n \"cache_read_input_tokens\",\n \"cacheReadInputTokens\",\n \"cache_read_tokens\",\n \"cacheReadTokens\",\n \"cache_read\",\n ]);\n\nexport const TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS: readonly string[] =\n Object.freeze([\"cache_write_tokens\", \"cacheWriteTokens\"]);\n\nexport const TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS: readonly string[] =\n Object.freeze([\n \"cache_creation_input_tokens\",\n \"cacheCreationInputTokens\",\n \"cache_creation_tokens\",\n \"cacheCreationTokens\",\n \"cache_write\",\n ]);\n\n/** Nested bags that spell cache counters with the SAME vocabulary as the flat\n * usage object — the OpenAI-compatible `*_details` shape, whose\n * `cached_tokens` is inclusive. Searched after the flat keys, in this order. */\nexport const TOKEN_USAGE_DETAIL_KEYS: readonly string[] = Object.freeze([\n \"prompt_tokens_details\",\n \"input_tokens_details\",\n \"promptTokensDetails\",\n \"inputTokensDetails\",\n]);\n\n/**\n * Dedicated prompt-cache bags, which spell their counters with BARE names\n * (`read` / `write`) that only mean \"cache\" because of the bag they sit in.\n * `cache` is opencode's `step_finish.tokens.cache`; `prompt_cache` is the\n * Tangle router's provider-normalized bag.\n *\n * Counters found here are treated as EXCLUSIVE of the reported input total.\n * Measured for opencode on 2026-08-03, `glm-5.2` via the router:\n * `{total: 17007, input: 16940, output: 3, reasoning: 0, cache: {read: 64, write: 0}}`\n * — `16940 + 64 + 0 + 3 === 17007`, so `input` is the uncached tail. The\n * router's `prompt_cache` only ever reaches a caller ALONGSIDE the\n * provider-native key, which wins over this fallback, so this rule decides\n * only the opencode-shaped case it was measured on.\n */\nexport const TOKEN_USAGE_CACHE_BAG_KEYS: readonly string[] = Object.freeze([\n \"cache\",\n \"prompt_cache\",\n \"promptCache\",\n]);\n\nexport const TOKEN_USAGE_CACHE_BAG_READ_KEYS: readonly string[] = Object.freeze(\n [\"read\", \"read_tokens\", \"readTokens\"],\n);\n\nexport const TOKEN_USAGE_CACHE_BAG_WRITE_KEYS: readonly string[] =\n Object.freeze([\"write\", \"write_tokens\", \"writeTokens\"]);\n\n/**\n * Normalized token counts for one call.\n *\n * The three prompt-side fields are DISJOINT and additive by construction:\n * `inputTokens + cacheReadTokens + cacheWriteTokens` is the size of the whole\n * prompt the model saw, and each term is billed at its own rate. `inputTokens`\n * is the freshly-billed tail ONLY — it is not the context size, and reading it\n * as one understates a warm agent loop's context by the cached share, which on\n * a stable-prefix loop is the overwhelming majority of it.\n */\nexport interface TokenUsageCounts {\n /** Prompt tokens billed at the full input rate: the uncached tail. */\n inputTokens: number;\n outputTokens: number;\n /** Prompt tokens served from the provider's prompt cache, billed at the\n * (much cheaper) cache-read rate. Absent when the producer reported no\n * cache information at all — which is not the same as a measured zero. */\n cacheReadTokens?: number;\n /** Prompt tokens written INTO the provider's cache on this call. */\n cacheWriteTokens?: 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\n/** Every place a cache counter can hide for one call: the usage bag itself,\n * then each nested detail bag, in `TOKEN_USAGE_DETAIL_KEYS` order. */\nfunction usageBags(\n source: Record<string, unknown>,\n): readonly Record<string, unknown>[] {\n const bags: Record<string, unknown>[] = [source];\n for (const key of TOKEN_USAGE_DETAIL_KEYS) {\n const nested = source[key];\n if (nested && typeof nested === \"object\" && !Array.isArray(nested)) {\n bags.push(nested as Record<string, unknown>);\n }\n }\n return bags;\n}\n\n/** First match for `keys` across the usage bag and its nested detail bags. */\nfunction cacheCount(\n source: Record<string, unknown>,\n keys: readonly string[],\n): number | undefined {\n for (const bag of usageBags(source)) {\n const count = firstTokenCount(bag, keys);\n if (count !== undefined) return count;\n }\n return undefined;\n}\n\n/** First match for a bare-named counter inside a dedicated cache bag. */\nfunction cacheBagCount(\n source: Record<string, unknown>,\n keys: readonly string[],\n): number | undefined {\n for (const key of TOKEN_USAGE_CACHE_BAG_KEYS) {\n const bag = source[key];\n if (!bag || typeof bag !== \"object\" || Array.isArray(bag)) continue;\n const count = firstTokenCount(bag as Record<string, unknown>, keys);\n if (count !== undefined) return count;\n }\n return undefined;\n}\n\n/**\n * One provider's cache counters, normalized so the caller never has to know\n * which convention produced them. `inclusive` is the amount already counted\n * inside the reported input/prompt total and therefore must be SUBTRACTED from\n * it; `exclusive` sits beside that total and must not be.\n */\nfunction readCacheSplit(source: Record<string, unknown>): {\n read?: number;\n write?: number;\n inclusive: number;\n} {\n // Precedence, and the reason for it. A provider reports ONE convention per\n // counter, but a router response can carry TWO spellings of the same tokens:\n // the upstream provider's native key, plus the router's own normalized\n // `prompt_cache` echo of it. Summing both would double the prompt, and\n // letting the echo decide the convention would mis-classify the OpenAI shape\n // (measured: `cached_tokens=8576` inclusive, echoed as\n // `prompt_cache.read_tokens=8576`). So a provider-native key ALWAYS outranks\n // the bag, and the bag is consulted only when no native key was reported.\n const resolve = (\n inclusiveKeys: readonly string[],\n exclusiveKeys: readonly string[],\n bagKeys: readonly string[],\n ): { count?: number; inclusive: number } => {\n const nativeExclusive = cacheCount(source, exclusiveKeys);\n if (nativeExclusive !== undefined) {\n return { count: nativeExclusive, inclusive: 0 };\n }\n const nativeInclusive = cacheCount(source, inclusiveKeys);\n if (nativeInclusive !== undefined) {\n return { count: nativeInclusive, inclusive: nativeInclusive };\n }\n const fromBag = cacheBagCount(source, bagKeys);\n return fromBag !== undefined\n ? { count: fromBag, inclusive: 0 }\n : { inclusive: 0 };\n };\n\n const read = resolve(\n TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS,\n TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS,\n TOKEN_USAGE_CACHE_BAG_READ_KEYS,\n );\n const write = resolve(\n TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS,\n TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS,\n TOKEN_USAGE_CACHE_BAG_WRITE_KEYS,\n );\n\n return {\n ...(read.count !== undefined ? { read: read.count } : {}),\n ...(write.count !== undefined ? { write: write.count } : {}),\n inclusive: read.inclusive + write.inclusive,\n };\n}\n\nexport function readTokenUsage(\n data: Record<string, unknown>,\n): TokenUsageCounts | undefined {\n const source = tokenUsageSource(data);\n const reportedInput = firstTokenCount(source, TOKEN_USAGE_INPUT_KEYS);\n const outputTokens = firstTokenCount(source, TOKEN_USAGE_OUTPUT_KEYS);\n const cache = readCacheSplit(source);\n if (\n reportedInput === undefined &&\n outputTokens === undefined &&\n cache.read === undefined &&\n cache.write === undefined\n ) {\n return undefined;\n }\n\n // Clamped, because a provider whose cached count exceeds its own reported\n // prompt total is reporting something we cannot reconcile — a negative\n // billed-tail is never the right answer, and silently emitting one would\n // corrupt every cost sum downstream.\n const inputTokens = Math.max(0, (reportedInput ?? 0) - cache.inclusive);\n\n return {\n inputTokens,\n outputTokens: outputTokens ?? 0,\n ...(cache.read !== undefined ? { cacheReadTokens: cache.read } : {}),\n ...(cache.write !== undefined ? { cacheWriteTokens: cache.write } : {}),\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\n/** Sum an optional counter across two calls, staying `undefined` only when\n * NEITHER side reported it — so \"no producer ever mentioned the cache\" stays\n * distinguishable from \"the cache was measured and it was zero\". */\nfunction addOptional(\n left: number | undefined,\n right: number | undefined,\n): number | undefined {\n if (left === undefined && right === undefined) return undefined;\n return (left ?? 0) + (right ?? 0);\n}\n\nexport function addTokenUsage(\n current: TokenUsageCounts | undefined,\n next: TokenUsageCounts,\n): TokenUsageCounts {\n if (!current) return next;\n const cacheReadTokens = addOptional(\n current.cacheReadTokens,\n next.cacheReadTokens,\n );\n const cacheWriteTokens = addOptional(\n current.cacheWriteTokens,\n next.cacheWriteTokens,\n );\n return {\n inputTokens: current.inputTokens + next.inputTokens,\n outputTokens: current.outputTokens + next.outputTokens,\n ...(cacheReadTokens !== undefined ? { cacheReadTokens } : {}),\n ...(cacheWriteTokens !== undefined ? { cacheWriteTokens } : {}),\n };\n}\n\n/** The size of the prompt the model actually saw: billed tail + cache read +\n * cache write. This — never `inputTokens` — is the number to read as \"context\n * size\", and the gap between them is the whole point of the cache. */\nexport function promptTokens(usage: TokenUsageCounts): number {\n return (\n usage.inputTokens +\n (usage.cacheReadTokens ?? 0) +\n (usage.cacheWriteTokens ?? 0)\n );\n}\n\n/** Share of the prompt served from cache, in `[0, 1]`. `undefined` when the\n * producer reported no cache information, so an unmetered path cannot be read\n * as a 0% hit rate. */\nexport function cacheHitRate(usage: TokenUsageCounts): number | undefined {\n if (usage.cacheReadTokens === undefined && usage.cacheWriteTokens === undefined) {\n return undefined;\n }\n const total = promptTokens(usage);\n return total === 0 ? 0 : (usage.cacheReadTokens ?? 0) / total;\n}\n"],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as ToolStateCompleted, C as TerminalDataEvent, D as TextPart, E as TerminalResizeEvent, F as isReasoningPart, I as isSubtaskPart, L as isTextPart, M as ToolStatePending, N as ToolStateRunning, O as ToolPart, P as isFilePart, R as isToolPart, S as SubtaskPart, T as TerminalExitEvent, _ as ReplayEndEvent, a as DoneEvent, b as SessionUpdatedEvent, c as ExecutionCompletedEvent, d as FilePart, f as HeartbeatEvent, g as ReasoningPart, h as PartBase, i as ConnectionInitEvent, j as ToolStateError, k as ToolState, l as ExecutionFailedEvent, m as Part, n as AgentEventType, o as ErrorEvent, p as MessagePartUpdatedEvent, r as AgentEventTypeSchema, s as EventMetadata, t as AgentEvent, u as ExecutionStartedEvent, v as ReplayStartEvent, w as TerminalEvent, x as StatusEvent, y as ResultEvent } from "../index-
|
|
1
|
+
import { A as ToolStateCompleted, C as TerminalDataEvent, D as TextPart, E as TerminalResizeEvent, F as isReasoningPart, I as isSubtaskPart, L as isTextPart, M as ToolStatePending, N as ToolStateRunning, O as ToolPart, P as isFilePart, R as isToolPart, S as SubtaskPart, T as TerminalExitEvent, _ as ReplayEndEvent, a as DoneEvent, b as SessionUpdatedEvent, c as ExecutionCompletedEvent, d as FilePart, f as HeartbeatEvent, g as ReasoningPart, h as PartBase, i as ConnectionInitEvent, j as ToolStateError, k as ToolState, l as ExecutionFailedEvent, m as Part, n as AgentEventType, o as ErrorEvent, p as MessagePartUpdatedEvent, r as AgentEventTypeSchema, s as EventMetadata, t as AgentEvent, u as ExecutionStartedEvent, v as ReplayStartEvent, w as TerminalEvent, x as StatusEvent, y as ResultEvent } from "../index-Dz1ZLn3K.js";
|
|
2
2
|
export { AgentEvent, AgentEventType, AgentEventTypeSchema, ConnectionInitEvent, DoneEvent, ErrorEvent, EventMetadata, ExecutionCompletedEvent, ExecutionFailedEvent, ExecutionStartedEvent, FilePart, HeartbeatEvent, MessagePartUpdatedEvent, Part, PartBase, ReasoningPart, ReplayEndEvent, ReplayStartEvent, ResultEvent, SessionUpdatedEvent, StatusEvent, SubtaskPart, TerminalDataEvent, TerminalEvent, TerminalExitEvent, TerminalResizeEvent, TextPart, ToolPart, ToolState, ToolStateCompleted, ToolStateError, ToolStatePending, ToolStateRunning, isFilePart, isReasoningPart, isSubtaskPart, isTextPart, isToolPart };
|