@geminixiang/mikan 1.0.0-beta.55 → 1.0.0-beta.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,26 @@ any release.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.0.0-beta.57]
13
+
14
+ ### Changed
15
+
16
+ - Export raw platform conversation, channel, session, thread, message, and user identifiers so Sentry traces can be mapped directly back to their source while continuing to exclude human-readable names and conversation or tool content.
17
+
18
+ ### Removed
19
+
20
+ - Remove telemetry identifier hashing and the `TELEMETRY_HASH_KEY` deployment setting.
21
+
22
+ ## [1.0.0-beta.56]
23
+
24
+ ### Added
25
+
26
+ - Add content-free agent diagnostics for actual model IDs, token and cost totals, first-token latency, context utilization, message and attachment counts, payload sizes, tool categories and status, and retry, compaction, and budget summaries.
27
+
28
+ ### Security
29
+
30
+ - Replace raw conversation, session, message, thread, and user telemetry identifiers with stable opaque values, support deployment-specific HMACs through `TELEMETRY_HASH_KEY`, and stop sending usernames to Sentry.
31
+
12
32
  ## [1.0.0-beta.55]
13
33
 
14
34
  ### Added
@@ -1 +1 @@
1
- {"version":3,"file":"presenter.d.ts","sourceRoot":"","sources":["../../src/harness/presenter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,GAAG,EAAE,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAEV,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAOtD,OAAO,KAAK,EAAE,qBAAqB,EAA4B,MAAM,eAAe,CAAC;AACrF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAwDhE,wBAAgB,cAAc,IAAI,kBAAkB,CAsBnD;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE;IACP,SAAS,EAAE,qBAAqB,CAAC;IACjC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC,GACA,eAAe,CA0DjB;AAOD,wBAAgB,yBAAyB,CAAC,kBAAkB,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEzF;AAmKD,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,qBAAqB,EAChC,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE;IACR,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GACA,OAAO,CAAC,IAAI,CAAC,CAiBf;AA4DD,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmE/E;AA+XD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IACjD,OAAO,EAAE,iBAAiB,CAAC;IAC3B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,WAAW,EAAE,UAAU,CAAC,OAAO,2BAA2B,CAAC,CAAC;CAC7D,GAAG,IAAI,CAiBP"}
1
+ {"version":3,"file":"presenter.d.ts","sourceRoot":"","sources":["../../src/harness/presenter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,GAAG,EAAE,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAEV,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAOtD,OAAO,KAAK,EAAE,qBAAqB,EAA4B,MAAM,eAAe,CAAC;AACrF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AA+DhE,wBAAgB,cAAc,IAAI,kBAAkB,CAkCnD;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE;IACP,SAAS,EAAE,qBAAqB,CAAC;IACjC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC,GACA,eAAe,CAsEjB;AAOD,wBAAgB,yBAAyB,CAAC,kBAAkB,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEzF;AAmKD,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,qBAAqB,EAChC,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE;IACR,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GACA,OAAO,CAAC,IAAI,CAAC,CAiBf;AA4DD,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2F/E;AAubD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IACjD,OAAO,EAAE,iBAAiB,CAAC;IAC3B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,WAAW,EAAE,UAAU,CAAC,OAAO,2BAA2B,CAAC,CAAC;CAC7D,GAAG,IAAI,CAiBP"}
@@ -1,6 +1,6 @@
1
1
  import { contentText } from "@earendil-works/pi-ai";
2
2
  import { mergeSubagentProgress, parseSubagentProgressSnapshot, renderSubagentDashboard, settleSubagentProgress, } from "./tools/subagent.js";
3
- import { addLifecycleEvent, metricAttributes, recordCounter, recordDistribution, recordGauge, reportUserFacingError, startOperationSpan, } from "../observability/index.js";
3
+ import { addLifecycleEvent, metricAttributes, recordCounter, recordDistribution, recordGauge, reportUserFacingError, startOperationSpan, updateActiveSpanAttribution, } from "../observability/index.js";
4
4
  import { appendTriggerAttribution } from "./prompt.js";
5
5
  import * as log from "../log.js";
6
6
  const operationSpans = new WeakMap();
@@ -22,8 +22,8 @@ function endOutstandingOperationSpans(runState) {
22
22
  if (!spans)
23
23
  return;
24
24
  const aborted = operationError("AbortError");
25
- for (const span of spans.llm)
26
- span.end({ error: aborted });
25
+ for (const entry of spans.llm)
26
+ entry.span.end({ error: aborted });
27
27
  for (const span of spans.tools.values())
28
28
  span.end({ error: aborted });
29
29
  operationSpans.delete(runState);
@@ -53,6 +53,18 @@ export function createRunState() {
53
53
  toolProgressTimer: undefined,
54
54
  totalUsage: createEmptyUsageTotals(),
55
55
  llmCallCount: 0,
56
+ toolCallCount: 0,
57
+ toolErrorCount: 0,
58
+ toolInputCharacters: 0,
59
+ toolOutputCharacters: 0,
60
+ assistantMessageCount: 0,
61
+ outputCharacters: 0,
62
+ reasoningTokens: 0,
63
+ retryCount: 0,
64
+ compactionCount: 0,
65
+ budgetExceeded: false,
66
+ firstTokenLatencyMs: undefined,
67
+ responseModel: undefined,
56
68
  stopReason: "stop",
57
69
  errorMessage: undefined,
58
70
  reportedLlmError: false,
@@ -82,6 +94,18 @@ export function activateRunPresentation(runState, context) {
82
94
  runState.toolProgressTimer = undefined;
83
95
  runState.totalUsage = createEmptyUsageTotals();
84
96
  runState.llmCallCount = 0;
97
+ runState.toolCallCount = 0;
98
+ runState.toolErrorCount = 0;
99
+ runState.toolInputCharacters = 0;
100
+ runState.toolOutputCharacters = 0;
101
+ runState.assistantMessageCount = 0;
102
+ runState.outputCharacters = 0;
103
+ runState.reasoningTokens = 0;
104
+ runState.retryCount = 0;
105
+ runState.compactionCount = 0;
106
+ runState.budgetExceeded = false;
107
+ runState.firstTokenLatencyMs = undefined;
108
+ runState.responseModel = undefined;
85
109
  runState.stopReason = "stop";
86
110
  runState.errorMessage = undefined;
87
111
  runState.reportedLlmError = false;
@@ -320,7 +344,7 @@ async function publishFinalResponse(responder, runState, finalText, options) {
320
344
  }
321
345
  }
322
346
  export async function reportUsageSummary(ctx) {
323
- const { session, runState, responder, platform, model, agentConfig, sessionConversation, sessionUuid, waitForQueue, } = ctx;
347
+ const { session, runState, responder, platform, model, sessionConversation, sessionUuid, waitForQueue, } = ctx;
324
348
  const lastAssistantMessage = session.messages.findLast((message) => message.role === "assistant" && message.stopReason !== "aborted");
325
349
  const contextTokens = lastAssistantMessage
326
350
  ? lastAssistantMessage.usage.input +
@@ -332,7 +356,7 @@ export async function reportUsageSummary(ctx) {
332
356
  const { totalUsage } = runState;
333
357
  const runMetricAttributes = metricAttributes({
334
358
  provider: model.provider,
335
- model: agentConfig.model,
359
+ model: model.id,
336
360
  channel_id: sessionConversation,
337
361
  session_id: sessionUuid,
338
362
  stop_reason: runState.stopReason,
@@ -353,10 +377,35 @@ export async function reportUsageSummary(ctx) {
353
377
  recordDistribution("agent.run.cost", totalUsage.cost.total, {
354
378
  attributes: runMetricAttributes,
355
379
  });
356
- recordGauge("agent.context.utilization", contextTokens / contextWindow, {
380
+ const contextUtilization = contextTokens / contextWindow;
381
+ recordGauge("agent.context.utilization", contextUtilization, {
357
382
  unit: "ratio",
358
383
  attributes: runMetricAttributes,
359
384
  });
385
+ updateActiveSpanAttribution({
386
+ "gen_ai.request.model": model.id,
387
+ "gen_ai.response.model": runState.responseModel ?? model.id,
388
+ "gen_ai.usage.input_tokens": totalUsage.input + totalUsage.cacheRead + totalUsage.cacheWrite,
389
+ "gen_ai.usage.input_tokens.cached": totalUsage.cacheRead,
390
+ "gen_ai.usage.input_tokens.cache_write": totalUsage.cacheWrite,
391
+ "gen_ai.usage.output_tokens": totalUsage.output,
392
+ "gen_ai.usage.output_tokens.reasoning": runState.reasoningTokens,
393
+ "mikan.usage.cost_usd": totalUsage.cost.total,
394
+ "mikan.context.utilization": contextUtilization,
395
+ "mikan.llm.call_count": runState.llmCallCount,
396
+ "mikan.tool.call_count": runState.toolCallCount,
397
+ "mikan.tool.error_count": runState.toolErrorCount,
398
+ "mikan.tool.input.characters": runState.toolInputCharacters,
399
+ "mikan.tool.output.characters": runState.toolOutputCharacters,
400
+ "mikan.output.message_count": runState.assistantMessageCount,
401
+ "mikan.output.characters": runState.outputCharacters,
402
+ "mikan.retry.count": runState.retryCount,
403
+ "mikan.compaction.count": runState.compactionCount,
404
+ "mikan.budget.exceeded": runState.budgetExceeded,
405
+ ...(runState.firstTokenLatencyMs === undefined
406
+ ? {}
407
+ : { "mikan.response.first_token_ms": runState.firstTokenLatencyMs }),
408
+ });
360
409
  const summary = log.logUsageSummary(runState.logCtx, runState.totalUsage, contextTokens, contextWindow);
361
410
  if (platform.diagnostics?.showUsageSummary === true) {
362
411
  runState.queue.enqueue(() => responder.respondDiagnostic(summary, { style: "muted" }), "usage summary");
@@ -380,10 +429,35 @@ function extractToolResultText(result) {
380
429
  return result;
381
430
  return toolResultContentText(result) ?? JSON.stringify(result);
382
431
  }
432
+ function serializedLength(value) {
433
+ if (typeof value === "string")
434
+ return value.length;
435
+ try {
436
+ return JSON.stringify(value)?.length ?? 0;
437
+ }
438
+ catch {
439
+ return 0;
440
+ }
441
+ }
442
+ function toolCategory(name) {
443
+ if (["read", "write", "edit", "bash"].includes(name))
444
+ return "sandbox";
445
+ if (name === "subagent")
446
+ return "agent";
447
+ if (name.startsWith("mcp__"))
448
+ return "mcp";
449
+ if (name.startsWith("github_"))
450
+ return "github";
451
+ if (name === "slack_blockkit")
452
+ return "platform";
453
+ return "function";
454
+ }
383
455
  function handleToolStart(event, context) {
384
456
  const { runState, responder, logCtx, queue, baseAttrs } = context;
385
457
  const args = (event.args ?? {});
386
458
  const label = args.label || event.toolName;
459
+ runState.toolCallCount += 1;
460
+ runState.toolInputCharacters += serializedLength(event.args);
387
461
  runState.pendingTools.set(event.toolCallId, {
388
462
  toolName: event.toolName,
389
463
  args: event.args,
@@ -402,6 +476,9 @@ function handleToolStart(event, context) {
402
476
  spansFor(runState).tools.set(event.toolCallId, startOperationSpan(`execute_tool ${event.toolName}`, metricAttributes({
403
477
  "gen_ai.operation.name": "execute_tool",
404
478
  "gen_ai.tool.name": event.toolName,
479
+ "gen_ai.tool.type": "function",
480
+ "mikan.tool.category": toolCategory(event.toolName),
481
+ "mikan.tool.input.characters": serializedLength(event.args),
405
482
  "openinference.span.kind": "TOOL",
406
483
  ...baseAttrs,
407
484
  })));
@@ -437,6 +514,10 @@ function recordToolMetrics(event, durationMs, context) {
437
514
  function handleToolEnd(event, context) {
438
515
  const { runState, responder, logCtx } = context;
439
516
  const resultStr = extractToolResultText(event.result);
517
+ const outputCharacters = serializedLength(event.result);
518
+ runState.toolOutputCharacters += outputCharacters;
519
+ if (event.isError)
520
+ runState.toolErrorCount += 1;
440
521
  const pending = runState.pendingTools.get(event.toolCallId);
441
522
  const progress = runState.toolProgress.get(event.toolCallId);
442
523
  if (progress)
@@ -456,6 +537,9 @@ function handleToolEnd(event, context) {
456
537
  toolSpan?.end({
457
538
  attributes: metricAttributes({
458
539
  "gen_ai.tool.name": event.toolName,
540
+ "gen_ai.tool.type": "function",
541
+ "mikan.tool.category": toolCategory(event.toolName),
542
+ "mikan.tool.output.characters": outputCharacters,
459
543
  "openinference.span.kind": "TOOL",
460
544
  duration_ms: durationMs,
461
545
  ...context.baseAttrs,
@@ -476,15 +560,18 @@ function handleMessageStart(event, context) {
476
560
  if (event.message.role !== "assistant")
477
561
  return;
478
562
  context.runState.llmCallCount += 1;
479
- spansFor(context.runState).llm.push(startOperationSpan(`chat ${context.agentConfig.model}`, metricAttributes({
480
- "gen_ai.operation.name": "chat",
481
- "gen_ai.provider.name": context.model.provider,
482
- "gen_ai.request.model": context.agentConfig.model,
483
- "openinference.span.kind": "LLM",
484
- "llm.provider": context.model.provider,
485
- "llm.model_name": context.agentConfig.model,
486
- ...context.baseAttrs,
487
- })));
563
+ spansFor(context.runState).llm.push({
564
+ span: startOperationSpan(`chat ${context.model.id}`, metricAttributes({
565
+ "gen_ai.operation.name": "chat",
566
+ "gen_ai.provider.name": context.model.provider,
567
+ "gen_ai.request.model": context.model.id,
568
+ "openinference.span.kind": "LLM",
569
+ "llm.provider": context.model.provider,
570
+ "llm.model_name": context.model.id,
571
+ ...context.baseAttrs,
572
+ })),
573
+ startedAt: Date.now(),
574
+ });
488
575
  addLifecycleEvent("agent.llm.call.started", {
489
576
  call_index: context.runState.llmCallCount,
490
577
  provider: context.model.provider,
@@ -497,6 +584,11 @@ function handleMessageUpdate(event, context) {
497
584
  const update = event.assistantMessageEvent;
498
585
  if (update.type !== "text_delta" || !update.delta)
499
586
  return;
587
+ const llmEntry = spansFor(context.runState).llm[0];
588
+ if (llmEntry && llmEntry.firstTokenAt === undefined) {
589
+ llmEntry.firstTokenAt = Date.now();
590
+ context.runState.firstTokenLatencyMs ??= llmEntry.firstTokenAt - llmEntry.startedAt;
591
+ }
500
592
  if (context.responder.appendResponseDelta && !context.runState.suppressResponseDeltas) {
501
593
  context.queue.enqueue(async () => {
502
594
  await context.responder.appendResponseDelta?.(update.delta);
@@ -516,6 +608,8 @@ function recordAssistantUsage(message, context) {
516
608
  totalUsage.cost.cacheRead += message.usage.cost.cacheRead;
517
609
  totalUsage.cost.cacheWrite += message.usage.cost.cacheWrite;
518
610
  totalUsage.cost.total += message.usage.cost.total;
611
+ context.runState.reasoningTokens += message.usage.reasoning ?? 0;
612
+ context.runState.responseModel = message.responseModel ?? message.model;
519
613
  const attributes = metricAttributes({
520
614
  provider: context.model.provider,
521
615
  model: context.agentConfig.model,
@@ -587,27 +681,36 @@ function handleMessageEnd(event, context) {
587
681
  if (event.message.role !== "assistant")
588
682
  return;
589
683
  const message = event.message;
684
+ context.runState.assistantMessageCount += 1;
685
+ context.runState.outputCharacters += message.content.reduce((total, part) => total + (part.type === "text" ? part.text.length : 0), 0);
590
686
  if (message.stopReason) {
591
687
  context.runState.stopReason = message.stopReason;
592
688
  // The settling message clears any stale error left by a recovered retry.
593
689
  context.runState.errorMessage = message.errorMessage;
594
690
  }
595
691
  recordAssistantUsage(message, context);
596
- const llmSpan = spansFor(context.runState).llm.shift();
692
+ const llmEntry = spansFor(context.runState).llm.shift();
597
693
  const inputTokens = message.usage
598
694
  ? message.usage.input + message.usage.cacheRead + message.usage.cacheWrite
599
695
  : undefined;
600
- llmSpan?.end({
696
+ llmEntry?.span.end({
601
697
  attributes: metricAttributes({
602
698
  "gen_ai.provider.name": context.model.provider,
603
- "gen_ai.request.model": context.agentConfig.model,
699
+ "gen_ai.request.model": context.model.id,
700
+ "gen_ai.response.model": message.responseModel ?? message.model,
604
701
  "gen_ai.usage.input_tokens": inputTokens,
605
702
  "gen_ai.usage.output_tokens": message.usage?.output,
606
- "gen_ai.usage.cache_read.input_tokens": message.usage?.cacheRead,
607
- "gen_ai.usage.cache_write.input_tokens": message.usage?.cacheWrite,
703
+ "gen_ai.usage.input_tokens.cached": message.usage?.cacheRead,
704
+ "gen_ai.usage.input_tokens.cache_write": message.usage?.cacheWrite,
705
+ "gen_ai.usage.output_tokens.reasoning": message.usage?.reasoning,
706
+ "mikan.usage.cost_usd": message.usage?.cost.total,
707
+ "mikan.response.first_token_ms": llmEntry?.firstTokenAt === undefined
708
+ ? undefined
709
+ : llmEntry.firstTokenAt - llmEntry.startedAt,
710
+ "mikan.output.characters": message.content.reduce((total, part) => total + (part.type === "text" ? part.text.length : 0), 0),
608
711
  "openinference.span.kind": "LLM",
609
712
  "llm.provider": context.model.provider,
610
- "llm.model_name": context.agentConfig.model,
713
+ "llm.model_name": context.model.id,
611
714
  "llm.token_count.prompt": inputTokens,
612
715
  "llm.token_count.completion": message.usage?.output,
613
716
  "llm.token_count.total": inputTokens !== undefined && message.usage ? inputTokens + message.usage.output : undefined,
@@ -622,6 +725,7 @@ function handleMessageEnd(event, context) {
622
725
  }
623
726
  function handleLifecycleEvent(event, context) {
624
727
  if (event.type === "compaction_start") {
728
+ context.runState.compactionCount += 1;
625
729
  const text = "_Compacting context..._";
626
730
  log.logInfo(`Auto-compaction started (reason: ${event.reason})`);
627
731
  context.queue.enqueue(() => context.responder.respond(text), "compaction start");
@@ -637,11 +741,13 @@ function handleLifecycleEvent(event, context) {
637
741
  return;
638
742
  }
639
743
  if (event.type === "auto_retry_start") {
744
+ context.runState.retryCount += 1;
640
745
  log.logWarning(`Retrying (${event.attempt}/${event.maxAttempts})`, event.errorMessage);
641
746
  const text = `_Retrying (${event.attempt}/${event.maxAttempts})..._`;
642
747
  context.queue.enqueue(() => context.responder.respond(text), "retry");
643
748
  return;
644
749
  }
750
+ context.runState.budgetExceeded = true;
645
751
  log.logWarning("Run stopped by budget circuit breaker", `${event.reason} (tokens=${event.tokens}, cost=${event.costUsd.toFixed(2)}, calls=${event.llmCalls}, ${event.durationMs}ms)`);
646
752
  const text = `_Stopped: run budget exceeded (${event.reason})_`;
647
753
  context.queue.enqueue(() => context.responder.respondDiagnostic(text, { style: "error" }), "budget exceeded");