@agimon-ai/doompi-log 0.0.1-alpha.71 → 0.0.1-alpha.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- const e=require("../constants/telemetry.cjs"),t=require("../services/telemetryEnabled/index.cjs");let n=require("@agimon-ai/doompi-core/readiness"),r=require("@agimon-ai/doompi-telemetry");function i(e){let t={"pi.mode":e.mode,"pi.session.id":e.sessionManager.getSessionId()};return e.thinkingLevel&&(t[`pi.thinking_level`]=e.thinkingLevel),e.model&&(t[`gen_ai.provider.name`]=e.model.provider,t[`gen_ai.request.model`]=e.model.id),t}function a(e){if(e.role!==`assistant`)return;let t={"gen_ai.response.model":e.responseModel??e.model,"gen_ai.response.finish_reasons":e.stopReason,"gen_ai.usage.input_tokens":e.usage.input,"gen_ai.usage.output_tokens":e.usage.output,"gen_ai.usage.cache_read_tokens":e.usage.cacheRead,"gen_ai.usage.cache_write_tokens":e.usage.cacheWrite,"gen_ai.usage.total_tokens":e.usage.totalTokens,"gen_ai.usage.cost":e.usage.cost.total};return e.usage.reasoning!==void 0&&(t[`gen_ai.usage.reasoning_tokens`]=e.usage.reasoning),t}function o(e){if(e.role!==`assistant`)return{"error.type":`AgentError`,outcome:`error`};let t=e.stopReason===`aborted`;return{"error.type":`ProviderError`,"error.code":e.stopReason,outcome:t?`aborted`:`error`}}function s(e){return{service:e.serviceName,backend:e.backend,endpoint:e.endpoint??`none`,endpointSource:e.endpointSource,traces:e.traces,redaction:!0,fileFallback:e.fileFallback}}function c(c,l={},u=!1){let d=l.env??process.env,f=l.metrics,p=new Map,m=!0,h=0,g,_,v,y,b=Promise.resolve(),x=e=>{b=b.then(e).catch(()=>void 0)},S=async(e,t,n)=>{m&&_&&(e===`debug`?await _.recordDebug(t,n):e===`warn`?await _.recordWarning(t,void 0,n):e===`error`?await _.recordError(t,void 0,n):await _.recordEvent(t,n))},C=n=>{if(!m)throw Error(`Log telemetry runtime is disposed.`);let i=n.sessionManager.getSessionId();if(_&&v!==i){let e=_;_=void 0,v=void 0,x(()=>e.shutdown())}return _||=(v=i,(0,r.createDoomTelemetry)({serviceName:l.serviceName??`pi`,packageName:e.PACKAGE_NAME,cwd:n.cwd,env:{...d,PI_SESSION_ID:i},allowFileFallback:l.allowFileFallback,telemetryFactory:l.telemetryFactory,warn:l.onDiagnostic,enableLogs:!0,enableTraces:d.AGENT_OTEL_TRACES===void 0||t.isEnabled(d.AGENT_OTEL_TRACES),onRecord:e=>{m&&f?.record(e.event,e.attributes)},onStatus:e=>{m&&l.onSinkStatus?.(s(e))}})),m&&l.onSinkStatus?.(s(_.status())),_},w=async e=>{let t=y;if(t){if(t.sessionManager!==e.sessionManager)throw Error(`Log readiness belongs to a stale Pi session.`);if(await t.operation,!m||t!==y)throw Error(`Log readiness belongs to a stale extension generation.`)}},T=(e,t)=>{let n=y,r=async()=>{if(n){if(!await n.operation.then(()=>!0,()=>!1)||!m||n!==y||n.sessionManager!==e.sessionManager)return}else if(!m)return;await t()};if(!u)return r();x(r)};return{events:{session_start:(t,r)=>{if(!m)return;let a=++h;p.clear(),g=void 0;let o=async e=>{e?.throwIfAborted(),C(r),await S(`info`,`pi.session.started`,{...i(r),"pi.session.reason":t.reason,"pi.session.previous":t.previousSessionFile!==void 0}),e?.throwIfAborted()},s=u?(0,n.readDoomReadinessCoordinator)(c):void 0;if(!s)return o();let l=y,d=(async()=>{l?.coordinator===s&&await l.operation.catch(()=>void 0),m&&a===h&&await s.start(e.PACKAGE_NAME,`${r.sessionManager.getSessionId()}:${a}`,async e=>(await o(e),{value:void 0})).wait()})();d.catch(()=>void 0),y={sessionManager:r.sessionManager,coordinator:s,operation:d}},before_agent_start:(e,t)=>{if(m)return T(t,async()=>{C(t),await S(`info`,`pi.user_prompt`,{...i(t),"pi.user_message.length":e.prompt.length,"pi.user_message.image_count":e.images?.length??0})})},agent_start:(e,t)=>{if(m)return T(t,async()=>{g=Date.now(),C(t),await S(`info`,`pi.agent.started`,i(t))})},turn_start:(e,t)=>{if(m)return T(t,async()=>{C(t),await S(`debug`,`pi.turn.started`,{...i(t),"pi.turn.index":e.turnIndex})})},turn_end:(t,n)=>{if(m)return T(n,async()=>{C(n);let r=a(t.message);if(await S(`info`,`pi.turn.finished`,{...i(n),"pi.turn.index":t.turnIndex,"pi.tool_result.count":t.toolResults.length,...r}),t.message.role===`assistant`&&e.FAILURE_STOP_REASONS.includes(t.message.stopReason)&&await S(t.message.stopReason===`aborted`?`warn`:`error`,`pi.turn.failed`,{...i(n),"pi.turn.index":t.turnIndex,...o(t.message),...r}),r)for(let e of t.toolResults)await S(`debug`,`pi.tool_token_sample`,{...i(n),...r,"tool.name":e.toolName,token_attribution:`toolCallTurn`})})},tool_execution_start:(e,t)=>{if(m)return T(t,async()=>{p.set(e.toolCallId,Date.now()),C(t),await S(`debug`,`pi.tool_call`,{...i(t),"tool.name":e.toolName,"tool.call.id":e.toolCallId})})},tool_execution_end:(e,t)=>{if(m)return T(t,async()=>{let n=p.get(e.toolCallId);p.delete(e.toolCallId),C(t),await S(`info`,`pi.tool_result`,{...i(t),success:!e.isError,"tool.name":e.toolName,"tool.call.id":e.toolCallId,"tool.result.error":e.isError,...n===void 0?{}:{"tool.duration_ms":Date.now()-n}})})},after_provider_response:(e,t)=>{if(m)return T(t,async()=>{C(t);let n={...i(t),"http.response.status_code":e.status};e.status>=400?await S(`error`,`pi.api_error`,n):await S(`debug`,`pi.api_response`,n)})},model_select:(e,t)=>{if(m)return T(t,async()=>{C(t),await S(`info`,`pi.model.selected`,{...i(t),"pi.model.source":e.source,"gen_ai.provider.name":e.model.provider,"gen_ai.request.model":e.model.id})})},agent_end:(e,t)=>{if(!m)return;let n=h;return T(t,async()=>{C(t),await S(`info`,`pi.agent.finished`,{...i(t),"pi.message.count":e.messages.length,...g===void 0?{}:{"pi.agent.duration_ms":Date.now()-g}}),m&&n===h&&(g=void 0,await _?.flush())})},agent_settled:(e,t)=>{if(m)return T(t,async()=>{C(t),await S(`debug`,`pi.agent.settled`,i(t))})}},onDispose:async()=>{m=!1,h+=1,y=void 0,p.clear(),g=void 0;let e=_;_=void 0,v=void 0,await e?.shutdown()},waitForSession:w,async finishSession(e,t){if(!m)return;await b;let n=y;n&&await n.operation.catch(()=>void 0),m&&(n&&n!==y||_&&(C(t),await S(`info`,`pi.session.finished`,{...i(t),"pi.session.reason":e})))}}}exports.createPiTelemetryRuntime=c;
1
+ const e=require("../constants/telemetry.cjs"),t=require("../services/telemetryEnabled/index.cjs");let n=require("@agimon-ai/doompi-core/readiness"),r=require("@agimon-ai/doompi-telemetry");function i(e){let t={"pi.mode":e.mode,"pi.session.id":e.sessionManager.getSessionId()};return e.thinkingLevel&&(t[`pi.thinking_level`]=e.thinkingLevel),e.model&&(t[`gen_ai.provider.name`]=e.model.provider,t[`gen_ai.request.model`]=e.model.id),t}function a(e){if(e.role!==`assistant`)return;let t={"gen_ai.response.model":e.responseModel??e.model,"gen_ai.response.finish_reasons":e.stopReason,"gen_ai.usage.input_tokens":e.usage.input,"gen_ai.usage.output_tokens":e.usage.output,"gen_ai.usage.cache_read_tokens":e.usage.cacheRead,"gen_ai.usage.cache_write_tokens":e.usage.cacheWrite,"gen_ai.usage.total_tokens":e.usage.totalTokens,"gen_ai.usage.cost":e.usage.cost.total};return e.usage.reasoning!==void 0&&(t[`gen_ai.usage.reasoning_tokens`]=e.usage.reasoning),t}function o(e){if(e.role!==`assistant`)return{"error.type":`AgentError`,outcome:`error`};let t=e.stopReason===`aborted`;return{"error.type":`ProviderError`,"error.code":e.stopReason,outcome:t?`aborted`:`error`}}function s(e){return{service:e.serviceName,backend:e.backend,endpoint:e.endpoint??`none`,endpointSource:e.endpointSource,traces:e.traces,redaction:!0,fileFallback:e.fileFallback}}function c(c,l={},u=!1){let d=l.env??process.env,f=l.metrics,p=new Map,m=new Map,h=!0,g=0,_,v,y,b,x=Promise.resolve(),S=e=>{x=x.then(e).catch(()=>void 0)},C=async(e,t,n)=>{h&&v&&(e===`debug`?await v.recordDebug(t,n):e===`warn`?await v.recordWarning(t,void 0,n):e===`error`?await v.recordError(t,void 0,n):await v.recordEvent(t,n))},w=n=>{if(!h)throw Error(`Log telemetry runtime is disposed.`);let i=n.sessionManager.getSessionId();if(v&&y!==i){let e=v;v=void 0,y=void 0,S(()=>e.shutdown())}return v||=(y=i,(0,r.createDoomTelemetry)({serviceName:l.serviceName??`pi`,packageName:e.PACKAGE_NAME,cwd:n.cwd,env:{...d,PI_SESSION_ID:i},allowFileFallback:l.allowFileFallback,telemetryFactory:l.telemetryFactory,warn:l.onDiagnostic,enableLogs:!0,enableTraces:d.AGENT_OTEL_TRACES===void 0||t.isEnabled(d.AGENT_OTEL_TRACES),onRecord:e=>{h&&f?.record(e.event,e.attributes)},onStatus:e=>{h&&l.onSinkStatus?.(s(e))}})),h&&l.onSinkStatus?.(s(v.status())),v},T=async e=>{let t=b;if(t){if(t.sessionManager!==e.sessionManager)throw Error(`Log readiness belongs to a stale Pi session.`);if(await t.operation,!h||t!==b)throw Error(`Log readiness belongs to a stale extension generation.`)}},E=(e,t)=>{let n=b,r=async()=>{if(n){if(!await n.operation.then(()=>!0,()=>!1)||!h||n!==b||n.sessionManager!==e.sessionManager)return}else if(!h)return;await t()};if(!u)return r();S(r)};return{events:{session_start:(t,r)=>{if(!h)return;let a=++g;p.clear(),m.clear(),_=void 0;let o=async e=>{e?.throwIfAborted(),w(r),await C(`info`,`pi.session.started`,{...i(r),"pi.session.reason":t.reason,"pi.session.previous":t.previousSessionFile!==void 0}),e?.throwIfAborted()},s=u?(0,n.readDoomReadinessCoordinator)(c):void 0;if(!s)return o();let l=b,d=(async()=>{l?.coordinator===s&&await l.operation.catch(()=>void 0),h&&a===g&&await s.start(e.PACKAGE_NAME,`${r.sessionManager.getSessionId()}:${a}`,async e=>(await o(e),{value:void 0})).wait()})();d.catch(()=>void 0),b={sessionManager:r.sessionManager,coordinator:s,operation:d}},before_agent_start:(e,t)=>{if(h)return E(t,async()=>{w(t),await C(`info`,`pi.user_prompt`,{...i(t),"pi.user_message.length":e.prompt.length,"pi.user_message.image_count":e.images?.length??0})})},agent_start:(e,t)=>{if(h)return E(t,async()=>{_=Date.now(),w(t),await C(`info`,`pi.agent.started`,i(t))})},turn_start:(e,t)=>{if(h)return m.set(e.turnIndex,Date.now()),E(t,async()=>{w(t),await C(`debug`,`pi.turn.started`,{...i(t),"pi.turn.index":e.turnIndex})})},turn_end:(t,n)=>{if(!h)return;let r=m.get(t.turnIndex);m.delete(t.turnIndex);let s=r===void 0?void 0:Date.now()-r;return E(n,async()=>{w(n);let r=a(t.message);if(await C(`info`,`pi.turn.finished`,{...i(n),"pi.turn.index":t.turnIndex,"pi.tool_result.count":t.toolResults.length,...s===void 0?{}:{"pi.turn.duration_ms":s},...r}),t.message.role===`assistant`&&e.FAILURE_STOP_REASONS.includes(t.message.stopReason)&&await C(t.message.stopReason===`aborted`?`warn`:`error`,`pi.turn.failed`,{...i(n),"pi.turn.index":t.turnIndex,...o(t.message),...r}),r)for(let e of t.toolResults)await C(`debug`,`pi.tool_token_sample`,{...i(n),...r,"tool.name":e.toolName,token_attribution:`toolCallTurn`})})},tool_execution_start:(e,t)=>{if(h)return E(t,async()=>{p.set(e.toolCallId,Date.now()),w(t),await C(`debug`,`pi.tool_call`,{...i(t),"tool.name":e.toolName,"tool.call.id":e.toolCallId})})},tool_execution_end:(e,t)=>{if(h)return E(t,async()=>{let n=p.get(e.toolCallId);p.delete(e.toolCallId),w(t),await C(`info`,`pi.tool_result`,{...i(t),success:!e.isError,"tool.name":e.toolName,"tool.call.id":e.toolCallId,"tool.result.error":e.isError,...n===void 0?{}:{"tool.duration_ms":Date.now()-n}})})},after_provider_response:(e,t)=>{if(h)return E(t,async()=>{w(t);let n={...i(t),"http.response.status_code":e.status};e.status>=400?await C(`error`,`pi.api_error`,n):await C(`debug`,`pi.api_response`,n)})},model_select:(e,t)=>{if(h)return E(t,async()=>{w(t),await C(`info`,`pi.model.selected`,{...i(t),"pi.model.source":e.source,"gen_ai.provider.name":e.model.provider,"gen_ai.request.model":e.model.id})})},agent_end:(e,t)=>{if(!h)return;let n=g;return E(t,async()=>{w(t),await C(`info`,`pi.agent.finished`,{...i(t),"pi.message.count":e.messages.length,..._===void 0?{}:{"pi.agent.duration_ms":Date.now()-_}}),h&&n===g&&(_=void 0,await v?.flush())})},agent_settled:(e,t)=>{if(h)return E(t,async()=>{w(t),await C(`debug`,`pi.agent.settled`,i(t))})}},onDispose:async()=>{h=!1,g+=1,b=void 0,p.clear(),m.clear(),_=void 0;let e=v;v=void 0,y=void 0,await e?.shutdown()},waitForSession:T,async finishSession(e,t){if(!h)return;await x;let n=b;n&&await n.operation.catch(()=>void 0),h&&(n&&n!==b||v&&(w(t),await C(`info`,`pi.session.finished`,{...i(t),"pi.session.reason":e})))}}}exports.createPiTelemetryRuntime=c;
2
2
  //# sourceMappingURL=piTelemetry.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"piTelemetry.cjs","names":["createDoomTelemetry","PACKAGE_NAME","isEnabled","readDoomReadinessCoordinator","FAILURE_STOP_REASONS"],"sources":["../../src/controllers/piTelemetry.ts"],"sourcesContent":["import type { PiEventHandlers } from '@agimon-ai/doompi-core/pi-extension';\nimport { type DoomReadinessCoordinator, readDoomReadinessCoordinator } from '@agimon-ai/doompi-core/readiness';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionContext, TurnEndEvent } from '@earendil-works/pi-coding-agent';\n\nimport {\n SERVICE_NAME,\n PACKAGE_NAME,\n NO_ENDPOINT,\n API_ERROR_STATUS,\n FAILURE_STOP_REASONS,\n} from '../constants/telemetry';\nimport type {\n PiTelemetryExtensionOptions,\n PiTelemetryRuntimeHandle,\n TelemetryAttributes,\n RecordLevel,\n} from '../types/piTelemetry';\nimport type { SinkStatus } from '../types/sinkStatus';\ninterface PiTelemetryRuntime extends PiTelemetryRuntimeHandle {\n events: PiEventHandlers;\n onDispose(this: void): Promise<void>;\n}\nimport { isEnabled } from '../services/telemetryEnabled';\nfunction contextAttributes(ctx: ExtensionContext): TelemetryAttributes {\n const attributes: TelemetryAttributes = {\n 'pi.mode': ctx.mode,\n 'pi.session.id': ctx.sessionManager.getSessionId(),\n };\n if (ctx.thinkingLevel) attributes['pi.thinking_level'] = ctx.thinkingLevel;\n if (ctx.model) {\n attributes['gen_ai.provider.name'] = ctx.model.provider;\n attributes['gen_ai.request.model'] = ctx.model.id;\n }\n return attributes;\n}\n\nfunction usageAttributes(message: TurnEndEvent['message']): TelemetryAttributes | undefined {\n if (message.role !== 'assistant') return undefined;\n const attributes: TelemetryAttributes = {\n 'gen_ai.response.model': message.responseModel ?? message.model,\n 'gen_ai.response.finish_reasons': message.stopReason,\n 'gen_ai.usage.input_tokens': message.usage.input,\n 'gen_ai.usage.output_tokens': message.usage.output,\n 'gen_ai.usage.cache_read_tokens': message.usage.cacheRead,\n 'gen_ai.usage.cache_write_tokens': message.usage.cacheWrite,\n 'gen_ai.usage.total_tokens': message.usage.totalTokens,\n 'gen_ai.usage.cost': message.usage.cost.total,\n };\n if (message.usage.reasoning !== undefined) attributes['gen_ai.usage.reasoning_tokens'] = message.usage.reasoning;\n return attributes;\n}\n\nfunction failureAttributes(message: TurnEndEvent['message']): TelemetryAttributes {\n if (message.role !== 'assistant') return { 'error.type': 'AgentError', outcome: 'error' };\n const aborted = message.stopReason === 'aborted';\n return {\n 'error.type': 'ProviderError',\n 'error.code': message.stopReason,\n outcome: aborted ? 'aborted' : 'error',\n };\n}\n\nfunction statusFromTelemetry(status: ReturnType<DoomTelemetry['status']>): SinkStatus {\n return {\n service: status.serviceName,\n backend: status.backend,\n endpoint: status.endpoint ?? NO_ENDPOINT,\n endpointSource: status.endpointSource,\n traces: status.traces,\n redaction: true,\n fileFallback: status.fileFallback,\n };\n}\n\nexport function createPiTelemetryRuntime(\n cordis: Context,\n options: PiTelemetryExtensionOptions = {},\n splitReadiness = false,\n): PiTelemetryRuntime {\n const env = options.env ?? process.env;\n const metrics = options.metrics;\n const toolStartedAt = new Map<string, number>();\n let active = true;\n let sessionGeneration = 0;\n let agentStartedAt: number | undefined;\n let telemetry: DoomTelemetry | undefined;\n let telemetrySessionId: string | undefined;\n let sessionReadiness:\n | {\n readonly sessionManager: object;\n readonly coordinator: DoomReadinessCoordinator;\n readonly operation: Promise<void>;\n }\n | undefined;\n let backgroundQueue = Promise.resolve();\n\n const enqueue = (operation: () => Promise<void>): void => {\n const scheduled = backgroundQueue.then(operation);\n // Telemetry is advisory and the coordinator owns startup failure notification.\n backgroundQueue = scheduled.catch(() => undefined);\n };\n\n const emit = async (level: RecordLevel, name: string, attributes: TelemetryAttributes): Promise<void> => {\n if (!active || !telemetry) return;\n if (level === 'debug') await telemetry.recordDebug(name, attributes);\n else if (level === 'warn') await telemetry.recordWarning(name, undefined, attributes);\n else if (level === 'error') await telemetry.recordError(name, undefined, attributes);\n else await telemetry.recordEvent(name, attributes);\n };\n\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n if (!active) throw new Error('Log telemetry runtime is disposed.');\n // The sink groups records by the x-agent-session-id header, which the SDK derives from\n // the environment once per handle. Nothing stamps PI_SESSION_ID for a top-level session\n // (only doompi-workflow does, for spawned children), so without this overlay every record\n // lands in a single unattributed group and per-agent cost cannot be queried.\n const sessionId = ctx.sessionManager.getSessionId();\n // The handle caches those headers, so a second session in the same process would keep\n // reporting under the first session's id. Rebuild when the session changes.\n if (telemetry && telemetrySessionId !== sessionId) {\n const staleTelemetry = telemetry;\n telemetry = undefined;\n telemetrySessionId = undefined;\n enqueue(() => staleTelemetry.shutdown());\n }\n if (!telemetry) {\n telemetrySessionId = sessionId;\n telemetry = createDoomTelemetry({\n serviceName: options.serviceName ?? SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: ctx.cwd,\n env: { ...env, PI_SESSION_ID: sessionId },\n allowFileFallback: options.allowFileFallback,\n telemetryFactory: options.telemetryFactory,\n warn: options.onDiagnostic,\n enableLogs: true,\n enableTraces: env.AGENT_OTEL_TRACES === undefined || isEnabled(env.AGENT_OTEL_TRACES),\n onRecord: (record) => {\n if (active) metrics?.record(record.event, record.attributes);\n },\n onStatus: (status) => {\n if (active) options.onSinkStatus?.(statusFromTelemetry(status));\n },\n });\n }\n if (active) options.onSinkStatus?.(statusFromTelemetry(telemetry.status()));\n return telemetry;\n };\n\n const waitForSession = async (ctx: ExtensionContext): Promise<void> => {\n const current = sessionReadiness;\n if (!current) return;\n if (current.sessionManager !== ctx.sessionManager) {\n throw new Error('Log readiness belongs to a stale Pi session.');\n }\n await current.operation;\n if (!active || current !== sessionReadiness) {\n throw new Error('Log readiness belongs to a stale extension generation.');\n }\n };\n\n const enqueueForSession = (ctx: ExtensionContext, operation: () => Promise<void>): void | Promise<void> => {\n const expected = sessionReadiness;\n const run = async (): Promise<void> => {\n if (expected) {\n const ready = await expected.operation.then(\n () => true,\n () => false,\n );\n if (!ready || !active || expected !== sessionReadiness || expected.sessionManager !== ctx.sessionManager) {\n return;\n }\n } else if (!active) {\n return;\n }\n await operation();\n };\n if (!splitReadiness) return run();\n enqueue(run);\n return undefined;\n };\n\n return {\n events: {\n session_start: (event, ctx) => {\n if (!active) return undefined;\n const ownGeneration = ++sessionGeneration;\n toolStartedAt.clear();\n agentStartedAt = undefined;\n const initialize = async (signal?: AbortSignal): Promise<void> => {\n signal?.throwIfAborted();\n getTelemetry(ctx);\n await emit('info', 'pi.session.started', {\n ...contextAttributes(ctx),\n 'pi.session.reason': event.reason,\n 'pi.session.previous': event.previousSessionFile !== undefined,\n });\n signal?.throwIfAborted();\n };\n const coordinator = splitReadiness ? readDoomReadinessCoordinator(cordis) : undefined;\n if (!coordinator) return initialize();\n\n const previous = sessionReadiness;\n const operation = (async (): Promise<void> => {\n if (previous?.coordinator === coordinator) await previous.operation.catch(() => undefined);\n if (!active || ownGeneration !== sessionGeneration) return;\n const handle = coordinator.start(\n PACKAGE_NAME,\n `${ctx.sessionManager.getSessionId()}:${ownGeneration}`,\n async (signal) => {\n await initialize(signal);\n return { value: undefined };\n },\n );\n await handle.wait();\n })();\n // Config's coordinator owns the single user-facing failure notification.\n void operation.catch(() => undefined);\n sessionReadiness = { sessionManager: ctx.sessionManager, coordinator, operation };\n return undefined;\n },\n\n before_agent_start: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.user_prompt', {\n ...contextAttributes(ctx),\n 'pi.user_message.length': event.prompt.length,\n 'pi.user_message.image_count': event.images?.length ?? 0,\n });\n });\n },\n\n agent_start: (_event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n agentStartedAt = Date.now();\n getTelemetry(ctx);\n await emit('info', 'pi.agent.started', contextAttributes(ctx));\n });\n },\n\n turn_start: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('debug', 'pi.turn.started', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n });\n });\n },\n\n turn_end: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n const usage = usageAttributes(event.message);\n await emit('info', 'pi.turn.finished', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n 'pi.tool_result.count': event.toolResults.length,\n ...usage,\n });\n if (event.message.role === 'assistant' && FAILURE_STOP_REASONS.includes(event.message.stopReason)) {\n await emit(event.message.stopReason === 'aborted' ? 'warn' : 'error', 'pi.turn.failed', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n ...failureAttributes(event.message),\n ...usage,\n });\n }\n if (!usage) return;\n for (const result of event.toolResults) {\n await emit('debug', 'pi.tool_token_sample', {\n ...contextAttributes(ctx),\n ...usage,\n 'tool.name': result.toolName,\n token_attribution: 'toolCallTurn',\n });\n }\n });\n },\n\n tool_execution_start: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n toolStartedAt.set(event.toolCallId, Date.now());\n getTelemetry(ctx);\n await emit('debug', 'pi.tool_call', {\n ...contextAttributes(ctx),\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n });\n });\n },\n\n tool_execution_end: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n const startedAt = toolStartedAt.get(event.toolCallId);\n toolStartedAt.delete(event.toolCallId);\n getTelemetry(ctx);\n await emit('info', 'pi.tool_result', {\n ...contextAttributes(ctx),\n success: !event.isError,\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n 'tool.result.error': event.isError,\n ...(startedAt === undefined ? {} : { 'tool.duration_ms': Date.now() - startedAt }),\n });\n });\n },\n\n after_provider_response: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n const attributes = {\n ...contextAttributes(ctx),\n 'http.response.status_code': event.status,\n };\n if (event.status >= API_ERROR_STATUS) await emit('error', 'pi.api_error', attributes);\n else await emit('debug', 'pi.api_response', attributes);\n });\n },\n\n model_select: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.model.selected', {\n ...contextAttributes(ctx),\n 'pi.model.source': event.source,\n 'gen_ai.provider.name': event.model.provider,\n 'gen_ai.request.model': event.model.id,\n });\n });\n },\n\n agent_end: (event, ctx) => {\n if (!active) return;\n const generation = sessionGeneration;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.agent.finished', {\n ...contextAttributes(ctx),\n 'pi.message.count': event.messages.length,\n ...(agentStartedAt === undefined ? {} : { 'pi.agent.duration_ms': Date.now() - agentStartedAt }),\n });\n if (!active || generation !== sessionGeneration) return;\n agentStartedAt = undefined;\n await telemetry?.flush();\n });\n },\n\n agent_settled: (_event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('debug', 'pi.agent.settled', contextAttributes(ctx));\n });\n },\n },\n onDispose: async () => {\n active = false;\n sessionGeneration += 1;\n sessionReadiness = undefined;\n toolStartedAt.clear();\n agentStartedAt = undefined;\n const ownedTelemetry = telemetry;\n telemetry = undefined;\n telemetrySessionId = undefined;\n await ownedTelemetry?.shutdown();\n },\n waitForSession,\n async finishSession(reason: string, ctx: ExtensionContext) {\n if (!active) return;\n await backgroundQueue;\n const current = sessionReadiness;\n if (current) await current.operation.catch(() => undefined);\n if (!active) return;\n if (current && current !== sessionReadiness) return;\n if (!telemetry) return;\n getTelemetry(ctx);\n await emit('info', 'pi.session.finished', {\n ...contextAttributes(ctx),\n 'pi.session.reason': reason,\n });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"piTelemetry.cjs","names":["createDoomTelemetry","PACKAGE_NAME","isEnabled","readDoomReadinessCoordinator","FAILURE_STOP_REASONS"],"sources":["../../src/controllers/piTelemetry.ts"],"sourcesContent":["import type { PiEventHandlers } from '@agimon-ai/doompi-core/pi-extension';\nimport { type DoomReadinessCoordinator, readDoomReadinessCoordinator } from '@agimon-ai/doompi-core/readiness';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionContext, TurnEndEvent } from '@earendil-works/pi-coding-agent';\n\nimport {\n SERVICE_NAME,\n PACKAGE_NAME,\n NO_ENDPOINT,\n API_ERROR_STATUS,\n FAILURE_STOP_REASONS,\n} from '../constants/telemetry';\nimport type {\n PiTelemetryExtensionOptions,\n PiTelemetryRuntimeHandle,\n TelemetryAttributes,\n RecordLevel,\n} from '../types/piTelemetry';\nimport type { SinkStatus } from '../types/sinkStatus';\ninterface PiTelemetryRuntime extends PiTelemetryRuntimeHandle {\n events: PiEventHandlers;\n onDispose(this: void): Promise<void>;\n}\nimport { isEnabled } from '../services/telemetryEnabled';\nfunction contextAttributes(ctx: ExtensionContext): TelemetryAttributes {\n const attributes: TelemetryAttributes = {\n 'pi.mode': ctx.mode,\n 'pi.session.id': ctx.sessionManager.getSessionId(),\n };\n if (ctx.thinkingLevel) attributes['pi.thinking_level'] = ctx.thinkingLevel;\n if (ctx.model) {\n attributes['gen_ai.provider.name'] = ctx.model.provider;\n attributes['gen_ai.request.model'] = ctx.model.id;\n }\n return attributes;\n}\n\nfunction usageAttributes(message: TurnEndEvent['message']): TelemetryAttributes | undefined {\n if (message.role !== 'assistant') return undefined;\n const attributes: TelemetryAttributes = {\n 'gen_ai.response.model': message.responseModel ?? message.model,\n 'gen_ai.response.finish_reasons': message.stopReason,\n 'gen_ai.usage.input_tokens': message.usage.input,\n 'gen_ai.usage.output_tokens': message.usage.output,\n 'gen_ai.usage.cache_read_tokens': message.usage.cacheRead,\n 'gen_ai.usage.cache_write_tokens': message.usage.cacheWrite,\n 'gen_ai.usage.total_tokens': message.usage.totalTokens,\n 'gen_ai.usage.cost': message.usage.cost.total,\n };\n if (message.usage.reasoning !== undefined) attributes['gen_ai.usage.reasoning_tokens'] = message.usage.reasoning;\n return attributes;\n}\n\nfunction failureAttributes(message: TurnEndEvent['message']): TelemetryAttributes {\n if (message.role !== 'assistant') return { 'error.type': 'AgentError', outcome: 'error' };\n const aborted = message.stopReason === 'aborted';\n return {\n 'error.type': 'ProviderError',\n 'error.code': message.stopReason,\n outcome: aborted ? 'aborted' : 'error',\n };\n}\n\nfunction statusFromTelemetry(status: ReturnType<DoomTelemetry['status']>): SinkStatus {\n return {\n service: status.serviceName,\n backend: status.backend,\n endpoint: status.endpoint ?? NO_ENDPOINT,\n endpointSource: status.endpointSource,\n traces: status.traces,\n redaction: true,\n fileFallback: status.fileFallback,\n };\n}\n\nexport function createPiTelemetryRuntime(\n cordis: Context,\n options: PiTelemetryExtensionOptions = {},\n splitReadiness = false,\n): PiTelemetryRuntime {\n const env = options.env ?? process.env;\n const metrics = options.metrics;\n const toolStartedAt = new Map<string, number>();\n const turnStartedAt = new Map<number, number>();\n let active = true;\n let sessionGeneration = 0;\n let agentStartedAt: number | undefined;\n let telemetry: DoomTelemetry | undefined;\n let telemetrySessionId: string | undefined;\n let sessionReadiness:\n | {\n readonly sessionManager: object;\n readonly coordinator: DoomReadinessCoordinator;\n readonly operation: Promise<void>;\n }\n | undefined;\n let backgroundQueue = Promise.resolve();\n\n const enqueue = (operation: () => Promise<void>): void => {\n const scheduled = backgroundQueue.then(operation);\n // Telemetry is advisory and the coordinator owns startup failure notification.\n backgroundQueue = scheduled.catch(() => undefined);\n };\n\n const emit = async (level: RecordLevel, name: string, attributes: TelemetryAttributes): Promise<void> => {\n if (!active || !telemetry) return;\n if (level === 'debug') await telemetry.recordDebug(name, attributes);\n else if (level === 'warn') await telemetry.recordWarning(name, undefined, attributes);\n else if (level === 'error') await telemetry.recordError(name, undefined, attributes);\n else await telemetry.recordEvent(name, attributes);\n };\n\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n if (!active) throw new Error('Log telemetry runtime is disposed.');\n // The sink groups records by the x-agent-session-id header, which the SDK derives from\n // the environment once per handle. Nothing stamps PI_SESSION_ID for a top-level session\n // (only doompi-workflow does, for spawned children), so without this overlay every record\n // lands in a single unattributed group and per-agent cost cannot be queried.\n const sessionId = ctx.sessionManager.getSessionId();\n // The handle caches those headers, so a second session in the same process would keep\n // reporting under the first session's id. Rebuild when the session changes.\n if (telemetry && telemetrySessionId !== sessionId) {\n const staleTelemetry = telemetry;\n telemetry = undefined;\n telemetrySessionId = undefined;\n enqueue(() => staleTelemetry.shutdown());\n }\n if (!telemetry) {\n telemetrySessionId = sessionId;\n telemetry = createDoomTelemetry({\n serviceName: options.serviceName ?? SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: ctx.cwd,\n env: { ...env, PI_SESSION_ID: sessionId },\n allowFileFallback: options.allowFileFallback,\n telemetryFactory: options.telemetryFactory,\n warn: options.onDiagnostic,\n enableLogs: true,\n enableTraces: env.AGENT_OTEL_TRACES === undefined || isEnabled(env.AGENT_OTEL_TRACES),\n onRecord: (record) => {\n if (active) metrics?.record(record.event, record.attributes);\n },\n onStatus: (status) => {\n if (active) options.onSinkStatus?.(statusFromTelemetry(status));\n },\n });\n }\n if (active) options.onSinkStatus?.(statusFromTelemetry(telemetry.status()));\n return telemetry;\n };\n\n const waitForSession = async (ctx: ExtensionContext): Promise<void> => {\n const current = sessionReadiness;\n if (!current) return;\n if (current.sessionManager !== ctx.sessionManager) {\n throw new Error('Log readiness belongs to a stale Pi session.');\n }\n await current.operation;\n if (!active || current !== sessionReadiness) {\n throw new Error('Log readiness belongs to a stale extension generation.');\n }\n };\n\n const enqueueForSession = (ctx: ExtensionContext, operation: () => Promise<void>): void | Promise<void> => {\n const expected = sessionReadiness;\n const run = async (): Promise<void> => {\n if (expected) {\n const ready = await expected.operation.then(\n () => true,\n () => false,\n );\n if (!ready || !active || expected !== sessionReadiness || expected.sessionManager !== ctx.sessionManager) {\n return;\n }\n } else if (!active) {\n return;\n }\n await operation();\n };\n if (!splitReadiness) return run();\n enqueue(run);\n return undefined;\n };\n\n return {\n events: {\n session_start: (event, ctx) => {\n if (!active) return undefined;\n const ownGeneration = ++sessionGeneration;\n toolStartedAt.clear();\n turnStartedAt.clear();\n agentStartedAt = undefined;\n const initialize = async (signal?: AbortSignal): Promise<void> => {\n signal?.throwIfAborted();\n getTelemetry(ctx);\n await emit('info', 'pi.session.started', {\n ...contextAttributes(ctx),\n 'pi.session.reason': event.reason,\n 'pi.session.previous': event.previousSessionFile !== undefined,\n });\n signal?.throwIfAborted();\n };\n const coordinator = splitReadiness ? readDoomReadinessCoordinator(cordis) : undefined;\n if (!coordinator) return initialize();\n\n const previous = sessionReadiness;\n const operation = (async (): Promise<void> => {\n if (previous?.coordinator === coordinator) await previous.operation.catch(() => undefined);\n if (!active || ownGeneration !== sessionGeneration) return;\n const handle = coordinator.start(\n PACKAGE_NAME,\n `${ctx.sessionManager.getSessionId()}:${ownGeneration}`,\n async (signal) => {\n await initialize(signal);\n return { value: undefined };\n },\n );\n await handle.wait();\n })();\n // Config's coordinator owns the single user-facing failure notification.\n void operation.catch(() => undefined);\n sessionReadiness = { sessionManager: ctx.sessionManager, coordinator, operation };\n return undefined;\n },\n\n before_agent_start: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.user_prompt', {\n ...contextAttributes(ctx),\n 'pi.user_message.length': event.prompt.length,\n 'pi.user_message.image_count': event.images?.length ?? 0,\n });\n });\n },\n\n agent_start: (_event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n agentStartedAt = Date.now();\n getTelemetry(ctx);\n await emit('info', 'pi.agent.started', contextAttributes(ctx));\n });\n },\n\n turn_start: (event, ctx) => {\n if (!active) return;\n // Stamped as the event fires, not inside the queue, so a backed-up queue\n // cannot inflate the turn duration this records.\n turnStartedAt.set(event.turnIndex, Date.now());\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('debug', 'pi.turn.started', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n });\n });\n },\n\n turn_end: (event, ctx) => {\n if (!active) return;\n const turnStart = turnStartedAt.get(event.turnIndex);\n turnStartedAt.delete(event.turnIndex);\n const turnDurationMs = turnStart === undefined ? undefined : Date.now() - turnStart;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n const usage = usageAttributes(event.message);\n await emit('info', 'pi.turn.finished', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n 'pi.tool_result.count': event.toolResults.length,\n ...(turnDurationMs === undefined ? {} : { 'pi.turn.duration_ms': turnDurationMs }),\n ...usage,\n });\n if (event.message.role === 'assistant' && FAILURE_STOP_REASONS.includes(event.message.stopReason)) {\n await emit(event.message.stopReason === 'aborted' ? 'warn' : 'error', 'pi.turn.failed', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n ...failureAttributes(event.message),\n ...usage,\n });\n }\n if (!usage) return;\n for (const result of event.toolResults) {\n await emit('debug', 'pi.tool_token_sample', {\n ...contextAttributes(ctx),\n ...usage,\n 'tool.name': result.toolName,\n token_attribution: 'toolCallTurn',\n });\n }\n });\n },\n\n tool_execution_start: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n toolStartedAt.set(event.toolCallId, Date.now());\n getTelemetry(ctx);\n await emit('debug', 'pi.tool_call', {\n ...contextAttributes(ctx),\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n });\n });\n },\n\n tool_execution_end: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n const startedAt = toolStartedAt.get(event.toolCallId);\n toolStartedAt.delete(event.toolCallId);\n getTelemetry(ctx);\n await emit('info', 'pi.tool_result', {\n ...contextAttributes(ctx),\n success: !event.isError,\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n 'tool.result.error': event.isError,\n ...(startedAt === undefined ? {} : { 'tool.duration_ms': Date.now() - startedAt }),\n });\n });\n },\n\n after_provider_response: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n const attributes = {\n ...contextAttributes(ctx),\n 'http.response.status_code': event.status,\n };\n if (event.status >= API_ERROR_STATUS) await emit('error', 'pi.api_error', attributes);\n else await emit('debug', 'pi.api_response', attributes);\n });\n },\n\n model_select: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.model.selected', {\n ...contextAttributes(ctx),\n 'pi.model.source': event.source,\n 'gen_ai.provider.name': event.model.provider,\n 'gen_ai.request.model': event.model.id,\n });\n });\n },\n\n agent_end: (event, ctx) => {\n if (!active) return;\n const generation = sessionGeneration;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.agent.finished', {\n ...contextAttributes(ctx),\n 'pi.message.count': event.messages.length,\n ...(agentStartedAt === undefined ? {} : { 'pi.agent.duration_ms': Date.now() - agentStartedAt }),\n });\n if (!active || generation !== sessionGeneration) return;\n agentStartedAt = undefined;\n await telemetry?.flush();\n });\n },\n\n agent_settled: (_event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('debug', 'pi.agent.settled', contextAttributes(ctx));\n });\n },\n },\n onDispose: async () => {\n active = false;\n sessionGeneration += 1;\n sessionReadiness = undefined;\n toolStartedAt.clear();\n turnStartedAt.clear();\n agentStartedAt = undefined;\n const ownedTelemetry = telemetry;\n telemetry = undefined;\n telemetrySessionId = undefined;\n await ownedTelemetry?.shutdown();\n },\n waitForSession,\n async finishSession(reason: string, ctx: ExtensionContext) {\n if (!active) return;\n await backgroundQueue;\n const current = sessionReadiness;\n if (current) await current.operation.catch(() => undefined);\n if (!active) return;\n if (current && current !== sessionReadiness) return;\n if (!telemetry) return;\n getTelemetry(ctx);\n await emit('info', 'pi.session.finished', {\n ...contextAttributes(ctx),\n 'pi.session.reason': reason,\n });\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{FAILURE_STOP_REASONS as e,PACKAGE_NAME as t}from"../constants/telemetry.mjs";import{isEnabled as n}from"../services/telemetryEnabled/index.mjs";import{readDoomReadinessCoordinator as r}from"@agimon-ai/doompi-core/readiness";import{createDoomTelemetry as i}from"@agimon-ai/doompi-telemetry";function a(e){let t={"pi.mode":e.mode,"pi.session.id":e.sessionManager.getSessionId()};return e.thinkingLevel&&(t[`pi.thinking_level`]=e.thinkingLevel),e.model&&(t[`gen_ai.provider.name`]=e.model.provider,t[`gen_ai.request.model`]=e.model.id),t}function o(e){if(e.role!==`assistant`)return;let t={"gen_ai.response.model":e.responseModel??e.model,"gen_ai.response.finish_reasons":e.stopReason,"gen_ai.usage.input_tokens":e.usage.input,"gen_ai.usage.output_tokens":e.usage.output,"gen_ai.usage.cache_read_tokens":e.usage.cacheRead,"gen_ai.usage.cache_write_tokens":e.usage.cacheWrite,"gen_ai.usage.total_tokens":e.usage.totalTokens,"gen_ai.usage.cost":e.usage.cost.total};return e.usage.reasoning!==void 0&&(t[`gen_ai.usage.reasoning_tokens`]=e.usage.reasoning),t}function s(e){if(e.role!==`assistant`)return{"error.type":`AgentError`,outcome:`error`};let t=e.stopReason===`aborted`;return{"error.type":`ProviderError`,"error.code":e.stopReason,outcome:t?`aborted`:`error`}}function c(e){return{service:e.serviceName,backend:e.backend,endpoint:e.endpoint??`none`,endpointSource:e.endpointSource,traces:e.traces,redaction:!0,fileFallback:e.fileFallback}}function l(l,u={},d=!1){let f=u.env??process.env,p=u.metrics,m=new Map,h=!0,g=0,_,v,y,b,x=Promise.resolve(),S=e=>{x=x.then(e).catch(()=>void 0)},C=async(e,t,n)=>{h&&v&&(e===`debug`?await v.recordDebug(t,n):e===`warn`?await v.recordWarning(t,void 0,n):e===`error`?await v.recordError(t,void 0,n):await v.recordEvent(t,n))},w=e=>{if(!h)throw Error(`Log telemetry runtime is disposed.`);let r=e.sessionManager.getSessionId();if(v&&y!==r){let e=v;v=void 0,y=void 0,S(()=>e.shutdown())}return v||=(y=r,i({serviceName:u.serviceName??`pi`,packageName:t,cwd:e.cwd,env:{...f,PI_SESSION_ID:r},allowFileFallback:u.allowFileFallback,telemetryFactory:u.telemetryFactory,warn:u.onDiagnostic,enableLogs:!0,enableTraces:f.AGENT_OTEL_TRACES===void 0||n(f.AGENT_OTEL_TRACES),onRecord:e=>{h&&p?.record(e.event,e.attributes)},onStatus:e=>{h&&u.onSinkStatus?.(c(e))}})),h&&u.onSinkStatus?.(c(v.status())),v},T=async e=>{let t=b;if(t){if(t.sessionManager!==e.sessionManager)throw Error(`Log readiness belongs to a stale Pi session.`);if(await t.operation,!h||t!==b)throw Error(`Log readiness belongs to a stale extension generation.`)}},E=(e,t)=>{let n=b,r=async()=>{if(n){if(!await n.operation.then(()=>!0,()=>!1)||!h||n!==b||n.sessionManager!==e.sessionManager)return}else if(!h)return;await t()};if(!d)return r();S(r)};return{events:{session_start:(e,n)=>{if(!h)return;let i=++g;m.clear(),_=void 0;let o=async t=>{t?.throwIfAborted(),w(n),await C(`info`,`pi.session.started`,{...a(n),"pi.session.reason":e.reason,"pi.session.previous":e.previousSessionFile!==void 0}),t?.throwIfAborted()},s=d?r(l):void 0;if(!s)return o();let c=b,u=(async()=>{c?.coordinator===s&&await c.operation.catch(()=>void 0),h&&i===g&&await s.start(t,`${n.sessionManager.getSessionId()}:${i}`,async e=>(await o(e),{value:void 0})).wait()})();u.catch(()=>void 0),b={sessionManager:n.sessionManager,coordinator:s,operation:u}},before_agent_start:(e,t)=>{if(h)return E(t,async()=>{w(t),await C(`info`,`pi.user_prompt`,{...a(t),"pi.user_message.length":e.prompt.length,"pi.user_message.image_count":e.images?.length??0})})},agent_start:(e,t)=>{if(h)return E(t,async()=>{_=Date.now(),w(t),await C(`info`,`pi.agent.started`,a(t))})},turn_start:(e,t)=>{if(h)return E(t,async()=>{w(t),await C(`debug`,`pi.turn.started`,{...a(t),"pi.turn.index":e.turnIndex})})},turn_end:(t,n)=>{if(h)return E(n,async()=>{w(n);let r=o(t.message);if(await C(`info`,`pi.turn.finished`,{...a(n),"pi.turn.index":t.turnIndex,"pi.tool_result.count":t.toolResults.length,...r}),t.message.role===`assistant`&&e.includes(t.message.stopReason)&&await C(t.message.stopReason===`aborted`?`warn`:`error`,`pi.turn.failed`,{...a(n),"pi.turn.index":t.turnIndex,...s(t.message),...r}),r)for(let e of t.toolResults)await C(`debug`,`pi.tool_token_sample`,{...a(n),...r,"tool.name":e.toolName,token_attribution:`toolCallTurn`})})},tool_execution_start:(e,t)=>{if(h)return E(t,async()=>{m.set(e.toolCallId,Date.now()),w(t),await C(`debug`,`pi.tool_call`,{...a(t),"tool.name":e.toolName,"tool.call.id":e.toolCallId})})},tool_execution_end:(e,t)=>{if(h)return E(t,async()=>{let n=m.get(e.toolCallId);m.delete(e.toolCallId),w(t),await C(`info`,`pi.tool_result`,{...a(t),success:!e.isError,"tool.name":e.toolName,"tool.call.id":e.toolCallId,"tool.result.error":e.isError,...n===void 0?{}:{"tool.duration_ms":Date.now()-n}})})},after_provider_response:(e,t)=>{if(h)return E(t,async()=>{w(t);let n={...a(t),"http.response.status_code":e.status};e.status>=400?await C(`error`,`pi.api_error`,n):await C(`debug`,`pi.api_response`,n)})},model_select:(e,t)=>{if(h)return E(t,async()=>{w(t),await C(`info`,`pi.model.selected`,{...a(t),"pi.model.source":e.source,"gen_ai.provider.name":e.model.provider,"gen_ai.request.model":e.model.id})})},agent_end:(e,t)=>{if(!h)return;let n=g;return E(t,async()=>{w(t),await C(`info`,`pi.agent.finished`,{...a(t),"pi.message.count":e.messages.length,..._===void 0?{}:{"pi.agent.duration_ms":Date.now()-_}}),h&&n===g&&(_=void 0,await v?.flush())})},agent_settled:(e,t)=>{if(h)return E(t,async()=>{w(t),await C(`debug`,`pi.agent.settled`,a(t))})}},onDispose:async()=>{h=!1,g+=1,b=void 0,m.clear(),_=void 0;let e=v;v=void 0,y=void 0,await e?.shutdown()},waitForSession:T,async finishSession(e,t){if(!h)return;await x;let n=b;n&&await n.operation.catch(()=>void 0),h&&(n&&n!==b||v&&(w(t),await C(`info`,`pi.session.finished`,{...a(t),"pi.session.reason":e})))}}}export{l as createPiTelemetryRuntime};
