autotel-agents 0.1.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/index.cjs +702 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +335 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +335 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +676 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/attrs.ts","../src/cost.ts","../src/identity.ts","../src/mcp.ts","../src/tool-taxonomy.ts","../src/adapters/prefix-adapter.ts","../src/adapters/claude-code.ts","../src/adapters/opencode.ts","../src/adapters/registry.ts","../src/reduce.ts"],"sourcesContent":["/**\n * Attribute coercion helpers. OTLP attributes arrive as strings, numbers or\n * booleans depending on the SDK (Claude Code emits some numbers as strings,\n * e.g. `success: \"true\"`), so every read goes through a coercer.\n */\n\nimport type { Attributes, AttrValue } from './types';\n\nexport function str(attrs: Attributes, ...keys: string[]): string | undefined {\n for (const key of keys) {\n const value = attrs[key];\n if (typeof value === 'string' && value.length > 0) return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n }\n return undefined;\n}\n\nexport function num(attrs: Attributes, ...keys: string[]): number | undefined {\n for (const key of keys) {\n const value = attrs[key];\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'string') {\n const parsed = Number(value);\n if (Number.isFinite(parsed) && value.trim() !== '') return parsed;\n }\n }\n return undefined;\n}\n\nexport function bool(attrs: Attributes, ...keys: string[]): boolean | undefined {\n for (const key of keys) {\n const value = attrs[key];\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') {\n if (value === 'true') return true;\n if (value === 'false') return false;\n }\n }\n return undefined;\n}\n\n/** Read a value without coercion (for pass-through into `AgentEvent.attributes`). */\nexport function raw(attrs: Attributes, key: string): AttrValue | undefined {\n return attrs[key];\n}\n","/**\n * Fallback cost estimation. Reported cost (`cost_usd` on `api_request`) always\n * wins — Claude Code computes it cache-accurately. This table is ONLY used when\n * an agent reports tokens but not cost (e.g. a future agent, or a misconfigured\n * run). Estimated values are badged `estimated` in the UI.\n *\n * Prices are USD per 1,000,000 tokens. Matched by substring so model ids like\n * `claude-sonnet-4-6` or `claude-3-5-sonnet-20241022` resolve to a family rate.\n * Keep deliberately small — this is a safety net, not a billing source.\n */\n\nconst PRICES: ReadonlyArray<readonly [match: string, input: number, output: number]> = [\n ['claude-opus-4', 15, 75],\n ['claude-sonnet-4', 3, 15],\n ['claude-haiku-4', 0.8, 4],\n ['claude-3-5-sonnet', 3, 15],\n ['claude-3-5-haiku', 0.8, 4],\n ['claude-3-opus', 15, 75],\n ['claude-3-haiku', 0.25, 1.25],\n ['opus', 15, 75],\n ['sonnet', 3, 15],\n ['haiku', 0.8, 4],\n];\n\nexport function estimateCostUsd(\n model: string | undefined,\n inputTokens: number | undefined,\n outputTokens: number | undefined,\n): number | undefined {\n if (!model) return undefined;\n const lower = model.toLowerCase();\n const row = PRICES.find(([match]) => lower.includes(match));\n if (!row) return undefined;\n const [, inputRate, outputRate] = row;\n const input = ((inputTokens ?? 0) / 1_000_000) * inputRate;\n const output = ((outputTokens ?? 0) / 1_000_000) * outputRate;\n return input + output;\n}\n","import { str } from './attrs';\nimport type { Attributes } from './types';\nimport type { SessionIdentity } from './adapters/types';\n\nexport function mergeAttrs(...sources: Attributes[]): Attributes {\n return Object.assign({}, ...sources);\n}\n\n/** Pull the common identity attributes shared by every signal in a session. */\nexport function readIdentity(attrs: Attributes): SessionIdentity {\n return {\n user: str(attrs, 'user.id', 'user.account_uuid', 'user.email'),\n organization: str(attrs, 'organization.id'),\n terminal: str(attrs, 'terminal.type'),\n appVersion: str(attrs, 'app.version'),\n model: str(attrs, 'model'),\n };\n}\n","/**\n * Tool-name parsing. Claude Code (and opencode) expose MCP tools to the model\n * under the `mcp__<server>__<tool>` convention, and those names flow through the\n * `tool_result` / `tool_decision` events. Splitting the name is what lets the\n * Agents tab answer \"which MCP servers/tools is the agent actually using?\".\n */\n\n/** MCP-aware breakdown of a tool name (category is added by the taxonomy layer). */\nexport interface ParsedToolName {\n name: string;\n isMcp: boolean;\n mcpServer?: string;\n mcpTool?: string;\n}\n\nconst MCP_PREFIX = 'mcp__';\n\nexport function parseToolName(name: string): ParsedToolName {\n if (!name.startsWith(MCP_PREFIX)) {\n return { name, isMcp: false };\n }\n const rest = name.slice(MCP_PREFIX.length);\n const sep = rest.indexOf('__');\n if (sep === -1) {\n // `mcp__something` with no tool segment — treat the remainder as the server.\n return { name, isMcp: true, mcpServer: rest || undefined };\n }\n const server = rest.slice(0, sep);\n const tool = rest.slice(sep + 2);\n return {\n name,\n isMcp: true,\n mcpServer: server || undefined,\n mcpTool: tool || undefined,\n };\n}\n\nexport function isMcpTool(name: string): boolean {\n return name.startsWith(MCP_PREFIX);\n}\n","/**\n * Tool taxonomy. Claude Code reports every action the model takes as a tool\n * call (`tool_name` on `tool_result` / `tool_decision`), so the *kind* of work\n * an agent is doing is derivable from the name:\n *\n * - sub-agents are the `Task` tool\n * - skills are the `Skill` tool\n * - MCP tools are `mcp__<server>__<tool>`\n * - the rest are built-in file / shell / search / web / todo tools\n *\n * CC's native telemetry does NOT include tool *arguments*, so the specific\n * sub-agent type or skill name usually isn't present — we read them defensively\n * in case a future agent version (or another agent) adds them, and otherwise\n * fall back to the category count.\n */\n\nimport { str } from './attrs';\nimport { isMcpTool } from './mcp';\nimport type { Attributes } from './types';\n\nexport type ToolCategory =\n | 'file'\n | 'shell'\n | 'search'\n | 'web'\n | 'todo'\n | 'subagent'\n | 'skill'\n | 'mcp'\n | 'other';\n\nexport const TOOL_CATEGORIES: readonly ToolCategory[] = [\n 'file',\n 'shell',\n 'search',\n 'web',\n 'todo',\n 'subagent',\n 'skill',\n 'mcp',\n 'other',\n];\n\nconst BUILTIN: Record<string, ToolCategory> = {\n read: 'file',\n edit: 'file',\n write: 'file',\n multiedit: 'file',\n notebookedit: 'file',\n bash: 'shell',\n bashoutput: 'shell',\n killshell: 'shell',\n killbash: 'shell',\n grep: 'search',\n glob: 'search',\n ls: 'search',\n webfetch: 'web',\n websearch: 'web',\n todowrite: 'todo',\n task: 'subagent',\n agent: 'subagent',\n skill: 'skill',\n};\n\nexport function classifyTool(name: string): ToolCategory {\n if (isMcpTool(name)) return 'mcp';\n return BUILTIN[name.toLowerCase()] ?? 'other';\n}\n\n/** Sub-agent type, when the agent happens to emit it (defensive — often absent). */\nexport function readSubAgentType(attributes: Attributes): string | undefined {\n return str(attributes, 'subagent_type', 'agent_type', 'subagent.type');\n}\n\n/** Skill name, when present (defensive — often absent). */\nexport function readSkillName(attributes: Attributes): string | undefined {\n return str(attributes, 'skill', 'skill_name', 'skill.name');\n}\n","/**\n * Factory for \"Claude-Code-shaped\" agents. Claude Code, opencode and (soon)\n * Codex emit the *same* instrument and event names under different prefixes and\n * instrumentation scopes — opencode literally mirrors Claude Code's contract\n * with an `opencode.` prefix. So one parameterized adapter covers them all, and\n * a new agent is `createPrefixAdapter({ kind, prefix, scopeHint })`.\n */\n\nimport { bool, num, str } from '../attrs';\nimport { estimateCostUsd } from '../cost';\nimport { mergeAttrs, readIdentity } from '../identity';\nimport { parseToolName } from '../mcp';\nimport { classifyTool, readSkillName, readSubAgentType } from '../tool-taxonomy';\nimport type {\n AgentEvent,\n AgentEventType,\n AgentRawEvent,\n Attributes,\n ToolRef,\n OtelMetricRecord,\n} from '../types';\nimport type {\n AgentAdapter,\n AgentMetricKind,\n AgentMetricSignal,\n} from './types';\n\nexport interface PrefixAdapterConfig {\n kind: AgentAdapter['kind'];\n /** e.g. `\"claude_code.\"` or `\"opencode.\"`. */\n prefix: string;\n /** Substring expected in the instrumentation scope name, e.g. `\"claude_code\"`. */\n scopeHint: string;\n /** Expected `service.name` resource value, e.g. `\"claude-code\"`. */\n serviceHint: string;\n}\n\nconst EVENT_SUFFIXES: Record<string, AgentEventType> = {\n user_prompt: 'user_prompt',\n api_request: 'api_request',\n api_error: 'api_error',\n tool_result: 'tool_result',\n tool_decision: 'tool_decision',\n};\n\n/** Compose a full ToolRef: MCP split + category + (defensive) sub-agent/skill id. */\nfunction buildToolRef(name: string, attrs: Attributes): ToolRef {\n const category = classifyTool(name);\n const ref: ToolRef = { ...parseToolName(name), category };\n if (category === 'subagent') {\n const subAgentType = readSubAgentType(attrs);\n if (subAgentType) ref.subAgentType = subAgentType;\n } else if (category === 'skill') {\n const skillName = readSkillName(attrs);\n if (skillName) ref.skillName = skillName;\n }\n return ref;\n}\n\n// Only the metric-only signals get a kind; overlapping metrics (token/cost/\n// code_edit decision/session) fall through to 'other' and are not folded.\nconst METRIC_SUFFIXES: Record<string, AgentMetricKind> = {\n 'lines_of_code.count': 'lines_of_code',\n 'commit.count': 'commit',\n 'pull_request.count': 'pull_request',\n 'active_time.total': 'active_time',\n};\n\nexport function createPrefixAdapter(config: PrefixAdapterConfig): AgentAdapter {\n const { kind, prefix, scopeHint, serviceHint } = config;\n\n // Detection must rest on a POSITIVE agent signal — the metric/event name\n // prefix, the instrumentation scope, or the resource service.name. We never\n // match on a bare `event.name` like \"api_request\", because any application's\n // logs could carry that and would otherwise be misattributed as agent sessions.\n const scopeMatches = (scopeName?: string): boolean =>\n typeof scopeName === 'string' && scopeName.includes(scopeHint);\n const serviceMatches = (resource: Attributes): boolean => {\n const service = str(resource, 'service.name');\n return service !== undefined && service.includes(serviceHint);\n };\n\n /** Suffix after the agent prefix, e.g. `\"claude_code.api_request\"` → `\"api_request\"`. */\n const suffixOf = (name: string): string =>\n name.startsWith(prefix) ? name.slice(prefix.length) : name;\n\n return {\n kind,\n\n matchesMetric(record: OtelMetricRecord): boolean {\n return (\n record.name.startsWith(prefix) ||\n scopeMatches(record.scope?.name) ||\n serviceMatches(record.resource)\n );\n },\n\n matchesEvent(record: AgentRawEvent): boolean {\n return (\n record.eventName.startsWith(prefix) ||\n scopeMatches(record.scope?.name) ||\n serviceMatches(record.resource)\n );\n },\n\n normalizeEvent(record: AgentRawEvent): AgentEvent | null {\n const attrs = mergeAttrs(record.resource, record.attributes);\n const rawName =\n suffixOf(record.eventName) || str(attrs, 'event.name') || record.eventName;\n const type = EVENT_SUFFIXES[rawName] ?? 'other';\n const sessionId = str(attrs, 'session.id');\n if (!sessionId) return null;\n\n const event: AgentEvent = {\n id: `${sessionId}:0`, // real id assigned by the reducer (uses session.eventCount)\n sessionId,\n agent: kind,\n type,\n rawEventName: rawName,\n timestamp: record.timestamp,\n model: str(attrs, 'model'),\n attributes: record.attributes,\n };\n\n switch (type) {\n case 'api_request': {\n event.inputTokens = num(attrs, 'input_tokens');\n event.outputTokens = num(attrs, 'output_tokens');\n event.cacheReadTokens = num(attrs, 'cache_read_tokens');\n event.cacheCreationTokens = num(attrs, 'cache_creation_tokens');\n event.durationMs = num(attrs, 'duration_ms');\n const reported = num(attrs, 'cost_usd', 'cost');\n if (reported === undefined) {\n const estimated = estimateCostUsd(event.model, event.inputTokens, event.outputTokens);\n if (estimated !== undefined) {\n event.costUsd = estimated;\n event.costSource = 'estimated';\n }\n } else {\n event.costUsd = reported;\n event.costSource = 'reported';\n }\n break;\n }\n case 'api_error': {\n event.errorMessage = str(attrs, 'error', 'error.message', 'message');\n event.statusCode = num(attrs, 'status_code', 'http.status_code');\n event.durationMs = num(attrs, 'duration_ms');\n break;\n }\n case 'tool_result': {\n const toolName = str(attrs, 'tool_name', 'name', 'tool');\n if (toolName) event.tool = buildToolRef(toolName, attrs);\n event.success = bool(attrs, 'success');\n event.durationMs = num(attrs, 'duration_ms');\n const decision = str(attrs, 'decision');\n if (decision === 'accept' || decision === 'reject') event.decision = decision;\n break;\n }\n case 'tool_decision': {\n const toolName = str(attrs, 'tool_name', 'name', 'tool');\n if (toolName) event.tool = buildToolRef(toolName, attrs);\n const decision = str(attrs, 'decision');\n if (decision === 'accept' || decision === 'reject') event.decision = decision;\n break;\n }\n case 'user_prompt': {\n event.promptLength = num(attrs, 'prompt_length', 'prompt.length');\n const text = str(attrs, 'prompt');\n if (text) event.promptText = text;\n break;\n }\n default: {\n break;\n }\n }\n\n return event;\n },\n\n normalizeMetric(record: OtelMetricRecord): AgentMetricSignal[] {\n const kindOfMetric = METRIC_SUFFIXES[suffixOf(record.name)] ?? 'other';\n return record.dataPoints.map((point) => {\n const attrs = mergeAttrs(record.resource, point.attributes);\n return {\n agent: kind,\n sessionId: str(attrs, 'session.id'),\n identity: readIdentity(attrs),\n kind: kindOfMetric,\n value: point.value,\n temporality: record.temporality,\n timestamp: point.timestamp,\n attributes: point.attributes,\n } satisfies AgentMetricSignal;\n });\n },\n };\n}\n","import { createPrefixAdapter } from './prefix-adapter';\n\n/**\n * Claude Code: metrics/events prefixed `claude_code.*`, emitted under the\n * `com.anthropic.claude_code` instrumentation scope.\n * Contract: https://code.claude.com/docs/en/monitoring-usage\n */\nexport const claudeCodeAdapter = createPrefixAdapter({\n kind: 'claude-code',\n prefix: 'claude_code.',\n scopeHint: 'claude_code',\n serviceHint: 'claude-code',\n});\n","import { createPrefixAdapter } from './prefix-adapter';\n\n/**\n * opencode: the opencode-plugin-otel project mirrors Claude Code's exact\n * instrument and event names under an `opencode.*` prefix and the `com.opencode`\n * scope. Same shape → same factory.\n */\nexport const opencodeAdapter = createPrefixAdapter({\n kind: 'opencode',\n prefix: 'opencode.',\n scopeHint: 'opencode',\n serviceHint: 'opencode',\n});\n","import type { AgentRawEvent, OtelMetricRecord } from '../types';\nimport { claudeCodeAdapter } from './claude-code';\nimport { opencodeAdapter } from './opencode';\nimport type { AgentAdapter } from './types';\n\n/**\n * Ordered adapter registry. First match claims the record. Claude Code is most\n * specific (dedicated scope), so it leads. Add Codex here when its contract\n * lands — one line, no other changes.\n */\nexport const adapters: readonly AgentAdapter[] = [claudeCodeAdapter, opencodeAdapter];\n\nexport function detectAdapterForMetric(record: OtelMetricRecord): AgentAdapter | undefined {\n return adapters.find((adapter) => adapter.matchesMetric(record));\n}\n\nexport function detectAdapterForEvent(record: AgentRawEvent): AgentAdapter | undefined {\n return adapters.find((adapter) => adapter.matchesEvent(record));\n}\n\n/** True if any adapter recognizes this metric — used for zero-config \"agent detected\" toasts. */\nexport function isAgentMetric(record: OtelMetricRecord): boolean {\n return detectAdapterForMetric(record) !== undefined;\n}\n\n/** True if any adapter recognizes this log event. */\nexport function isAgentEvent(record: AgentRawEvent): boolean {\n return detectAdapterForEvent(record) !== undefined;\n}\n","/**\n * Session reducers. These are PURE (no I/O, no node:*) but stateful over a\n * caller-owned `Map`, so the devtools server can keep one canonical store and\n * broadcast finished `AgentSession` objects to the widget.\n *\n * Invariants:\n * - Rollups are kept forever; the raw `timeline` is ring-buffered (`timelineLimit`).\n * - Cost/token totals come from `api_request` EVENTS only. `token.usage` and\n * `cost.usage` METRICS are recognized but deliberately not summed, so the two\n * representations of the same fact never double-count.\n */\n\nimport { mergeAttrs, readIdentity } from './identity';\nimport { TOOL_CATEGORIES } from './tool-taxonomy';\nimport { detectAdapterForEvent, detectAdapterForMetric } from './adapters/registry';\nimport type { AgentMetricSignal, SessionIdentity } from './adapters/types';\nimport type {\n AgentEvent,\n AgentKind,\n AgentRawEvent,\n AgentSession,\n AgentSessionRollup,\n AgentSessionStore,\n OtelMetricRecord,\n ToolCategory,\n ToolUsage,\n} from './types';\n\nexport const DEFAULT_TIMELINE_LIMIT = 500;\n\nexport interface IngestOptions {\n timelineLimit?: number;\n}\n\nfunction emptyToolCategories(): Record<ToolCategory, number> {\n const out = {} as Record<ToolCategory, number>;\n for (const category of TOOL_CATEGORIES) out[category] = 0;\n return out;\n}\n\nfunction emptyRollup(): AgentSessionRollup {\n return {\n costUsd: 0,\n costReportedUsd: 0,\n costEstimatedUsd: 0,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheCreationTokens: 0,\n apiRequests: 0,\n apiErrors: 0,\n prompts: 0,\n toolCalls: 0,\n accepted: 0,\n rejected: 0,\n linesAdded: 0,\n linesRemoved: 0,\n commits: 0,\n pullRequests: 0,\n activeTimeSeconds: 0,\n models: {},\n tools: {},\n toolCategories: emptyToolCategories(),\n subAgents: {},\n skills: {},\n };\n}\n\nfunction createSession(id: string, agent: AgentKind, timestamp: number): AgentSession {\n return {\n id,\n agent,\n firstSeen: timestamp,\n lastSeen: timestamp,\n eventCount: 0,\n metricState: {},\n rollup: emptyRollup(),\n timeline: [],\n };\n}\n\nfunction getOrCreate(\n store: AgentSessionStore,\n id: string,\n agent: AgentKind,\n timestamp: number,\n): AgentSession {\n let session = store.get(id);\n if (!session) {\n session = createSession(id, agent, timestamp);\n store.set(id, session);\n }\n return session;\n}\n\nfunction applyIdentity(session: AgentSession, identity: SessionIdentity): void {\n if (!session.user && identity.user) session.user = identity.user;\n if (!session.organization && identity.organization) session.organization = identity.organization;\n if (!session.terminal && identity.terminal) session.terminal = identity.terminal;\n if (!session.appVersion && identity.appVersion) session.appVersion = identity.appVersion;\n}\n\nfunction touch(session: AgentSession, timestamp: number): void {\n if (timestamp < session.firstSeen) session.firstSeen = timestamp;\n if (timestamp > session.lastSeen) session.lastSeen = timestamp;\n}\n\nfunction bumpTool(rollup: AgentSessionRollup, event: AgentEvent): ToolUsage {\n const ref = event.tool!;\n const existing = rollup.tools[ref.name] ?? {\n name: ref.name,\n category: ref.category,\n isMcp: ref.isMcp,\n mcpServer: ref.mcpServer,\n count: 0,\n accepted: 0,\n rejected: 0,\n failures: 0,\n totalDurationMs: 0,\n };\n rollup.tools[ref.name] = existing;\n return existing;\n}\n\n/** Count a tool against its category and (for sub-agents/skills) its named bucket. */\nfunction tallyToolKind(rollup: AgentSessionRollup, event: AgentEvent): void {\n const ref = event.tool;\n if (!ref) return;\n rollup.toolCategories[ref.category] += 1;\n if (ref.category === 'subagent') {\n const key = ref.subAgentType ?? 'subagent';\n rollup.subAgents[key] = (rollup.subAgents[key] ?? 0) + 1;\n } else if (ref.category === 'skill') {\n const key = ref.skillName ?? 'skill';\n rollup.skills[key] = (rollup.skills[key] ?? 0) + 1;\n }\n}\n\n/** Fold a normalized event into a session rollup + timeline. Returns the session. */\nexport function foldEvent(\n session: AgentSession,\n event: AgentEvent,\n timelineLimit: number = DEFAULT_TIMELINE_LIMIT,\n): AgentSession {\n session.eventCount += 1;\n event.id = `${session.id}:${session.eventCount}`;\n touch(session, event.timestamp);\n\n const { rollup } = session;\n switch (event.type) {\n case 'api_request': {\n rollup.apiRequests += 1;\n rollup.inputTokens += event.inputTokens ?? 0;\n rollup.outputTokens += event.outputTokens ?? 0;\n rollup.cacheReadTokens += event.cacheReadTokens ?? 0;\n rollup.cacheCreationTokens += event.cacheCreationTokens ?? 0;\n if (event.costUsd !== undefined) {\n rollup.costUsd += event.costUsd;\n if (event.costSource === 'estimated') rollup.costEstimatedUsd += event.costUsd;\n else rollup.costReportedUsd += event.costUsd;\n }\n if (event.model) rollup.models[event.model] = (rollup.models[event.model] ?? 0) + 1;\n break;\n }\n case 'api_error':\n rollup.apiErrors += 1;\n break;\n case 'user_prompt':\n rollup.prompts += 1;\n break;\n case 'tool_result': {\n // tool_result is the actual execution: it owns the call count, duration,\n // failures and tool taxonomy. Accept/reject is NOT counted here — that's\n // owned solely by tool_decision (see below), so a single decision can't be\n // double-counted across the event + the code_edit_tool.decision metric.\n rollup.toolCalls += 1;\n if (event.tool) {\n const usage = bumpTool(rollup, event);\n usage.count += 1;\n usage.totalDurationMs += event.durationMs ?? 0;\n if (event.success === false) usage.failures += 1;\n tallyToolKind(rollup, event);\n }\n break;\n }\n case 'tool_decision': {\n // The single source of truth for accept/reject (matches the\n // events-authoritative rule used for cost/tokens).\n if (event.decision === 'accept') rollup.accepted += 1;\n if (event.decision === 'reject') rollup.rejected += 1;\n if (event.tool) {\n const usage = bumpTool(rollup, event);\n if (event.decision === 'accept') usage.accepted += 1;\n if (event.decision === 'reject') usage.rejected += 1;\n }\n break;\n }\n default:\n break;\n }\n\n session.timeline.push(event);\n if (session.timeline.length > timelineLimit) {\n session.timeline.splice(0, session.timeline.length - timelineLimit);\n }\n return session;\n}\n\n/**\n * The amount to add to a running total for one metric data point.\n *\n * `delta` points already carry the per-interval change, so they're added as-is.\n * `cumulative` points carry an absolute running total per series, so we add the\n * difference from the last value we saw for that series — otherwise re-exporting\n * `lines_of_code.count = 42` every interval would inflate the total without end.\n * A drop (counter reset / new process) is treated as a fresh delta.\n */\nfunction counterDelta(session: AgentSession, seriesKey: string, signal: AgentMetricSignal): number {\n if (signal.temporality !== 'cumulative') return signal.value;\n const last = session.metricState[seriesKey] ?? 0;\n session.metricState[seriesKey] = signal.value;\n return signal.value >= last ? signal.value - last : signal.value;\n}\n\n/** Stable per-series key: a cumulative counter is one series per attribute set. */\nfunction seriesKey(signal: AgentMetricSignal): string {\n const attrs = Object.entries(signal.attributes)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}=${String(v)}`)\n .join(',');\n return `${signal.kind}|${attrs}`;\n}\n\n/** Fold a metric-only signal (lines, commits, PRs, active time) into the rollup. */\nexport function foldMetricSignal(session: AgentSession, signal: AgentMetricSignal): void {\n touch(session, signal.timestamp);\n applyIdentity(session, signal.identity);\n const { rollup } = session;\n const value = counterDelta(session, seriesKey(signal), signal);\n switch (signal.kind) {\n case 'lines_of_code': {\n const type = String(signal.attributes['type'] ?? '').toLowerCase();\n if (type.includes('remov') || type.includes('delet')) rollup.linesRemoved += value;\n else rollup.linesAdded += value;\n break;\n }\n case 'commit':\n rollup.commits += value;\n break;\n case 'pull_request':\n rollup.pullRequests += value;\n break;\n case 'active_time':\n rollup.activeTimeSeconds += value;\n break;\n // Everything else ('other') is a metric that overlaps an event — token.usage,\n // cost.usage, code_edit_tool.decision, session.count — and is deliberately\n // NOT folded here: events are authoritative (see source-of-truth invariant).\n default:\n break;\n }\n}\n\n/**\n * Ingest a decoded OTLP log record. No-op (returns null) if no adapter claims it\n * or the record lacks a session id.\n */\nexport function ingestEventRecord(\n store: AgentSessionStore,\n record: AgentRawEvent,\n options: IngestOptions = {},\n): AgentSession | null {\n const adapter = detectAdapterForEvent(record);\n if (!adapter) return null;\n const event = adapter.normalizeEvent(record);\n if (!event) return null;\n\n const session = getOrCreate(store, event.sessionId, adapter.kind, event.timestamp);\n applyIdentity(session, readIdentity(mergeAttrs(record.resource, record.attributes)));\n return foldEvent(session, event, options.timelineLimit ?? DEFAULT_TIMELINE_LIMIT);\n}\n\n/** Ingest a decoded OTLP metric. Returns sessions touched (may be several). */\nexport function ingestMetricRecord(\n store: AgentSessionStore,\n record: OtelMetricRecord,\n): AgentSession[] {\n const adapter = detectAdapterForMetric(record);\n if (!adapter) return [];\n const touched = new Map<string, AgentSession>();\n for (const signal of adapter.normalizeMetric(record)) {\n if (!signal.sessionId) continue;\n const session = getOrCreate(store, signal.sessionId, adapter.kind, signal.timestamp);\n foldMetricSignal(session, signal);\n touched.set(session.id, session);\n }\n return [...touched.values()];\n}\n\n/** Batch-ingest decoded OTLP log records. */\nexport function ingestAgentEvents(\n store: AgentSessionStore,\n records: AgentRawEvent[],\n options: IngestOptions = {},\n): void {\n for (const record of records) ingestEventRecord(store, record, options);\n}\n\n/** Batch-ingest decoded OTLP metric records. */\nexport function ingestAgentMetrics(\n store: AgentSessionStore,\n records: OtelMetricRecord[],\n): void {\n for (const record of records) ingestMetricRecord(store, record);\n}\n\n// ── Aggregate strip (v1: across visible sessions) ──────────────────────────\n\nexport interface AgentAggregate {\n sessions: number;\n costUsd: number;\n inputTokens: number;\n outputTokens: number;\n apiRequests: number;\n apiErrors: number;\n accepted: number;\n rejected: number;\n models: Record<string, number>;\n /** tool name → call count, MCP and built-in together. */\n tools: Record<string, number>;\n /** tool category → call count. */\n toolCategories: Record<ToolCategory, number>;\n /** MCP server id → call count. */\n mcpServers: Record<string, number>;\n /** sub-agent type (or `\"subagent\"`) → invocation count. */\n subAgents: Record<string, number>;\n /** skill name (or `\"skill\"`) → invocation count. */\n skills: Record<string, number>;\n}\n\nexport function summarizeSessions(sessions: Iterable<AgentSession>): AgentAggregate {\n const agg: AgentAggregate = {\n sessions: 0,\n costUsd: 0,\n inputTokens: 0,\n outputTokens: 0,\n apiRequests: 0,\n apiErrors: 0,\n accepted: 0,\n rejected: 0,\n models: {},\n tools: {},\n toolCategories: emptyToolCategories(),\n mcpServers: {},\n subAgents: {},\n skills: {},\n };\n for (const session of sessions) {\n agg.sessions += 1;\n const { rollup } = session;\n agg.costUsd += rollup.costUsd;\n agg.inputTokens += rollup.inputTokens;\n agg.outputTokens += rollup.outputTokens;\n agg.apiRequests += rollup.apiRequests;\n agg.apiErrors += rollup.apiErrors;\n agg.accepted += rollup.accepted;\n agg.rejected += rollup.rejected;\n for (const [model, count] of Object.entries(rollup.models)) {\n agg.models[model] = (agg.models[model] ?? 0) + count;\n }\n for (const usage of Object.values(rollup.tools)) {\n agg.tools[usage.name] = (agg.tools[usage.name] ?? 0) + usage.count;\n if (usage.isMcp && usage.mcpServer) {\n agg.mcpServers[usage.mcpServer] = (agg.mcpServers[usage.mcpServer] ?? 0) + usage.count;\n }\n }\n for (const category of TOOL_CATEGORIES) {\n agg.toolCategories[category] += rollup.toolCategories[category];\n }\n for (const [type, count] of Object.entries(rollup.subAgents)) {\n agg.subAgents[type] = (agg.subAgents[type] ?? 0) + count;\n }\n for (const [name, count] of Object.entries(rollup.skills)) {\n agg.skills[name] = (agg.skills[name] ?? 0) + count;\n }\n }\n return agg;\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
//#region src/tool-taxonomy.d.ts
|
|
2
|
+
type ToolCategory = 'file' | 'shell' | 'search' | 'web' | 'todo' | 'subagent' | 'skill' | 'mcp' | 'other';
|
|
3
|
+
declare const TOOL_CATEGORIES: readonly ToolCategory[];
|
|
4
|
+
declare function classifyTool(name: string): ToolCategory;
|
|
5
|
+
/** Sub-agent type, when the agent happens to emit it (defensive — often absent). */
|
|
6
|
+
declare function readSubAgentType(attributes: Attributes): string | undefined;
|
|
7
|
+
/** Skill name, when present (defensive — often absent). */
|
|
8
|
+
declare function readSkillName(attributes: Attributes): string | undefined;
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/types.d.ts
|
|
11
|
+
type AttrValue = string | number | boolean | null | AttrValue[] | {
|
|
12
|
+
[key: string]: AttrValue;
|
|
13
|
+
};
|
|
14
|
+
type Attributes = Record<string, AttrValue>;
|
|
15
|
+
interface OtelScope {
|
|
16
|
+
name?: string;
|
|
17
|
+
version?: string;
|
|
18
|
+
}
|
|
19
|
+
/** One numeric data point of an OTLP metric (sum / gauge / histogram count). */
|
|
20
|
+
interface OtelDataPoint {
|
|
21
|
+
value: number;
|
|
22
|
+
attributes: Attributes;
|
|
23
|
+
/** Epoch milliseconds (server converts from OTLP's `timeUnixNano`). */
|
|
24
|
+
timestamp: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Aggregation temporality of a counter. `delta` points carry the change since
|
|
28
|
+
* the last export (safe to sum); `cumulative` points carry a running total
|
|
29
|
+
* (must be differenced per series, or you over-count on every export). Claude
|
|
30
|
+
* Code defaults to `delta`; most other SDKs default to `cumulative`.
|
|
31
|
+
*/
|
|
32
|
+
type MetricTemporality = 'delta' | 'cumulative';
|
|
33
|
+
/** A decoded OTLP metric — the server fills `dataPoints` from any instrument type. */
|
|
34
|
+
interface OtelMetricRecord {
|
|
35
|
+
name: string;
|
|
36
|
+
unit?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
/** Counter temporality. Absent ⇒ treated as `delta` (Claude Code's default). */
|
|
39
|
+
temporality?: MetricTemporality;
|
|
40
|
+
dataPoints: OtelDataPoint[];
|
|
41
|
+
resource: Attributes;
|
|
42
|
+
scope?: OtelScope;
|
|
43
|
+
}
|
|
44
|
+
/** A decoded OTLP log record (Claude Code / opencode emit their events as logs). */
|
|
45
|
+
interface AgentRawEvent {
|
|
46
|
+
/** Best-effort event name: OTLP `EventName`, else the `event.name` attribute. */
|
|
47
|
+
eventName: string;
|
|
48
|
+
/** Epoch milliseconds. */
|
|
49
|
+
timestamp: number;
|
|
50
|
+
body?: unknown;
|
|
51
|
+
attributes: Attributes;
|
|
52
|
+
resource: Attributes;
|
|
53
|
+
scope?: OtelScope;
|
|
54
|
+
}
|
|
55
|
+
type AgentKind = 'claude-code' | 'opencode' | 'codex' | 'unknown';
|
|
56
|
+
type AgentEventType = 'user_prompt' | 'api_request' | 'api_error' | 'tool_result' | 'tool_decision' | 'other';
|
|
57
|
+
type ToolDecision = 'accept' | 'reject';
|
|
58
|
+
/**
|
|
59
|
+
* A tool the agent invoked. MCP tools follow Claude Code's `mcp__<server>__<tool>`
|
|
60
|
+
* naming, so we can split server/tool out of the name — that's what powers the
|
|
61
|
+
* "which MCP servers is the agent using" breakdown.
|
|
62
|
+
*/
|
|
63
|
+
interface ToolRef {
|
|
64
|
+
/** Raw tool name, e.g. `"Edit"`, `"Task"`, `"Skill"` or `"mcp__github__create_issue"`. */
|
|
65
|
+
name: string;
|
|
66
|
+
/** What kind of work this tool represents (file/shell/subagent/skill/mcp/…). */
|
|
67
|
+
category: ToolCategory;
|
|
68
|
+
isMcp: boolean;
|
|
69
|
+
/** MCP server id, e.g. `"github"` (only when `isMcp`). */
|
|
70
|
+
mcpServer?: string;
|
|
71
|
+
/** MCP tool name, e.g. `"create_issue"` (only when `isMcp`). */
|
|
72
|
+
mcpTool?: string;
|
|
73
|
+
/** Sub-agent type for `Task` calls, when the agent emits it. */
|
|
74
|
+
subAgentType?: string;
|
|
75
|
+
/** Skill name for `Skill` calls, when the agent emits it. */
|
|
76
|
+
skillName?: string;
|
|
77
|
+
}
|
|
78
|
+
type CostSource = 'reported' | 'estimated';
|
|
79
|
+
/** A single normalized agent interaction (a row on the session timeline). */
|
|
80
|
+
interface AgentEvent {
|
|
81
|
+
id: string;
|
|
82
|
+
sessionId: string;
|
|
83
|
+
agent: AgentKind;
|
|
84
|
+
type: AgentEventType;
|
|
85
|
+
/** The agent's own event name, e.g. `"api_request"`. */
|
|
86
|
+
rawEventName: string;
|
|
87
|
+
timestamp: number;
|
|
88
|
+
model?: string;
|
|
89
|
+
costUsd?: number;
|
|
90
|
+
costSource?: CostSource;
|
|
91
|
+
inputTokens?: number;
|
|
92
|
+
outputTokens?: number;
|
|
93
|
+
cacheReadTokens?: number;
|
|
94
|
+
cacheCreationTokens?: number;
|
|
95
|
+
durationMs?: number;
|
|
96
|
+
tool?: ToolRef;
|
|
97
|
+
decision?: ToolDecision;
|
|
98
|
+
success?: boolean;
|
|
99
|
+
promptLength?: number;
|
|
100
|
+
/** Only present when prompt capture is explicitly enabled. */
|
|
101
|
+
promptText?: string;
|
|
102
|
+
errorMessage?: string;
|
|
103
|
+
statusCode?: number;
|
|
104
|
+
attributes: Attributes;
|
|
105
|
+
}
|
|
106
|
+
/** Per-tool usage tally within a session. */
|
|
107
|
+
interface ToolUsage {
|
|
108
|
+
name: string;
|
|
109
|
+
category: ToolCategory;
|
|
110
|
+
isMcp: boolean;
|
|
111
|
+
mcpServer?: string;
|
|
112
|
+
count: number;
|
|
113
|
+
accepted: number;
|
|
114
|
+
rejected: number;
|
|
115
|
+
failures: number;
|
|
116
|
+
totalDurationMs: number;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Running totals for a session. Kept indefinitely even as the raw `timeline`
|
|
120
|
+
* is ring-buffered, so headline numbers never drift. Per the source-of-truth
|
|
121
|
+
* rule, cost/token totals come from `api_request` *events* only — the
|
|
122
|
+
* `token.usage`/`cost.usage` *metrics* are intentionally NOT summed in here.
|
|
123
|
+
*/
|
|
124
|
+
interface AgentSessionRollup {
|
|
125
|
+
costUsd: number;
|
|
126
|
+
costReportedUsd: number;
|
|
127
|
+
costEstimatedUsd: number;
|
|
128
|
+
inputTokens: number;
|
|
129
|
+
outputTokens: number;
|
|
130
|
+
cacheReadTokens: number;
|
|
131
|
+
cacheCreationTokens: number;
|
|
132
|
+
apiRequests: number;
|
|
133
|
+
apiErrors: number;
|
|
134
|
+
prompts: number;
|
|
135
|
+
toolCalls: number;
|
|
136
|
+
accepted: number;
|
|
137
|
+
rejected: number;
|
|
138
|
+
linesAdded: number;
|
|
139
|
+
linesRemoved: number;
|
|
140
|
+
commits: number;
|
|
141
|
+
pullRequests: number;
|
|
142
|
+
activeTimeSeconds: number;
|
|
143
|
+
/** model id → api_request count. */
|
|
144
|
+
models: Record<string, number>;
|
|
145
|
+
/** tool name → usage. */
|
|
146
|
+
tools: Record<string, ToolUsage>;
|
|
147
|
+
/** tool category → call count (file/shell/subagent/skill/mcp/…). */
|
|
148
|
+
toolCategories: Record<ToolCategory, number>;
|
|
149
|
+
/** sub-agent type (or `"subagent"` when type unknown) → invocation count. */
|
|
150
|
+
subAgents: Record<string, number>;
|
|
151
|
+
/** skill name (or `"skill"` when name unknown) → invocation count. */
|
|
152
|
+
skills: Record<string, number>;
|
|
153
|
+
}
|
|
154
|
+
interface AgentSession {
|
|
155
|
+
id: string;
|
|
156
|
+
agent: AgentKind;
|
|
157
|
+
user?: string;
|
|
158
|
+
organization?: string;
|
|
159
|
+
terminal?: string;
|
|
160
|
+
appVersion?: string;
|
|
161
|
+
firstSeen: number;
|
|
162
|
+
lastSeen: number;
|
|
163
|
+
/** Total events ever seen (drives stable event ids; survives timeline eviction). */
|
|
164
|
+
eventCount: number;
|
|
165
|
+
/**
|
|
166
|
+
* Internal reducer state (not for UI): last-seen value per cumulative metric
|
|
167
|
+
* series, so re-exported cumulative counters are differenced instead of summed.
|
|
168
|
+
* Keyed by metric kind + datapoint attributes.
|
|
169
|
+
*/
|
|
170
|
+
metricState: Record<string, number>;
|
|
171
|
+
rollup: AgentSessionRollup;
|
|
172
|
+
/** Ring-buffered raw interactions (newest last), bounded by the reducer caller. */
|
|
173
|
+
timeline: AgentEvent[];
|
|
174
|
+
}
|
|
175
|
+
type AgentSessionStore = Map<string, AgentSession>;
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/adapters/types.d.ts
|
|
178
|
+
/** A metric-only signal (lines of code, commits, active time, …) keyed to a session. */
|
|
179
|
+
interface AgentMetricSignal {
|
|
180
|
+
agent: AgentKind;
|
|
181
|
+
/** session.id from the data point / resource, when present. */
|
|
182
|
+
sessionId?: string;
|
|
183
|
+
identity: SessionIdentity;
|
|
184
|
+
kind: AgentMetricKind;
|
|
185
|
+
value: number;
|
|
186
|
+
/** Counter temporality — `cumulative` values are differenced per series. */
|
|
187
|
+
temporality?: MetricTemporality;
|
|
188
|
+
timestamp: number;
|
|
189
|
+
/** Original data-point attributes (e.g. `type: "input"` on token usage). */
|
|
190
|
+
attributes: Record<string, unknown>;
|
|
191
|
+
}
|
|
192
|
+
type AgentMetricKind = 'lines_of_code' | 'commit' | 'pull_request' | 'active_time' | 'other';
|
|
193
|
+
/** Common identity attributes shared by every signal in a session. */
|
|
194
|
+
interface SessionIdentity {
|
|
195
|
+
user?: string;
|
|
196
|
+
organization?: string;
|
|
197
|
+
terminal?: string;
|
|
198
|
+
appVersion?: string;
|
|
199
|
+
model?: string;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* An adapter recognizes one agent's telemetry (by instrumentation scope and/or
|
|
203
|
+
* metric/event-name prefix) and normalizes it to the shared model. Adding Codex
|
|
204
|
+
* or another agent = adding one adapter; no UI or reducer changes.
|
|
205
|
+
*/
|
|
206
|
+
interface AgentAdapter {
|
|
207
|
+
kind: AgentKind;
|
|
208
|
+
matchesMetric(record: OtelMetricRecord): boolean;
|
|
209
|
+
matchesEvent(record: AgentRawEvent): boolean;
|
|
210
|
+
normalizeEvent(record: AgentRawEvent): AgentEvent | null;
|
|
211
|
+
normalizeMetric(record: OtelMetricRecord): AgentMetricSignal[];
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/adapters/registry.d.ts
|
|
215
|
+
/**
|
|
216
|
+
* Ordered adapter registry. First match claims the record. Claude Code is most
|
|
217
|
+
* specific (dedicated scope), so it leads. Add Codex here when its contract
|
|
218
|
+
* lands — one line, no other changes.
|
|
219
|
+
*/
|
|
220
|
+
declare const adapters: readonly AgentAdapter[];
|
|
221
|
+
declare function detectAdapterForMetric(record: OtelMetricRecord): AgentAdapter | undefined;
|
|
222
|
+
declare function detectAdapterForEvent(record: AgentRawEvent): AgentAdapter | undefined;
|
|
223
|
+
/** True if any adapter recognizes this metric — used for zero-config "agent detected" toasts. */
|
|
224
|
+
declare function isAgentMetric(record: OtelMetricRecord): boolean;
|
|
225
|
+
/** True if any adapter recognizes this log event. */
|
|
226
|
+
declare function isAgentEvent(record: AgentRawEvent): boolean;
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/adapters/prefix-adapter.d.ts
|
|
229
|
+
interface PrefixAdapterConfig {
|
|
230
|
+
kind: AgentAdapter['kind'];
|
|
231
|
+
/** e.g. `"claude_code."` or `"opencode."`. */
|
|
232
|
+
prefix: string;
|
|
233
|
+
/** Substring expected in the instrumentation scope name, e.g. `"claude_code"`. */
|
|
234
|
+
scopeHint: string;
|
|
235
|
+
/** Expected `service.name` resource value, e.g. `"claude-code"`. */
|
|
236
|
+
serviceHint: string;
|
|
237
|
+
}
|
|
238
|
+
declare function createPrefixAdapter(config: PrefixAdapterConfig): AgentAdapter;
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/adapters/claude-code.d.ts
|
|
241
|
+
/**
|
|
242
|
+
* Claude Code: metrics/events prefixed `claude_code.*`, emitted under the
|
|
243
|
+
* `com.anthropic.claude_code` instrumentation scope.
|
|
244
|
+
* Contract: https://code.claude.com/docs/en/monitoring-usage
|
|
245
|
+
*/
|
|
246
|
+
declare const claudeCodeAdapter: AgentAdapter;
|
|
247
|
+
//#endregion
|
|
248
|
+
//#region src/adapters/opencode.d.ts
|
|
249
|
+
/**
|
|
250
|
+
* opencode: the opencode-plugin-otel project mirrors Claude Code's exact
|
|
251
|
+
* instrument and event names under an `opencode.*` prefix and the `com.opencode`
|
|
252
|
+
* scope. Same shape → same factory.
|
|
253
|
+
*/
|
|
254
|
+
declare const opencodeAdapter: AgentAdapter;
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/reduce.d.ts
|
|
257
|
+
declare const DEFAULT_TIMELINE_LIMIT = 500;
|
|
258
|
+
interface IngestOptions {
|
|
259
|
+
timelineLimit?: number;
|
|
260
|
+
}
|
|
261
|
+
/** Fold a normalized event into a session rollup + timeline. Returns the session. */
|
|
262
|
+
declare function foldEvent(session: AgentSession, event: AgentEvent, timelineLimit?: number): AgentSession;
|
|
263
|
+
/** Fold a metric-only signal (lines, commits, PRs, active time) into the rollup. */
|
|
264
|
+
declare function foldMetricSignal(session: AgentSession, signal: AgentMetricSignal): void;
|
|
265
|
+
/**
|
|
266
|
+
* Ingest a decoded OTLP log record. No-op (returns null) if no adapter claims it
|
|
267
|
+
* or the record lacks a session id.
|
|
268
|
+
*/
|
|
269
|
+
declare function ingestEventRecord(store: AgentSessionStore, record: AgentRawEvent, options?: IngestOptions): AgentSession | null;
|
|
270
|
+
/** Ingest a decoded OTLP metric. Returns sessions touched (may be several). */
|
|
271
|
+
declare function ingestMetricRecord(store: AgentSessionStore, record: OtelMetricRecord): AgentSession[];
|
|
272
|
+
/** Batch-ingest decoded OTLP log records. */
|
|
273
|
+
declare function ingestAgentEvents(store: AgentSessionStore, records: AgentRawEvent[], options?: IngestOptions): void;
|
|
274
|
+
/** Batch-ingest decoded OTLP metric records. */
|
|
275
|
+
declare function ingestAgentMetrics(store: AgentSessionStore, records: OtelMetricRecord[]): void;
|
|
276
|
+
interface AgentAggregate {
|
|
277
|
+
sessions: number;
|
|
278
|
+
costUsd: number;
|
|
279
|
+
inputTokens: number;
|
|
280
|
+
outputTokens: number;
|
|
281
|
+
apiRequests: number;
|
|
282
|
+
apiErrors: number;
|
|
283
|
+
accepted: number;
|
|
284
|
+
rejected: number;
|
|
285
|
+
models: Record<string, number>;
|
|
286
|
+
/** tool name → call count, MCP and built-in together. */
|
|
287
|
+
tools: Record<string, number>;
|
|
288
|
+
/** tool category → call count. */
|
|
289
|
+
toolCategories: Record<ToolCategory, number>;
|
|
290
|
+
/** MCP server id → call count. */
|
|
291
|
+
mcpServers: Record<string, number>;
|
|
292
|
+
/** sub-agent type (or `"subagent"`) → invocation count. */
|
|
293
|
+
subAgents: Record<string, number>;
|
|
294
|
+
/** skill name (or `"skill"`) → invocation count. */
|
|
295
|
+
skills: Record<string, number>;
|
|
296
|
+
}
|
|
297
|
+
declare function summarizeSessions(sessions: Iterable<AgentSession>): AgentAggregate;
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/mcp.d.ts
|
|
300
|
+
/**
|
|
301
|
+
* Tool-name parsing. Claude Code (and opencode) expose MCP tools to the model
|
|
302
|
+
* under the `mcp__<server>__<tool>` convention, and those names flow through the
|
|
303
|
+
* `tool_result` / `tool_decision` events. Splitting the name is what lets the
|
|
304
|
+
* Agents tab answer "which MCP servers/tools is the agent actually using?".
|
|
305
|
+
*/
|
|
306
|
+
/** MCP-aware breakdown of a tool name (category is added by the taxonomy layer). */
|
|
307
|
+
interface ParsedToolName {
|
|
308
|
+
name: string;
|
|
309
|
+
isMcp: boolean;
|
|
310
|
+
mcpServer?: string;
|
|
311
|
+
mcpTool?: string;
|
|
312
|
+
}
|
|
313
|
+
declare function parseToolName(name: string): ParsedToolName;
|
|
314
|
+
declare function isMcpTool(name: string): boolean;
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/cost.d.ts
|
|
317
|
+
/**
|
|
318
|
+
* Fallback cost estimation. Reported cost (`cost_usd` on `api_request`) always
|
|
319
|
+
* wins — Claude Code computes it cache-accurately. This table is ONLY used when
|
|
320
|
+
* an agent reports tokens but not cost (e.g. a future agent, or a misconfigured
|
|
321
|
+
* run). Estimated values are badged `estimated` in the UI.
|
|
322
|
+
*
|
|
323
|
+
* Prices are USD per 1,000,000 tokens. Matched by substring so model ids like
|
|
324
|
+
* `claude-sonnet-4-6` or `claude-3-5-sonnet-20241022` resolve to a family rate.
|
|
325
|
+
* Keep deliberately small — this is a safety net, not a billing source.
|
|
326
|
+
*/
|
|
327
|
+
declare function estimateCostUsd(model: string | undefined, inputTokens: number | undefined, outputTokens: number | undefined): number | undefined;
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/identity.d.ts
|
|
330
|
+
declare function mergeAttrs(...sources: Attributes[]): Attributes;
|
|
331
|
+
/** Pull the common identity attributes shared by every signal in a session. */
|
|
332
|
+
declare function readIdentity(attrs: Attributes): SessionIdentity;
|
|
333
|
+
//#endregion
|
|
334
|
+
export { type AgentAdapter, type AgentAggregate, type AgentEvent, type AgentEventType, type AgentKind, type AgentMetricKind, type AgentMetricSignal, type AgentRawEvent, type AgentSession, type AgentSessionRollup, type AgentSessionStore, type AttrValue, type Attributes, type CostSource, DEFAULT_TIMELINE_LIMIT, type IngestOptions, type MetricTemporality, type OtelDataPoint, type OtelMetricRecord, type OtelScope, type ParsedToolName, type SessionIdentity, TOOL_CATEGORIES, type ToolCategory, type ToolDecision, type ToolRef, type ToolUsage, adapters, classifyTool, claudeCodeAdapter, createPrefixAdapter, detectAdapterForEvent, detectAdapterForMetric, estimateCostUsd, foldEvent, foldMetricSignal, ingestAgentEvents, ingestAgentMetrics, ingestEventRecord, ingestMetricRecord, isAgentEvent, isAgentMetric, isMcpTool, mergeAttrs, opencodeAdapter, parseToolName, readIdentity, readSkillName, readSubAgentType, summarizeSessions };
|
|
335
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/tool-taxonomy.ts","../src/types.ts","../src/adapters/types.ts","../src/adapters/registry.ts","../src/adapters/prefix-adapter.ts","../src/adapters/claude-code.ts","../src/adapters/opencode.ts","../src/reduce.ts","../src/mcp.ts","../src/cost.ts","../src/identity.ts"],"mappings":"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"}
|