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.js","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/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "autotel-agents",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser-safe domain layer for observing coding agents (Claude Code, opencode, Codex) from their OpenTelemetry metrics and log events — adapter registry + session reducers, no I/O.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"autotel",
|
|
21
|
+
"opentelemetry",
|
|
22
|
+
"observability",
|
|
23
|
+
"agent-observability",
|
|
24
|
+
"claude-code",
|
|
25
|
+
"opencode",
|
|
26
|
+
"coding-agents",
|
|
27
|
+
"otlp"
|
|
28
|
+
],
|
|
29
|
+
"author": "Jag Reehal <jag@jagreehal.com> (https://jagreehal.com)",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^25.9.2",
|
|
33
|
+
"rimraf": "^6.1.3",
|
|
34
|
+
"tsdown": "^0.22.2",
|
|
35
|
+
"typescript": "^6.0.3",
|
|
36
|
+
"vitest": "^4.1.8"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/jagreehal/autotel",
|
|
41
|
+
"directory": "packages/autotel-agents"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/jagreehal/autotel/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/jagreehal/autotel/tree/main/packages/autotel-agents#readme",
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsdown",
|
|
49
|
+
"dev": "tsdown --watch",
|
|
50
|
+
"lint": "eslint src/**/*.ts",
|
|
51
|
+
"lint:fix": "eslint src/**/*.ts --fix",
|
|
52
|
+
"type-check": "tsc --noEmit",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"clean": "rimraf dist"
|
|
56
|
+
}
|
|
57
|
+
}
|