1
+ import{FAILURE_STOP_REASONS as e,PACKAGE_NAME as t}from"../constants/telemetry.mjs";import{isEnabled as n}from"../services/telemetryEnabled/index.mjs";import{readDoomReadinessCoordinator as r}from"@agimon-ai/doompi-core/readiness";import{createDoomTelemetry as i}from"@agimon-ai/doompi-telemetry";function a(e){let t={"pi.mode":e.mode,"pi.session.id":e.sessionManager.getSessionId()};return e.thinkingLevel&&(t[`pi.thinking_level`]=e.thinkingLevel),e.model&&(t[`gen_ai.provider.name`]=e.model.provider,t[`gen_ai.request.model`]=e.model.id),t}function o(e){if(e.role!==`assistant`)return;let t={"gen_ai.response.model":e.responseModel??e.model,"gen_ai.response.finish_reasons":e.stopReason,"gen_ai.usage.input_tokens":e.usage.input,"gen_ai.usage.output_tokens":e.usage.output,"gen_ai.usage.cache_read_tokens":e.usage.cacheRead,"gen_ai.usage.cache_write_tokens":e.usage.cacheWrite,"gen_ai.usage.total_tokens":e.usage.totalTokens,"gen_ai.usage.cost":e.usage.cost.total};return e.usage.reasoning!==void 0&&(t[`gen_ai.usage.reasoning_tokens`]=e.usage.reasoning),t}function s(e){if(e.role!==`assistant`)return{"error.type":`AgentError`,outcome:`error`};let t=e.stopReason===`aborted`;return{"error.type":`ProviderError`,"error.code":e.stopReason,outcome:t?`aborted`:`error`}}function c(e){return{service:e.serviceName,backend:e.backend,endpoint:e.endpoint??`none`,endpointSource:e.endpointSource,traces:e.traces,redaction:!0,fileFallback:e.fileFallback}}function l(l,u={},d=!1){let f=u.env??process.env,p=u.metrics,m=new Map,h=new Map,g=!0,_=0,v,y,b,x,S=Promise.resolve(),C=e=>{S=S.then(e).catch(()=>void 0)},w=async(e,t,n)=>{g&&y&&(e===`debug`?await y.recordDebug(t,n):e===`warn`?await y.recordWarning(t,void 0,n):e===`error`?await y.recordError(t,void 0,n):await y.recordEvent(t,n))},T=e=>{if(!g)throw Error(`Log telemetry runtime is disposed.`);let r=e.sessionManager.getSessionId();if(y&&b!==r){let e=y;y=void 0,b=void 0,C(()=>e.shutdown())}return y||=(b=r,i({serviceName:u.serviceName??`pi`,packageName:t,cwd:e.cwd,env:{...f,PI_SESSION_ID:r},allowFileFallback:u.allowFileFallback,telemetryFactory:u.telemetryFactory,warn:u.onDiagnostic,enableLogs:!0,enableTraces:f.AGENT_OTEL_TRACES===void 0||n(f.AGENT_OTEL_TRACES),onRecord:e=>{g&&p?.record(e.event,e.attributes)},onStatus:e=>{g&&u.onSinkStatus?.(c(e))}})),g&&u.onSinkStatus?.(c(y.status())),y},E=async e=>{let t=x;if(t){if(t.sessionManager!==e.sessionManager)throw Error(`Log readiness belongs to a stale Pi session.`);if(await t.operation,!g||t!==x)throw Error(`Log readiness belongs to a stale extension generation.`)}},D=(e,t)=>{let n=x,r=async()=>{if(n){if(!await n.operation.then(()=>!0,()=>!1)||!g||n!==x||n.sessionManager!==e.sessionManager)return}else if(!g)return;await t()};if(!d)return r();C(r)};return{events:{session_start:(e,n)=>{if(!g)return;let i=++_;m.clear(),h.clear(),v=void 0;let o=async t=>{t?.throwIfAborted(),T(n),await w(`info`,`pi.session.started`,{...a(n),"pi.session.reason":e.reason,"pi.session.previous":e.previousSessionFile!==void 0}),t?.throwIfAborted()},s=d?r(l):void 0;if(!s)return o();let c=x,u=(async()=>{c?.coordinator===s&&await c.operation.catch(()=>void 0),g&&i===_&&await s.start(t,`${n.sessionManager.getSessionId()}:${i}`,async e=>(await o(e),{value:void 0})).wait()})();u.catch(()=>void 0),x={sessionManager:n.sessionManager,coordinator:s,operation:u}},before_agent_start:(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`info`,`pi.user_prompt`,{...a(t),"pi.user_message.length":e.prompt.length,"pi.user_message.image_count":e.images?.length??0})})},agent_start:(e,t)=>{if(g)return D(t,async()=>{v=Date.now(),T(t),await w(`info`,`pi.agent.started`,a(t))})},turn_start:(e,t)=>{if(g)return h.set(e.turnIndex,Date.now()),D(t,async()=>{T(t),await w(`debug`,`pi.turn.started`,{...a(t),"pi.turn.index":e.turnIndex})})},turn_end:(t,n)=>{if(!g)return;let r=h.get(t.turnIndex);h.delete(t.turnIndex);let i=r===void 0?void 0:Date.now()-r;return D(n,async()=>{T(n);let r=o(t.message);if(await w(`info`,`pi.turn.finished`,{...a(n),"pi.turn.index":t.turnIndex,"pi.tool_result.count":t.toolResults.length,...i===void 0?{}:{"pi.turn.duration_ms":i},...r}),t.message.role===`assistant`&&e.includes(t.message.stopReason)&&await w(t.message.stopReason===`aborted`?`warn`:`error`,`pi.turn.failed`,{...a(n),"pi.turn.index":t.turnIndex,...s(t.message),...r}),r)for(let e of t.toolResults)await w(`debug`,`pi.tool_token_sample`,{...a(n),...r,"tool.name":e.toolName,token_attribution:`toolCallTurn`})})},tool_execution_start:(e,t)=>{if(g)return D(t,async()=>{m.set(e.toolCallId,Date.now()),T(t),await w(`debug`,`pi.tool_call`,{...a(t),"tool.name":e.toolName,"tool.call.id":e.toolCallId})})},tool_execution_end:(e,t)=>{if(g)return D(t,async()=>{let n=m.get(e.toolCallId);m.delete(e.toolCallId),T(t),await w(`info`,`pi.tool_result`,{...a(t),success:!e.isError,"tool.name":e.toolName,"tool.call.id":e.toolCallId,"tool.result.error":e.isError,...n===void 0?{}:{"tool.duration_ms":Date.now()-n}})})},after_provider_response:(e,t)=>{if(g)return D(t,async()=>{T(t);let n={...a(t),"http.response.status_code":e.status};e.status>=400?await w(`error`,`pi.api_error`,n):await w(`debug`,`pi.api_response`,n)})},model_select:(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`info`,`pi.model.selected`,{...a(t),"pi.model.source":e.source,"gen_ai.provider.name":e.model.provider,"gen_ai.request.model":e.model.id})})},agent_end:(e,t)=>{if(!g)return;let n=_;return D(t,async()=>{T(t),await w(`info`,`pi.agent.finished`,{...a(t),"pi.message.count":e.messages.length,...v===void 0?{}:{"pi.agent.duration_ms":Date.now()-v}}),g&&n===_&&(v=void 0,await y?.flush())})},agent_settled:(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`debug`,`pi.agent.settled`,a(t))})}},onDispose:async()=>{g=!1,_+=1,x=void 0,m.clear(),h.clear(),v=void 0;let e=y;y=void 0,b=void 0,await e?.shutdown()},waitForSession:E,async finishSession(e,t){if(!g)return;await S;let n=x;n&&await n.operation.catch(()=>void 0),g&&(n&&n!==x||y&&(T(t),await w(`info`,`pi.session.finished`,{...a(t),"pi.session.reason":e})))}}}export{l as createPiTelemetryRuntime};
2
2
  //# sourceMappingURL=piTelemetry.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"piTelemetry.mjs","names":[],"sources":["../../src/controllers/piTelemetry.ts"],"sourcesContent":["import type { PiEventHandlers } from '@agimon-ai/doompi-core/pi-extension';\nimport { type DoomReadinessCoordinator, readDoomReadinessCoordinator } from '@agimon-ai/doompi-core/readiness';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionContext, TurnEndEvent } from '@earendil-works/pi-coding-agent';\n\nimport {\n SERVICE_NAME,\n PACKAGE_NAME,\n NO_ENDPOINT,\n API_ERROR_STATUS,\n FAILURE_STOP_REASONS,\n} from '../constants/telemetry';\nimport type {\n PiTelemetryExtensionOptions,\n PiTelemetryRuntimeHandle,\n TelemetryAttributes,\n RecordLevel,\n} from '../types/piTelemetry';\nimport type { SinkStatus } from '../types/sinkStatus';\ninterface PiTelemetryRuntime extends PiTelemetryRuntimeHandle {\n events: PiEventHandlers;\n onDispose(this: void): Promise<void>;\n}\nimport { isEnabled } from '../services/telemetryEnabled';\nfunction contextAttributes(ctx: ExtensionContext): TelemetryAttributes {\n const attributes: TelemetryAttributes = {\n 'pi.mode': ctx.mode,\n 'pi.session.id': ctx.sessionManager.getSessionId(),\n };\n if (ctx.thinkingLevel) attributes['pi.thinking_level'] = ctx.thinkingLevel;\n if (ctx.model) {\n attributes['gen_ai.provider.name'] = ctx.model.provider;\n attributes['gen_ai.request.model'] = ctx.model.id;\n }\n return attributes;\n}\n\nfunction usageAttributes(message: TurnEndEvent['message']): TelemetryAttributes | undefined {\n if (message.role !== 'assistant') return undefined;\n const attributes: TelemetryAttributes = {\n 'gen_ai.response.model': message.responseModel ?? message.model,\n 'gen_ai.response.finish_reasons': message.stopReason,\n 'gen_ai.usage.input_tokens': message.usage.input,\n 'gen_ai.usage.output_tokens': message.usage.output,\n 'gen_ai.usage.cache_read_tokens': message.usage.cacheRead,\n 'gen_ai.usage.cache_write_tokens': message.usage.cacheWrite,\n 'gen_ai.usage.total_tokens': message.usage.totalTokens,\n 'gen_ai.usage.cost': message.usage.cost.total,\n };\n if (message.usage.reasoning !== undefined) attributes['gen_ai.usage.reasoning_tokens'] = message.usage.reasoning;\n return attributes;\n}\n\nfunction failureAttributes(message: TurnEndEvent['message']): TelemetryAttributes {\n if (message.role !== 'assistant') return { 'error.type': 'AgentError', outcome: 'error' };\n const aborted = message.stopReason === 'aborted';\n return {\n 'error.type': 'ProviderError',\n 'error.code': message.stopReason,\n outcome: aborted ? 'aborted' : 'error',\n };\n}\n\nfunction statusFromTelemetry(status: ReturnType<DoomTelemetry['status']>): SinkStatus {\n return {\n service: status.serviceName,\n backend: status.backend,\n endpoint: status.endpoint ?? NO_ENDPOINT,\n endpointSource: status.endpointSource,\n traces: status.traces,\n redaction: true,\n fileFallback: status.fileFallback,\n };\n}\n\nexport function createPiTelemetryRuntime(\n cordis: Context,\n options: PiTelemetryExtensionOptions = {},\n splitReadiness = false,\n): PiTelemetryRuntime {\n const env = options.env ?? process.env;\n const metrics = options.metrics;\n const toolStartedAt = new Map<string, number>();\n let active = true;\n let sessionGeneration = 0;\n let agentStartedAt: number | undefined;\n let telemetry: DoomTelemetry | undefined;\n let telemetrySessionId: string | undefined;\n let sessionReadiness:\n | {\n readonly sessionManager: object;\n readonly coordinator: DoomReadinessCoordinator;\n readonly operation: Promise<void>;\n }\n | undefined;\n let backgroundQueue = Promise.resolve();\n\n const enqueue = (operation: () => Promise<void>): void => {\n const scheduled = backgroundQueue.then(operation);\n // Telemetry is advisory and the coordinator owns startup failure notification.\n backgroundQueue = scheduled.catch(() => undefined);\n };\n\n const emit = async (level: RecordLevel, name: string, attributes: TelemetryAttributes): Promise<void> => {\n if (!active || !telemetry) return;\n if (level === 'debug') await telemetry.recordDebug(name, attributes);\n else if (level === 'warn') await telemetry.recordWarning(name, undefined, attributes);\n else if (level === 'error') await telemetry.recordError(name, undefined, attributes);\n else await telemetry.recordEvent(name, attributes);\n };\n\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n if (!active) throw new Error('Log telemetry runtime is disposed.');\n // The sink groups records by the x-agent-session-id header, which the SDK derives from\n // the environment once per handle. Nothing stamps PI_SESSION_ID for a top-level session\n // (only doompi-workflow does, for spawned children), so without this overlay every record\n // lands in a single unattributed group and per-agent cost cannot be queried.\n const sessionId = ctx.sessionManager.getSessionId();\n // The handle caches those headers, so a second session in the same process would keep\n // reporting under the first session's id. Rebuild when the session changes.\n if (telemetry && telemetrySessionId !== sessionId) {\n const staleTelemetry = telemetry;\n telemetry = undefined;\n telemetrySessionId = undefined;\n enqueue(() => staleTelemetry.shutdown());\n }\n if (!telemetry) {\n telemetrySessionId = sessionId;\n telemetry = createDoomTelemetry({\n serviceName: options.serviceName ?? SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: ctx.cwd,\n env: { ...env, PI_SESSION_ID: sessionId },\n allowFileFallback: options.allowFileFallback,\n telemetryFactory: options.telemetryFactory,\n warn: options.onDiagnostic,\n enableLogs: true,\n enableTraces: env.AGENT_OTEL_TRACES === undefined || isEnabled(env.AGENT_OTEL_TRACES),\n onRecord: (record) => {\n if (active) metrics?.record(record.event, record.attributes);\n },\n onStatus: (status) => {\n if (active) options.onSinkStatus?.(statusFromTelemetry(status));\n },\n });\n }\n if (active) options.onSinkStatus?.(statusFromTelemetry(telemetry.status()));\n return telemetry;\n };\n\n const waitForSession = async (ctx: ExtensionContext): Promise<void> => {\n const current = sessionReadiness;\n if (!current) return;\n if (current.sessionManager !== ctx.sessionManager) {\n throw new Error('Log readiness belongs to a stale Pi session.');\n }\n await current.operation;\n if (!active || current !== sessionReadiness) {\n throw new Error('Log readiness belongs to a stale extension generation.');\n }\n };\n\n const enqueueForSession = (ctx: ExtensionContext, operation: () => Promise<void>): void | Promise<void> => {\n const expected = sessionReadiness;\n const run = async (): Promise<void> => {\n if (expected) {\n const ready = await expected.operation.then(\n () => true,\n () => false,\n );\n if (!ready || !active || expected !== sessionReadiness || expected.sessionManager !== ctx.sessionManager) {\n return;\n }\n } else if (!active) {\n return;\n }\n await operation();\n };\n if (!splitReadiness) return run();\n enqueue(run);\n return undefined;\n };\n\n return {\n events: {\n session_start: (event, ctx) => {\n if (!active) return undefined;\n const ownGeneration = ++sessionGeneration;\n toolStartedAt.clear();\n agentStartedAt = undefined;\n const initialize = async (signal?: AbortSignal): Promise<void> => {\n signal?.throwIfAborted();\n getTelemetry(ctx);\n await emit('info', 'pi.session.started', {\n ...contextAttributes(ctx),\n 'pi.session.reason': event.reason,\n 'pi.session.previous': event.previousSessionFile !== undefined,\n });\n signal?.throwIfAborted();\n };\n const coordinator = splitReadiness ? readDoomReadinessCoordinator(cordis) : undefined;\n if (!coordinator) return initialize();\n\n const previous = sessionReadiness;\n const operation = (async (): Promise<void> => {\n if (previous?.coordinator === coordinator) await previous.operation.catch(() => undefined);\n if (!active || ownGeneration !== sessionGeneration) return;\n const handle = coordinator.start(\n PACKAGE_NAME,\n `${ctx.sessionManager.getSessionId()}:${ownGeneration}`,\n async (signal) => {\n await initialize(signal);\n return { value: undefined };\n },\n );\n await handle.wait();\n })();\n // Config's coordinator owns the single user-facing failure notification.\n void operation.catch(() => undefined);\n sessionReadiness = { sessionManager: ctx.sessionManager, coordinator, operation };\n return undefined;\n },\n\n before_agent_start: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.user_prompt', {\n ...contextAttributes(ctx),\n 'pi.user_message.length': event.prompt.length,\n 'pi.user_message.image_count': event.images?.length ?? 0,\n });\n });\n },\n\n agent_start: (_event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n agentStartedAt = Date.now();\n getTelemetry(ctx);\n await emit('info', 'pi.agent.started', contextAttributes(ctx));\n });\n },\n\n turn_start: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('debug', 'pi.turn.started', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n });\n });\n },\n\n turn_end: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n const usage = usageAttributes(event.message);\n await emit('info', 'pi.turn.finished', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n 'pi.tool_result.count': event.toolResults.length,\n ...usage,\n });\n if (event.message.role === 'assistant' && FAILURE_STOP_REASONS.includes(event.message.stopReason)) {\n await emit(event.message.stopReason === 'aborted' ? 'warn' : 'error', 'pi.turn.failed', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n ...failureAttributes(event.message),\n ...usage,\n });\n }\n if (!usage) return;\n for (const result of event.toolResults) {\n await emit('debug', 'pi.tool_token_sample', {\n ...contextAttributes(ctx),\n ...usage,\n 'tool.name': result.toolName,\n token_attribution: 'toolCallTurn',\n });\n }\n });\n },\n\n tool_execution_start: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n toolStartedAt.set(event.toolCallId, Date.now());\n getTelemetry(ctx);\n await emit('debug', 'pi.tool_call', {\n ...contextAttributes(ctx),\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n });\n });\n },\n\n tool_execution_end: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n const startedAt = toolStartedAt.get(event.toolCallId);\n toolStartedAt.delete(event.toolCallId);\n getTelemetry(ctx);\n await emit('info', 'pi.tool_result', {\n ...contextAttributes(ctx),\n success: !event.isError,\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n 'tool.result.error': event.isError,\n ...(startedAt === undefined ? {} : { 'tool.duration_ms': Date.now() - startedAt }),\n });\n });\n },\n\n after_provider_response: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n const attributes = {\n ...contextAttributes(ctx),\n 'http.response.status_code': event.status,\n };\n if (event.status >= API_ERROR_STATUS) await emit('error', 'pi.api_error', attributes);\n else await emit('debug', 'pi.api_response', attributes);\n });\n },\n\n model_select: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.model.selected', {\n ...contextAttributes(ctx),\n 'pi.model.source': event.source,\n 'gen_ai.provider.name': event.model.provider,\n 'gen_ai.request.model': event.model.id,\n });\n });\n },\n\n agent_end: (event, ctx) => {\n if (!active) return;\n const generation = sessionGeneration;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.agent.finished', {\n ...contextAttributes(ctx),\n 'pi.message.count': event.messages.length,\n ...(agentStartedAt === undefined ? {} : { 'pi.agent.duration_ms': Date.now() - agentStartedAt }),\n });\n if (!active || generation !== sessionGeneration) return;\n agentStartedAt = undefined;\n await telemetry?.flush();\n });\n },\n\n agent_settled: (_event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('debug', 'pi.agent.settled', contextAttributes(ctx));\n });\n },\n },\n onDispose: async () => {\n active = false;\n sessionGeneration += 1;\n sessionReadiness = undefined;\n toolStartedAt.clear();\n agentStartedAt = undefined;\n const ownedTelemetry = telemetry;\n telemetry = undefined;\n telemetrySessionId = undefined;\n await ownedTelemetry?.shutdown();\n },\n waitForSession,\n async finishSession(reason: string, ctx: ExtensionContext) {\n if (!active) return;\n await backgroundQueue;\n const current = sessionReadiness;\n if (current) await current.operation.catch(() => undefined);\n if (!active) return;\n if (current && current !== sessionReadiness) return;\n if (!telemetry) return;\n getTelemetry(ctx);\n await emit('info', 'pi.session.finished', {\n ...contextAttributes(ctx),\n 'pi.session.reason': reason,\n });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"piTelemetry.mjs","names":[],"sources":["../../src/controllers/piTelemetry.ts"],"sourcesContent":["import type { PiEventHandlers } from '@agimon-ai/doompi-core/pi-extension';\nimport { type DoomReadinessCoordinator, readDoomReadinessCoordinator } from '@agimon-ai/doompi-core/readiness';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionContext, TurnEndEvent } from '@earendil-works/pi-coding-agent';\n\nimport {\n SERVICE_NAME,\n PACKAGE_NAME,\n NO_ENDPOINT,\n API_ERROR_STATUS,\n FAILURE_STOP_REASONS,\n} from '../constants/telemetry';\nimport type {\n PiTelemetryExtensionOptions,\n PiTelemetryRuntimeHandle,\n TelemetryAttributes,\n RecordLevel,\n} from '../types/piTelemetry';\nimport type { SinkStatus } from '../types/sinkStatus';\ninterface PiTelemetryRuntime extends PiTelemetryRuntimeHandle {\n events: PiEventHandlers;\n onDispose(this: void): Promise<void>;\n}\nimport { isEnabled } from '../services/telemetryEnabled';\nfunction contextAttributes(ctx: ExtensionContext): TelemetryAttributes {\n const attributes: TelemetryAttributes = {\n 'pi.mode': ctx.mode,\n 'pi.session.id': ctx.sessionManager.getSessionId(),\n };\n if (ctx.thinkingLevel) attributes['pi.thinking_level'] = ctx.thinkingLevel;\n if (ctx.model) {\n attributes['gen_ai.provider.name'] = ctx.model.provider;\n attributes['gen_ai.request.model'] = ctx.model.id;\n }\n return attributes;\n}\n\nfunction usageAttributes(message: TurnEndEvent['message']): TelemetryAttributes | undefined {\n if (message.role !== 'assistant') return undefined;\n const attributes: TelemetryAttributes = {\n 'gen_ai.response.model': message.responseModel ?? message.model,\n 'gen_ai.response.finish_reasons': message.stopReason,\n 'gen_ai.usage.input_tokens': message.usage.input,\n 'gen_ai.usage.output_tokens': message.usage.output,\n 'gen_ai.usage.cache_read_tokens': message.usage.cacheRead,\n 'gen_ai.usage.cache_write_tokens': message.usage.cacheWrite,\n 'gen_ai.usage.total_tokens': message.usage.totalTokens,\n 'gen_ai.usage.cost': message.usage.cost.total,\n };\n if (message.usage.reasoning !== undefined) attributes['gen_ai.usage.reasoning_tokens'] = message.usage.reasoning;\n return attributes;\n}\n\nfunction failureAttributes(message: TurnEndEvent['message']): TelemetryAttributes {\n if (message.role !== 'assistant') return { 'error.type': 'AgentError', outcome: 'error' };\n const aborted = message.stopReason === 'aborted';\n return {\n 'error.type': 'ProviderError',\n 'error.code': message.stopReason,\n outcome: aborted ? 'aborted' : 'error',\n };\n}\n\nfunction statusFromTelemetry(status: ReturnType<DoomTelemetry['status']>): SinkStatus {\n return {\n service: status.serviceName,\n backend: status.backend,\n endpoint: status.endpoint ?? NO_ENDPOINT,\n endpointSource: status.endpointSource,\n traces: status.traces,\n redaction: true,\n fileFallback: status.fileFallback,\n };\n}\n\nexport function createPiTelemetryRuntime(\n cordis: Context,\n options: PiTelemetryExtensionOptions = {},\n splitReadiness = false,\n): PiTelemetryRuntime {\n const env = options.env ?? process.env;\n const metrics = options.metrics;\n const toolStartedAt = new Map<string, number>();\n const turnStartedAt = new Map<number, number>();\n let active = true;\n let sessionGeneration = 0;\n let agentStartedAt: number | undefined;\n let telemetry: DoomTelemetry | undefined;\n let telemetrySessionId: string | undefined;\n let sessionReadiness:\n | {\n readonly sessionManager: object;\n readonly coordinator: DoomReadinessCoordinator;\n readonly operation: Promise<void>;\n }\n | undefined;\n let backgroundQueue = Promise.resolve();\n\n const enqueue = (operation: () => Promise<void>): void => {\n const scheduled = backgroundQueue.then(operation);\n // Telemetry is advisory and the coordinator owns startup failure notification.\n backgroundQueue = scheduled.catch(() => undefined);\n };\n\n const emit = async (level: RecordLevel, name: string, attributes: TelemetryAttributes): Promise<void> => {\n if (!active || !telemetry) return;\n if (level === 'debug') await telemetry.recordDebug(name, attributes);\n else if (level === 'warn') await telemetry.recordWarning(name, undefined, attributes);\n else if (level === 'error') await telemetry.recordError(name, undefined, attributes);\n else await telemetry.recordEvent(name, attributes);\n };\n\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n if (!active) throw new Error('Log telemetry runtime is disposed.');\n // The sink groups records by the x-agent-session-id header, which the SDK derives from\n // the environment once per handle. Nothing stamps PI_SESSION_ID for a top-level session\n // (only doompi-workflow does, for spawned children), so without this overlay every record\n // lands in a single unattributed group and per-agent cost cannot be queried.\n const sessionId = ctx.sessionManager.getSessionId();\n // The handle caches those headers, so a second session in the same process would keep\n // reporting under the first session's id. Rebuild when the session changes.\n if (telemetry && telemetrySessionId !== sessionId) {\n const staleTelemetry = telemetry;\n telemetry = undefined;\n telemetrySessionId = undefined;\n enqueue(() => staleTelemetry.shutdown());\n }\n if (!telemetry) {\n telemetrySessionId = sessionId;\n telemetry = createDoomTelemetry({\n serviceName: options.serviceName ?? SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: ctx.cwd,\n env: { ...env, PI_SESSION_ID: sessionId },\n allowFileFallback: options.allowFileFallback,\n telemetryFactory: options.telemetryFactory,\n warn: options.onDiagnostic,\n enableLogs: true,\n enableTraces: env.AGENT_OTEL_TRACES === undefined || isEnabled(env.AGENT_OTEL_TRACES),\n onRecord: (record) => {\n if (active) metrics?.record(record.event, record.attributes);\n },\n onStatus: (status) => {\n if (active) options.onSinkStatus?.(statusFromTelemetry(status));\n },\n });\n }\n if (active) options.onSinkStatus?.(statusFromTelemetry(telemetry.status()));\n return telemetry;\n };\n\n const waitForSession = async (ctx: ExtensionContext): Promise<void> => {\n const current = sessionReadiness;\n if (!current) return;\n if (current.sessionManager !== ctx.sessionManager) {\n throw new Error('Log readiness belongs to a stale Pi session.');\n }\n await current.operation;\n if (!active || current !== sessionReadiness) {\n throw new Error('Log readiness belongs to a stale extension generation.');\n }\n };\n\n const enqueueForSession = (ctx: ExtensionContext, operation: () => Promise<void>): void | Promise<void> => {\n const expected = sessionReadiness;\n const run = async (): Promise<void> => {\n if (expected) {\n const ready = await expected.operation.then(\n () => true,\n () => false,\n );\n if (!ready || !active || expected !== sessionReadiness || expected.sessionManager !== ctx.sessionManager) {\n return;\n }\n } else if (!active) {\n return;\n }\n await operation();\n };\n if (!splitReadiness) return run();\n enqueue(run);\n return undefined;\n };\n\n return {\n events: {\n session_start: (event, ctx) => {\n if (!active) return undefined;\n const ownGeneration = ++sessionGeneration;\n toolStartedAt.clear();\n turnStartedAt.clear();\n agentStartedAt = undefined;\n const initialize = async (signal?: AbortSignal): Promise<void> => {\n signal?.throwIfAborted();\n getTelemetry(ctx);\n await emit('info', 'pi.session.started', {\n ...contextAttributes(ctx),\n 'pi.session.reason': event.reason,\n 'pi.session.previous': event.previousSessionFile !== undefined,\n });\n signal?.throwIfAborted();\n };\n const coordinator = splitReadiness ? readDoomReadinessCoordinator(cordis) : undefined;\n if (!coordinator) return initialize();\n\n const previous = sessionReadiness;\n const operation = (async (): Promise<void> => {\n if (previous?.coordinator === coordinator) await previous.operation.catch(() => undefined);\n if (!active || ownGeneration !== sessionGeneration) return;\n const handle = coordinator.start(\n PACKAGE_NAME,\n `${ctx.sessionManager.getSessionId()}:${ownGeneration}`,\n async (signal) => {\n await initialize(signal);\n return { value: undefined };\n },\n );\n await handle.wait();\n })();\n // Config's coordinator owns the single user-facing failure notification.\n void operation.catch(() => undefined);\n sessionReadiness = { sessionManager: ctx.sessionManager, coordinator, operation };\n return undefined;\n },\n\n before_agent_start: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.user_prompt', {\n ...contextAttributes(ctx),\n 'pi.user_message.length': event.prompt.length,\n 'pi.user_message.image_count': event.images?.length ?? 0,\n });\n });\n },\n\n agent_start: (_event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n agentStartedAt = Date.now();\n getTelemetry(ctx);\n await emit('info', 'pi.agent.started', contextAttributes(ctx));\n });\n },\n\n turn_start: (event, ctx) => {\n if (!active) return;\n // Stamped as the event fires, not inside the queue, so a backed-up queue\n // cannot inflate the turn duration this records.\n turnStartedAt.set(event.turnIndex, Date.now());\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('debug', 'pi.turn.started', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n });\n });\n },\n\n turn_end: (event, ctx) => {\n if (!active) return;\n const turnStart = turnStartedAt.get(event.turnIndex);\n turnStartedAt.delete(event.turnIndex);\n const turnDurationMs = turnStart === undefined ? undefined : Date.now() - turnStart;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n const usage = usageAttributes(event.message);\n await emit('info', 'pi.turn.finished', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n 'pi.tool_result.count': event.toolResults.length,\n ...(turnDurationMs === undefined ? {} : { 'pi.turn.duration_ms': turnDurationMs }),\n ...usage,\n });\n if (event.message.role === 'assistant' && FAILURE_STOP_REASONS.includes(event.message.stopReason)) {\n await emit(event.message.stopReason === 'aborted' ? 'warn' : 'error', 'pi.turn.failed', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n ...failureAttributes(event.message),\n ...usage,\n });\n }\n if (!usage) return;\n for (const result of event.toolResults) {\n await emit('debug', 'pi.tool_token_sample', {\n ...contextAttributes(ctx),\n ...usage,\n 'tool.name': result.toolName,\n token_attribution: 'toolCallTurn',\n });\n }\n });\n },\n\n tool_execution_start: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n toolStartedAt.set(event.toolCallId, Date.now());\n getTelemetry(ctx);\n await emit('debug', 'pi.tool_call', {\n ...contextAttributes(ctx),\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n });\n });\n },\n\n tool_execution_end: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n const startedAt = toolStartedAt.get(event.toolCallId);\n toolStartedAt.delete(event.toolCallId);\n getTelemetry(ctx);\n await emit('info', 'pi.tool_result', {\n ...contextAttributes(ctx),\n success: !event.isError,\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n 'tool.result.error': event.isError,\n ...(startedAt === undefined ? {} : { 'tool.duration_ms': Date.now() - startedAt }),\n });\n });\n },\n\n after_provider_response: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n const attributes = {\n ...contextAttributes(ctx),\n 'http.response.status_code': event.status,\n };\n if (event.status >= API_ERROR_STATUS) await emit('error', 'pi.api_error', attributes);\n else await emit('debug', 'pi.api_response', attributes);\n });\n },\n\n model_select: (event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.model.selected', {\n ...contextAttributes(ctx),\n 'pi.model.source': event.source,\n 'gen_ai.provider.name': event.model.provider,\n 'gen_ai.request.model': event.model.id,\n });\n });\n },\n\n agent_end: (event, ctx) => {\n if (!active) return;\n const generation = sessionGeneration;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('info', 'pi.agent.finished', {\n ...contextAttributes(ctx),\n 'pi.message.count': event.messages.length,\n ...(agentStartedAt === undefined ? {} : { 'pi.agent.duration_ms': Date.now() - agentStartedAt }),\n });\n if (!active || generation !== sessionGeneration) return;\n agentStartedAt = undefined;\n await telemetry?.flush();\n });\n },\n\n agent_settled: (_event, ctx) => {\n if (!active) return;\n return enqueueForSession(ctx, async () => {\n getTelemetry(ctx);\n await emit('debug', 'pi.agent.settled', contextAttributes(ctx));\n });\n },\n },\n onDispose: async () => {\n active = false;\n sessionGeneration += 1;\n sessionReadiness = undefined;\n toolStartedAt.clear();\n turnStartedAt.clear();\n agentStartedAt = undefined;\n const ownedTelemetry = telemetry;\n telemetry = undefined;\n telemetrySessionId = undefined;\n await ownedTelemetry?.shutdown();\n },\n waitForSession,\n async finishSession(reason: string, ctx: ExtensionContext) {\n if (!active) return;\n await backgroundQueue;\n const current = sessionReadiness;\n if (current) await current.operation.catch(() => undefined);\n if (!active) return;\n if (current && current !== sessionReadiness) return;\n if (!telemetry) return;\n getTelemetry(ctx);\n await emit('info', 'pi.session.finished', {\n ...contextAttributes(ctx),\n 'pi.session.reason': reason,\n });\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"webMetrics.cjs","names":[],"sources":["../../src/types/webMetrics.ts"],"sourcesContent":["/**\n * The metrics API, shared by this package's hub-scoped route and its cockpit\n * plugin. The two halves run in different processes, so the wire vocabulary is\n * declared here: `src/web` may reach `src/types` and nothing else on the node\n * side.\n *\n * The shapes are narrower than log-sink-mcp's own report, which carries filter\n * echoes, span counts and token distributions the page never draws.\n * Re-exporting the sink's types would also tie the browser bundle to a node\n * package. What crosses the wire is what a chart reads.\n *\n * Two things the sink does not have, so this contract does not pretend to:\n * money, and per-tool failures. The sink records tokens only, and its tool rows\n * carry an invocation count with no error field.\n */\n\n/** The segment this package's API is mounted under, below /api/plugin. */\nexport const LOG_API_BASE_PATH = 'log';\n\n/**\n * The dimensions the page offers.\n *\n * log-sink-mcp also groups by 'workflow-run', 'workflow-name', 'job' and\n * 'step'. Those are omitted on purpose: no DoomPi package emits the matching\n * attributes today, so offering them would draw an empty chart and read as a\n * bug rather than as a gap.\n */\nexport const METRICS_DIMENSIONS = ['session', 'agent', 'model', 'provider'] as const;\nexport type MetricsDimension = (typeof METRICS_DIMENSIONS)[number];\n\nexport const METRICS_PERIODS = ['day', 'week', 'month', 'all'] as const;\nexport type MetricsPeriod = (typeof METRICS_PERIODS)[number];\n\nexport const METRICS_QUERY_PARAMS = {\n dimension: 'dimension',\n period: 'period',\n /** Narrows the whole report to one value of the current dimension. */\n focus: 'focus',\n} as const;\n\n/** How many groups and tools one response carries; the page ranks, it does not page. */\nexport const METRICS_GROUP_LIMIT = 20;\n\n/** One point on the token timeline. */\nexport interface MetricsBucket {\n /**\n * The sink's own local-time label ('YYYY-MM-DD', or 'YYYY-MM-DD HH:00' for an\n * hour bucket). Carried rather than derived: bucket boundaries are aligned to\n * local time, so slicing the ISO instant renders the wrong day east of\n * Greenwich.\n */\n label: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n}\n\n/** One row of the selected dimension. */\nexport interface MetricsGroup {\n /**\n * The group's identity as the sink reports it. For the session dimension\n * this is an opaque hash, never a session the cockpit can open, because\n * doompi-telemetry hashes identifier-shaped attributes before export.\n */\n key: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Problems the sink detected for this group; 0 when it saw none. */\n issueCount: number;\n failed: boolean;\n}\n\n/**\n * One tool's activity.\n *\n * `calls` is exact. `p90TotalTokens` is not this tool's consumption: the sink\n * attributes the whole turn's tokens to every tool that ran in that turn, so\n * the figure ranks tools and does not measure them. The page must say so\n * wherever it draws this field.\n *\n * There is no failure count here because the sink's tool rows do not carry\n * one. Per-tool failures live in its separate agent-issues report, which the\n * running daemon does not expose over HTTP.\n */\nexport interface MetricsTool {\n name: string;\n calls: number;\n p90TotalTokens: number;\n}\n\n/** Which transport answered, so the page can say where its numbers came from. */\nexport type MetricsTransport = 'http' | 'cli';\n\nexport interface MetricsTotals {\n /**\n * The providers' own reported total. It is not inputTokens + outputTokens:\n * on a cached agent workload it is dominated by cache traffic, and those two\n * fields are a fraction of a percent of it. The page must never present the\n * three as a breakdown.\n */\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Cache reads, usually the largest real component of totalTokens. */\n cachedTokens: number;\n reasoningTokens: number;\n groupCount: number;\n /** Groups the sink flagged as having failed, out of groupCount. */\n failedGroups: number;\n issueCount: number;\n}\n\nexport interface MetricsReport {\n generatedAt: string;\n dimension: MetricsDimension;\n period: MetricsPeriod;\n /**\n * The width of one timeline bucket, chosen by the sink from the period. A\n * long period over a short history collapses to a single bucket, so the page\n * says which unit it is drawing rather than implying a series it does not\n * have.\n */\n bucketUnit: string;\n /**\n * The dimension value this report is narrowed to, echoed back from the sink\n * rather than from the request. A daemon older than the filter ignores it\n * and returns everything, so trusting the request would label unfiltered\n * numbers as filtered; absence here means the drill-down did not happen.\n */\n focus?: string;\n /** Undefined when the source could not say which transport answered. */\n transport?: MetricsTransport;\n totals: MetricsTotals;\n timeline: MetricsBucket[];\n groups: MetricsGroup[];\n tools: MetricsTool[];\n}\n\n/**\n * Why the page has nothing to draw.\n *\n * The sink is a separate daemon that may be missing or simply empty, and those\n * are different things to tell a reader. Neither is an error: the metrics\n * source already treats a rejecting daemon as unavailable rather than fatal.\n *\n * 'no-api' is the third case and belongs to the cockpit rather than the sink.\n * A hub serving a bundle without package APIs mounted answers this route with\n * the SPA shell, and a 200 of HTML must read as a feature that is not\n * installed, not as a corrupt response.\n */\nexport type MetricsUnavailableReason = 'no-sink' | 'no-data' | 'no-api';\n\nexport interface MetricsUnavailable {\n unavailable: MetricsUnavailableReason;\n detail: string;\n}\n\nexport type MetricsResponse = MetricsReport | MetricsUnavailable;\n\nexport function isMetricsUnavailable(response: MetricsResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\nexport function isMetricsDimension(value: string): value is MetricsDimension {\n return (METRICS_DIMENSIONS as readonly string[]).includes(value);\n}\n\nexport function isMetricsPeriod(value: string): value is MetricsPeriod {\n return (METRICS_PERIODS as readonly string[]).includes(value);\n}\n\n/** One tool's failure count, paired with its call count from the metrics report. */\nexport interface IssueToolRow {\n name: string;\n failures: number;\n}\n\n/**\n * One recurring problem, as the sink fingerprints it.\n *\n * `occurrenceCount` is how many times it happened, which is the number a\n * reader acts on. `detail` is the actionable line; `message` is often only a\n * record name such as 'pi.tool_result'.\n */\nexport interface IssueSample {\n fingerprint: string;\n occurrenceCount: number;\n category: string;\n timestamp: string;\n level: string;\n message: string;\n detail: string;\n tool: string | null;\n errorType: string | null;\n agentName: string | null;\n model: string | null;\n statusCode: string | null;\n}\n\n/**\n * The detail behind the issue count.\n *\n * Fetched separately from the report because the running sink exposes no\n * issues route over HTTP, so this costs a subprocess. The page asks for it\n * when a reader opens the section, not with every refresh.\n */\nexport interface IssuesView {\n totalIssues: number;\n uniqueIncidents: number;\n byCategory: Record<string, number>;\n byTool: Record<string, number>;\n byErrorType: Record<string, number>;\n samples: IssueSample[];\n}\n\nexport type IssuesResponse = IssuesView | MetricsUnavailable;\n\nexport function isIssuesUnavailable(response: IssuesResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\n/** How many incidents the issues section ranks. */\nexport const ISSUE_SAMPLE_LIMIT = 50;\n\n/** How many tool rows the report ranks, so a failing tool has a denominator. */\nexport const METRICS_TOOL_LIMIT = 15;\n\n/** The absolute URL the page reads the issue detail from. */\nexport function issuesUrl(focus?: string): string {\n const search = new URLSearchParams();\n if (focus !== undefined && focus !== '') search.set(METRICS_QUERY_PARAMS.focus, focus);\n const query = search.toString();\n return `/api/global/plugin/${LOG_API_BASE_PATH}/issues${query === '' ? '' : `?${query}`}`;\n}\n\n/** The absolute URL the page reads one report from; `focus` narrows it to one group. */\nexport function metricsUrl(dimension: MetricsDimension, period: MetricsPeriod, focus?: string): string {\n const search = new URLSearchParams({\n [METRICS_QUERY_PARAMS.dimension]: dimension,\n [METRICS_QUERY_PARAMS.period]: period,\n });\n if (focus !== undefined && focus !== '') search.set(METRICS_QUERY_PARAMS.focus, focus);\n return `/api/global/plugin/${LOG_API_BASE_PATH}/metrics?${search.toString()}`;\n}\n"],"mappings":"AA2BA,MAAa,EAAqB,CAAC,UAAW,QAAS,QAAS,UAAU,EAG7D,EAAkB,CAAC,MAAO,OAAQ,QAAS,KAAK,EAGhD,EAAuB,CAClC,UAAW,YACX,OAAQ,SAER,MAAO,OACT,EA8HA,SAAgB,EAAmB,EAA0C,CAC3E,OAAQ,EAAyC,SAAS,CAAK,CACjE,CAEA,SAAgB,EAAgB,EAAuC,CACrE,OAAQ,EAAsC,SAAS,CAAK,CAC9D"}
1
+ {"version":3,"file":"webMetrics.cjs","names":[],"sources":["../../src/types/webMetrics.ts"],"sourcesContent":["/**\n * The metrics API, shared by this package's hub-scoped route and its cockpit\n * plugin. The two halves run in different processes, so the wire vocabulary is\n * declared here: `src/web` may reach `src/types` and nothing else on the node\n * side.\n *\n * The shapes are narrower than log-sink-mcp's own report, which carries filter\n * echoes, span counts and token distributions the page never draws.\n * Re-exporting the sink's types would also tie the browser bundle to a node\n * package. What crosses the wire is what a chart reads.\n *\n * Two things the sink does not have, so this contract does not pretend to:\n * money, and per-tool failures. The sink records tokens only, and its tool rows\n * carry an invocation count with no error field.\n */\n\n/** The segment this package's API is mounted under, below /api/plugin. */\nexport const LOG_API_BASE_PATH = 'log';\n\n/**\n * The dimensions the page offers.\n *\n * log-sink-mcp also groups by 'workflow-run', 'workflow-name', 'job' and\n * 'step'. Those are omitted on purpose: no DoomPi package emits the matching\n * attributes today, so offering them would draw an empty chart and read as a\n * bug rather than as a gap.\n */\nexport const METRICS_DIMENSIONS = ['session', 'agent', 'model', 'provider'] as const;\nexport type MetricsDimension = (typeof METRICS_DIMENSIONS)[number];\n\nexport const METRICS_PERIODS = ['day', 'week', 'month', 'all'] as const;\nexport type MetricsPeriod = (typeof METRICS_PERIODS)[number];\n\nexport const METRICS_QUERY_PARAMS = {\n dimension: 'dimension',\n period: 'period',\n /** Narrows the whole report to one value of the current dimension. */\n focus: 'focus',\n} as const;\n\n/** How many groups and tools one response carries; the page ranks, it does not page. */\nexport const METRICS_GROUP_LIMIT = 20;\n\n/** One point on the token timeline. */\nexport interface MetricsBucket {\n /**\n * The sink's own local-time label ('YYYY-MM-DD', or 'YYYY-MM-DD HH:00' for an\n * hour bucket). Carried rather than derived: bucket boundaries are aligned to\n * local time, so slicing the ISO instant renders the wrong day east of\n * Greenwich.\n */\n label: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n}\n\n/** One row of the selected dimension. */\nexport interface MetricsGroup {\n /**\n * The group's identity as the sink reports it. For the session dimension\n * this is an opaque hash, never a session the cockpit can open, because\n * doompi-telemetry hashes identifier-shaped attributes before export.\n */\n key: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Problems the sink detected for this group; 0 when it saw none. */\n issueCount: number;\n failed: boolean;\n}\n\n/**\n * One tool's activity.\n *\n * `calls` is exact. `p90TotalTokens` is not this tool's consumption: the sink\n * attributes the whole turn's tokens to every tool that ran in that turn, so\n * the figure ranks tools and does not measure them. The page must say so\n * wherever it draws this field.\n *\n * There is no failure count here because the sink's tool rows do not carry\n * one. Per-tool failures live in its separate agent-issues report, which the\n * running daemon does not expose over HTTP.\n */\nexport interface MetricsTool {\n name: string;\n calls: number;\n p90TotalTokens: number;\n}\n\n/** Which transport answered, so the page can say where its numbers came from. */\nexport type MetricsTransport = 'http' | 'cli';\n\nexport interface MetricsTotals {\n /**\n * The providers' own reported total. It is not inputTokens + outputTokens:\n * on a cached agent workload it is dominated by cache traffic, and those two\n * fields are a fraction of a percent of it. The page must never present the\n * three as a breakdown.\n */\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Cache reads, usually the largest real component of totalTokens. */\n cachedTokens: number;\n reasoningTokens: number;\n groupCount: number;\n /** Groups the sink flagged as having failed, out of groupCount. */\n failedGroups: number;\n issueCount: number;\n}\n\nexport interface MetricsReport {\n generatedAt: string;\n dimension: MetricsDimension;\n period: MetricsPeriod;\n /**\n * The width of one timeline bucket, chosen by the sink from the period. A\n * long period over a short history collapses to a single bucket, so the page\n * says which unit it is drawing rather than implying a series it does not\n * have.\n */\n bucketUnit: string;\n /**\n * The dimension value this report is narrowed to, echoed back from the sink\n * rather than from the request. A daemon older than the filter ignores it\n * and returns everything, so trusting the request would label unfiltered\n * numbers as filtered; absence here means the drill-down did not happen.\n */\n focus?: string;\n /** Undefined when the source could not say which transport answered. */\n transport?: MetricsTransport;\n totals: MetricsTotals;\n timeline: MetricsBucket[];\n groups: MetricsGroup[];\n tools: MetricsTool[];\n}\n\n/**\n * Why the page has nothing to draw.\n *\n * The sink is a separate daemon that may be missing or simply empty, and those\n * are different things to tell a reader. Neither is an error: the metrics\n * source already treats a rejecting daemon as unavailable rather than fatal.\n *\n * 'no-api' is the third case and belongs to the cockpit rather than the sink.\n * A hub serving a bundle without package APIs mounted answers this route with\n * the SPA shell, and a 200 of HTML must read as a feature that is not\n * installed, not as a corrupt response.\n */\nexport type MetricsUnavailableReason = 'no-sink' | 'no-data' | 'no-api';\n\nexport interface MetricsUnavailable {\n unavailable: MetricsUnavailableReason;\n detail: string;\n}\n\nexport type MetricsResponse = MetricsReport | MetricsUnavailable;\n\nexport function isMetricsUnavailable(response: MetricsResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\nexport function isMetricsDimension(value: string): value is MetricsDimension {\n return (METRICS_DIMENSIONS as readonly string[]).includes(value);\n}\n\nexport function isMetricsPeriod(value: string): value is MetricsPeriod {\n return (METRICS_PERIODS as readonly string[]).includes(value);\n}\n\n/** One tool's failure count, paired with its call count from the metrics report. */\nexport interface IssueToolRow {\n name: string;\n failures: number;\n}\n\n/**\n * One recurring problem, as the sink fingerprints it.\n *\n * `occurrenceCount` is how many times it happened, which is the number a\n * reader acts on. `detail` is the actionable line; `message` is often only a\n * record name such as 'pi.tool_result'.\n */\nexport interface IssueSample {\n fingerprint: string;\n occurrenceCount: number;\n category: string;\n timestamp: string;\n level: string;\n message: string;\n detail: string;\n tool: string | null;\n errorType: string | null;\n agentName: string | null;\n model: string | null;\n statusCode: string | null;\n}\n\n/**\n * The detail behind the issue count.\n *\n * Fetched separately from the report because the running sink exposes no\n * issues route over HTTP, so this costs a subprocess. The page asks for it\n * when a reader opens the section, not with every refresh.\n */\nexport interface IssuesView {\n totalIssues: number;\n uniqueIncidents: number;\n byCategory: Record<string, number>;\n byTool: Record<string, number>;\n byErrorType: Record<string, number>;\n samples: IssueSample[];\n}\n\nexport type IssuesResponse = IssuesView | MetricsUnavailable;\n\nexport function isIssuesUnavailable(response: IssuesResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\n/** How many incidents the issues section ranks. */\nexport const ISSUE_SAMPLE_LIMIT = 50;\n\n/** How many tool rows the report ranks, so a failing tool has a denominator. */\nexport const METRICS_TOOL_LIMIT = 15;\n\n/** The absolute URL the page reads the issue detail from. */\nexport function issuesUrl(focus?: string): string {\n const search = new URLSearchParams();\n if (focus !== undefined && focus !== '') search.set(METRICS_QUERY_PARAMS.focus, focus);\n const query = search.toString();\n return `/api/plugins/${LOG_API_BASE_PATH}/issues${query === '' ? '' : `?${query}`}`;\n}\n\n/** The absolute URL the page reads one report from; `focus` narrows it to one group. */\nexport function metricsUrl(dimension: MetricsDimension, period: MetricsPeriod, focus?: string): string {\n const search = new URLSearchParams({\n [METRICS_QUERY_PARAMS.dimension]: dimension,\n [METRICS_QUERY_PARAMS.period]: period,\n });\n if (focus !== undefined && focus !== '') search.set(METRICS_QUERY_PARAMS.focus, focus);\n return `/api/plugins/${LOG_API_BASE_PATH}/metrics?${search.toString()}`;\n}\n"],"mappings":"AA2BA,MAAa,EAAqB,CAAC,UAAW,QAAS,QAAS,UAAU,EAG7D,EAAkB,CAAC,MAAO,OAAQ,QAAS,KAAK,EAGhD,EAAuB,CAClC,UAAW,YACX,OAAQ,SAER,MAAO,OACT,EA8HA,SAAgB,EAAmB,EAA0C,CAC3E,OAAQ,EAAyC,SAAS,CAAK,CACjE,CAEA,SAAgB,EAAgB,EAAuC,CACrE,OAAQ,EAAsC,SAAS,CAAK,CAC9D"}
@@ -1 +1 @@
1
- {"version":3,"file":"webMetrics.mjs","names":[],"sources":["../../src/types/webMetrics.ts"],"sourcesContent":["/**\n * The metrics API, shared by this package's hub-scoped route and its cockpit\n * plugin. The two halves run in different processes, so the wire vocabulary is\n * declared here: `src/web` may reach `src/types` and nothing else on the node\n * side.\n *\n * The shapes are narrower than log-sink-mcp's own report, which carries filter\n * echoes, span counts and token distributions the page never draws.\n * Re-exporting the sink's types would also tie the browser bundle to a node\n * package. What crosses the wire is what a chart reads.\n *\n * Two things the sink does not have, so this contract does not pretend to:\n * money, and per-tool failures. The sink records tokens only, and its tool rows\n * carry an invocation count with no error field.\n */\n\n/** The segment this package's API is mounted under, below /api/plugin. */\nexport const LOG_API_BASE_PATH = 'log';\n\n/**\n * The dimensions the page offers.\n *\n * log-sink-mcp also groups by 'workflow-run', 'workflow-name', 'job' and\n * 'step'. Those are omitted on purpose: no DoomPi package emits the matching\n * attributes today, so offering them would draw an empty chart and read as a\n * bug rather than as a gap.\n */\nexport const METRICS_DIMENSIONS = ['session', 'agent', 'model', 'provider'] as const;\nexport type MetricsDimension = (typeof METRICS_DIMENSIONS)[number];\n\nexport const METRICS_PERIODS = ['day', 'week', 'month', 'all'] as const;\nexport type MetricsPeriod = (typeof METRICS_PERIODS)[number];\n\nexport const METRICS_QUERY_PARAMS = {\n dimension: 'dimension',\n period: 'period',\n /** Narrows the whole report to one value of the current dimension. */\n focus: 'focus',\n} as const;\n\n/** How many groups and tools one response carries; the page ranks, it does not page. */\nexport const METRICS_GROUP_LIMIT = 20;\n\n/** One point on the token timeline. */\nexport interface MetricsBucket {\n /**\n * The sink's own local-time label ('YYYY-MM-DD', or 'YYYY-MM-DD HH:00' for an\n * hour bucket). Carried rather than derived: bucket boundaries are aligned to\n * local time, so slicing the ISO instant renders the wrong day east of\n * Greenwich.\n */\n label: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n}\n\n/** One row of the selected dimension. */\nexport interface MetricsGroup {\n /**\n * The group's identity as the sink reports it. For the session dimension\n * this is an opaque hash, never a session the cockpit can open, because\n * doompi-telemetry hashes identifier-shaped attributes before export.\n */\n key: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Problems the sink detected for this group; 0 when it saw none. */\n issueCount: number;\n failed: boolean;\n}\n\n/**\n * One tool's activity.\n *\n * `calls` is exact. `p90TotalTokens` is not this tool's consumption: the sink\n * attributes the whole turn's tokens to every tool that ran in that turn, so\n * the figure ranks tools and does not measure them. The page must say so\n * wherever it draws this field.\n *\n * There is no failure count here because the sink's tool rows do not carry\n * one. Per-tool failures live in its separate agent-issues report, which the\n * running daemon does not expose over HTTP.\n */\nexport interface MetricsTool {\n name: string;\n calls: number;\n p90TotalTokens: number;\n}\n\n/** Which transport answered, so the page can say where its numbers came from. */\nexport type MetricsTransport = 'http' | 'cli';\n\nexport interface MetricsTotals {\n /**\n * The providers' own reported total. It is not inputTokens + outputTokens:\n * on a cached agent workload it is dominated by cache traffic, and those two\n * fields are a fraction of a percent of it. The page must never present the\n * three as a breakdown.\n */\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Cache reads, usually the largest real component of totalTokens. */\n cachedTokens: number;\n reasoningTokens: number;\n groupCount: number;\n /** Groups the sink flagged as having failed, out of groupCount. */\n failedGroups: number;\n issueCount: number;\n}\n\nexport interface MetricsReport {\n generatedAt: string;\n dimension: MetricsDimension;\n period: MetricsPeriod;\n /**\n * The width of one timeline bucket, chosen by the sink from the period. A\n * long period over a short history collapses to a single bucket, so the page\n * says which unit it is drawing rather than implying a series it does not\n * have.\n */\n bucketUnit: string;\n /**\n * The dimension value this report is narrowed to, echoed back from the sink\n * rather than from the request. A daemon older than the filter ignores it\n * and returns everything, so trusting the request would label unfiltered\n * numbers as filtered; absence here means the drill-down did not happen.\n */\n focus?: string;\n /** Undefined when the source could not say which transport answered. */\n transport?: MetricsTransport;\n totals: MetricsTotals;\n timeline: MetricsBucket[];\n groups: MetricsGroup[];\n tools: MetricsTool[];\n}\n\n/**\n * Why the page has nothing to draw.\n *\n * The sink is a separate daemon that may be missing or simply empty, and those\n * are different things to tell a reader. Neither is an error: the metrics\n * source already treats a rejecting daemon as unavailable rather than fatal.\n *\n * 'no-api' is the third case and belongs to the cockpit rather than the sink.\n * A hub serving a bundle without package APIs mounted answers this route with\n * the SPA shell, and a 200 of HTML must read as a feature that is not\n * installed, not as a corrupt response.\n */\nexport type MetricsUnavailableReason = 'no-sink' | 'no-data' | 'no-api';\n\nexport interface MetricsUnavailable {\n unavailable: MetricsUnavailableReason;\n detail: string;\n}\n\nexport type MetricsResponse = MetricsReport | MetricsUnavailable;\n\nexport function isMetricsUnavailable(response: MetricsResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\nexport function isMetricsDimension(value: string): value is MetricsDimension {\n return (METRICS_DIMENSIONS as readonly string[]).includes(value);\n}\n\nexport function isMetricsPeriod(value: string): value is MetricsPeriod {\n return (METRICS_PERIODS as readonly string[]).includes(value);\n}\n\n/** One tool's failure count, paired with its call count from the metrics report. */\nexport interface IssueToolRow {\n name: string;\n failures: number;\n}\n\n/**\n * One recurring problem, as the sink fingerprints it.\n *\n * `occurrenceCount` is how many times it happened, which is the number a\n * reader acts on. `detail` is the actionable line; `message` is often only a\n * record name such as 'pi.tool_result'.\n */\nexport interface IssueSample {\n fingerprint: string;\n occurrenceCount: number;\n category: string;\n timestamp: string;\n level: string;\n message: string;\n detail: string;\n tool: string | null;\n errorType: string | null;\n agentName: string | null;\n model: string | null;\n statusCode: string | null;\n}\n\n/**\n * The detail behind the issue count.\n *\n * Fetched separately from the report because the running sink exposes no\n * issues route over HTTP, so this costs a subprocess. The page asks for it\n * when a reader opens the section, not with every refresh.\n */\nexport interface IssuesView {\n totalIssues: number;\n uniqueIncidents: number;\n byCategory: Record<string, number>;\n byTool: Record<string, number>;\n byErrorType: Record<string, number>;\n samples: IssueSample[];\n}\n\nexport type IssuesResponse = IssuesView | MetricsUnavailable;\n\nexport function isIssuesUnavailable(response: IssuesResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\n/** How many incidents the issues section ranks. */\nexport const ISSUE_SAMPLE_LIMIT = 50;\n\n/** How many tool rows the report ranks, so a failing tool has a denominator. */\nexport const METRICS_TOOL_LIMIT = 15;\n\n/** The absolute URL the page reads the issue detail from. */\nexport function issuesUrl(focus?: string): string {\n const search = new URLSearchParams();\n if (focus !== undefined && focus !== '') search.set(METRICS_QUERY_PARAMS.focus, focus);\n const query = search.toString();\n return `/api/global/plugin/${LOG_API_BASE_PATH}/issues${query === '' ? '' : `?${query}`}`;\n}\n\n/** The absolute URL the page reads one report from; `focus` narrows it to one group. */\nexport function metricsUrl(dimension: MetricsDimension, period: MetricsPeriod, focus?: string): string {\n const search = new URLSearchParams({\n [METRICS_QUERY_PARAMS.dimension]: dimension,\n [METRICS_QUERY_PARAMS.period]: period,\n });\n if (focus !== undefined && focus !== '') search.set(METRICS_QUERY_PARAMS.focus, focus);\n return `/api/global/plugin/${LOG_API_BASE_PATH}/metrics?${search.toString()}`;\n}\n"],"mappings":"AA2BA,MAAa,EAAqB,CAAC,UAAW,QAAS,QAAS,UAAU,EAG7D,EAAkB,CAAC,MAAO,OAAQ,QAAS,KAAK,EAGhD,EAAuB,CAClC,UAAW,YACX,OAAQ,SAER,MAAO,OACT,EA8HA,SAAgB,EAAmB,EAA0C,CAC3E,OAAQ,EAAyC,SAAS,CAAK,CACjE,CAEA,SAAgB,EAAgB,EAAuC,CACrE,OAAQ,EAAsC,SAAS,CAAK,CAC9D"}
1
+ {"version":3,"file":"webMetrics.mjs","names":[],"sources":["../../src/types/webMetrics.ts"],"sourcesContent":["/**\n * The metrics API, shared by this package's hub-scoped route and its cockpit\n * plugin. The two halves run in different processes, so the wire vocabulary is\n * declared here: `src/web` may reach `src/types` and nothing else on the node\n * side.\n *\n * The shapes are narrower than log-sink-mcp's own report, which carries filter\n * echoes, span counts and token distributions the page never draws.\n * Re-exporting the sink's types would also tie the browser bundle to a node\n * package. What crosses the wire is what a chart reads.\n *\n * Two things the sink does not have, so this contract does not pretend to:\n * money, and per-tool failures. The sink records tokens only, and its tool rows\n * carry an invocation count with no error field.\n */\n\n/** The segment this package's API is mounted under, below /api/plugin. */\nexport const LOG_API_BASE_PATH = 'log';\n\n/**\n * The dimensions the page offers.\n *\n * log-sink-mcp also groups by 'workflow-run', 'workflow-name', 'job' and\n * 'step'. Those are omitted on purpose: no DoomPi package emits the matching\n * attributes today, so offering them would draw an empty chart and read as a\n * bug rather than as a gap.\n */\nexport const METRICS_DIMENSIONS = ['session', 'agent', 'model', 'provider'] as const;\nexport type MetricsDimension = (typeof METRICS_DIMENSIONS)[number];\n\nexport const METRICS_PERIODS = ['day', 'week', 'month', 'all'] as const;\nexport type MetricsPeriod = (typeof METRICS_PERIODS)[number];\n\nexport const METRICS_QUERY_PARAMS = {\n dimension: 'dimension',\n period: 'period',\n /** Narrows the whole report to one value of the current dimension. */\n focus: 'focus',\n} as const;\n\n/** How many groups and tools one response carries; the page ranks, it does not page. */\nexport const METRICS_GROUP_LIMIT = 20;\n\n/** One point on the token timeline. */\nexport interface MetricsBucket {\n /**\n * The sink's own local-time label ('YYYY-MM-DD', or 'YYYY-MM-DD HH:00' for an\n * hour bucket). Carried rather than derived: bucket boundaries are aligned to\n * local time, so slicing the ISO instant renders the wrong day east of\n * Greenwich.\n */\n label: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n}\n\n/** One row of the selected dimension. */\nexport interface MetricsGroup {\n /**\n * The group's identity as the sink reports it. For the session dimension\n * this is an opaque hash, never a session the cockpit can open, because\n * doompi-telemetry hashes identifier-shaped attributes before export.\n */\n key: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Problems the sink detected for this group; 0 when it saw none. */\n issueCount: number;\n failed: boolean;\n}\n\n/**\n * One tool's activity.\n *\n * `calls` is exact. `p90TotalTokens` is not this tool's consumption: the sink\n * attributes the whole turn's tokens to every tool that ran in that turn, so\n * the figure ranks tools and does not measure them. The page must say so\n * wherever it draws this field.\n *\n * There is no failure count here because the sink's tool rows do not carry\n * one. Per-tool failures live in its separate agent-issues report, which the\n * running daemon does not expose over HTTP.\n */\nexport interface MetricsTool {\n name: string;\n calls: number;\n p90TotalTokens: number;\n}\n\n/** Which transport answered, so the page can say where its numbers came from. */\nexport type MetricsTransport = 'http' | 'cli';\n\nexport interface MetricsTotals {\n /**\n * The providers' own reported total. It is not inputTokens + outputTokens:\n * on a cached agent workload it is dominated by cache traffic, and those two\n * fields are a fraction of a percent of it. The page must never present the\n * three as a breakdown.\n */\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Cache reads, usually the largest real component of totalTokens. */\n cachedTokens: number;\n reasoningTokens: number;\n groupCount: number;\n /** Groups the sink flagged as having failed, out of groupCount. */\n failedGroups: number;\n issueCount: number;\n}\n\nexport interface MetricsReport {\n generatedAt: string;\n dimension: MetricsDimension;\n period: MetricsPeriod;\n /**\n * The width of one timeline bucket, chosen by the sink from the period. A\n * long period over a short history collapses to a single bucket, so the page\n * says which unit it is drawing rather than implying a series it does not\n * have.\n */\n bucketUnit: string;\n /**\n * The dimension value this report is narrowed to, echoed back from the sink\n * rather than from the request. A daemon older than the filter ignores it\n * and returns everything, so trusting the request would label unfiltered\n * numbers as filtered; absence here means the drill-down did not happen.\n */\n focus?: string;\n /** Undefined when the source could not say which transport answered. */\n transport?: MetricsTransport;\n totals: MetricsTotals;\n timeline: MetricsBucket[];\n groups: MetricsGroup[];\n tools: MetricsTool[];\n}\n\n/**\n * Why the page has nothing to draw.\n *\n * The sink is a separate daemon that may be missing or simply empty, and those\n * are different things to tell a reader. Neither is an error: the metrics\n * source already treats a rejecting daemon as unavailable rather than fatal.\n *\n * 'no-api' is the third case and belongs to the cockpit rather than the sink.\n * A hub serving a bundle without package APIs mounted answers this route with\n * the SPA shell, and a 200 of HTML must read as a feature that is not\n * installed, not as a corrupt response.\n */\nexport type MetricsUnavailableReason = 'no-sink' | 'no-data' | 'no-api';\n\nexport interface MetricsUnavailable {\n unavailable: MetricsUnavailableReason;\n detail: string;\n}\n\nexport type MetricsResponse = MetricsReport | MetricsUnavailable;\n\nexport function isMetricsUnavailable(response: MetricsResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\nexport function isMetricsDimension(value: string): value is MetricsDimension {\n return (METRICS_DIMENSIONS as readonly string[]).includes(value);\n}\n\nexport function isMetricsPeriod(value: string): value is MetricsPeriod {\n return (METRICS_PERIODS as readonly string[]).includes(value);\n}\n\n/** One tool's failure count, paired with its call count from the metrics report. */\nexport interface IssueToolRow {\n name: string;\n failures: number;\n}\n\n/**\n * One recurring problem, as the sink fingerprints it.\n *\n * `occurrenceCount` is how many times it happened, which is the number a\n * reader acts on. `detail` is the actionable line; `message` is often only a\n * record name such as 'pi.tool_result'.\n */\nexport interface IssueSample {\n fingerprint: string;\n occurrenceCount: number;\n category: string;\n timestamp: string;\n level: string;\n message: string;\n detail: string;\n tool: string | null;\n errorType: string | null;\n agentName: string | null;\n model: string | null;\n statusCode: string | null;\n}\n\n/**\n * The detail behind the issue count.\n *\n * Fetched separately from the report because the running sink exposes no\n * issues route over HTTP, so this costs a subprocess. The page asks for it\n * when a reader opens the section, not with every refresh.\n */\nexport interface IssuesView {\n totalIssues: number;\n uniqueIncidents: number;\n byCategory: Record<string, number>;\n byTool: Record<string, number>;\n byErrorType: Record<string, number>;\n samples: IssueSample[];\n}\n\nexport type IssuesResponse = IssuesView | MetricsUnavailable;\n\nexport function isIssuesUnavailable(response: IssuesResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\n/** How many incidents the issues section ranks. */\nexport const ISSUE_SAMPLE_LIMIT = 50;\n\n/** How many tool rows the report ranks, so a failing tool has a denominator. */\nexport const METRICS_TOOL_LIMIT = 15;\n\n/** The absolute URL the page reads the issue detail from. */\nexport function issuesUrl(focus?: string): string {\n const search = new URLSearchParams();\n if (focus !== undefined && focus !== '') search.set(METRICS_QUERY_PARAMS.focus, focus);\n const query = search.toString();\n return `/api/plugins/${LOG_API_BASE_PATH}/issues${query === '' ? '' : `?${query}`}`;\n}\n\n/** The absolute URL the page reads one report from; `focus` narrows it to one group. */\nexport function metricsUrl(dimension: MetricsDimension, period: MetricsPeriod, focus?: string): string {\n const search = new URLSearchParams({\n [METRICS_QUERY_PARAMS.dimension]: dimension,\n [METRICS_QUERY_PARAMS.period]: period,\n });\n if (focus !== undefined && focus !== '') search.set(METRICS_QUERY_PARAMS.focus, focus);\n return `/api/plugins/${LOG_API_BASE_PATH}/metrics?${search.toString()}`;\n}\n"],"mappings":"AA2BA,MAAa,EAAqB,CAAC,UAAW,QAAS,QAAS,UAAU,EAG7D,EAAkB,CAAC,MAAO,OAAQ,QAAS,KAAK,EAGhD,EAAuB,CAClC,UAAW,YACX,OAAQ,SAER,MAAO,OACT,EA8HA,SAAgB,EAAmB,EAA0C,CAC3E,OAAQ,EAAyC,SAAS,CAAK,CACjE,CAEA,SAAgB,EAAgB,EAAuC,CACrE,OAAQ,EAAsC,SAAS,CAAK,CAC9D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-log",
3
- "version": "0.0.1-alpha.71",
3
+ "version": "0.0.1-alpha.72",
4
4
  "description": "Pi session metrics, findings, sink status, and a Log Metrics overlay for agent observability.",
5
5
  "keywords": [
6
6
  "agent-observability",
@@ -76,29 +76,29 @@
76
76
  "registry": "https://registry.npmjs.org/"
77
77
  },
78
78
  "dependencies": {
79
- "@agimon-ai/log-sink-mcp": "0.29.19",
79
+ "@agimon-ai/doompi-core": "0.0.1-alpha.72",
80
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.69",
81
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.30",
82
+ "@agimon-ai/doompi-web-security": "0.0.1-alpha.32",
83
+ "@agimon-ai/log-sink-mcp": "0.29.25",
80
84
  "@deepseek-ai/cordis": "4.0.2",
81
- "hono": "4.13.5",
82
- "typebox": "1.3.25",
83
- "@agimon-ai/doompi-core": "0.0.1-alpha.71",
84
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.68",
85
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.29",
86
- "@agimon-ai/doompi-web-security": "0.0.1-alpha.31"
85
+ "hono": "4.13.7",
86
+ "typebox": "1.3.30"
87
87
  },
88
88
  "devDependencies": {
89
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.72",
89
90
  "@earendil-works/pi-coding-agent": "0.85.1",
90
91
  "@earendil-works/pi-tui": "0.85.1",
91
92
  "@tanstack/react-store": "0.11.1",
92
93
  "@tanstack/store": "0.11.1",
93
- "@types/node": "26.4.1",
94
- "@types/react": "19.2.18",
94
+ "@types/node": "26.5.1",
95
+ "@types/react": "19.3.0",
95
96
  "@vitest/coverage-v8": "5.0.0",
96
- "react": "19.2.8",
97
- "react-dom": "19.2.8",
97
+ "react": "19.3.0",
98
+ "react-dom": "19.3.0",
98
99
  "tsdown": "0.23.0",
99
100
  "typescript": "7.0.2",
100
- "vitest": "5.0.0",
101
- "@agimon-ai/doompi-ui": "0.0.1-alpha.71"
101
+ "vitest": "5.0.0"
102
102
  },
103
103
  "peerDependencies": {
104
104
  "@earendil-works/pi-coding-agent": "0.85.1",
@@ -231,7 +231,7 @@ export function issuesUrl(focus?: string): string {
231
231
  const search = new URLSearchParams();
232
232
  if (focus !== undefined && focus !== '') search.set(METRICS_QUERY_PARAMS.focus, focus);
233
233
  const query = search.toString();
234
- return `/api/global/plugin/${LOG_API_BASE_PATH}/issues${query === '' ? '' : `?${query}`}`;
234
+ return `/api/plugins/${LOG_API_BASE_PATH}/issues${query === '' ? '' : `?${query}`}`;
235
235
  }
236
236
 
237
237
  /** The absolute URL the page reads one report from; `focus` narrows it to one group. */
@@ -241,5 +241,5 @@ export function metricsUrl(dimension: MetricsDimension, period: MetricsPeriod, f
241
241
  [METRICS_QUERY_PARAMS.period]: period,
242
242
  });
243
243
  if (focus !== undefined && focus !== '') search.set(METRICS_QUERY_PARAMS.focus, focus);
244
- return `/api/global/plugin/${LOG_API_BASE_PATH}/metrics?${search.toString()}`;
244
+ return `/api/plugins/${LOG_API_BASE_PATH}/metrics?${search.toString()}`;
245
245
  }