@graphorin/observability 0.5.0 → 0.6.1

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +10 -6
  3. package/dist/cost/cost-tracker.js.map +1 -1
  4. package/dist/cost/types.d.ts +1 -1
  5. package/dist/eval/runner.js.map +1 -1
  6. package/dist/eval/types.d.ts +19 -1
  7. package/dist/eval/types.d.ts.map +1 -1
  8. package/dist/exporters/console.js.map +1 -1
  9. package/dist/exporters/jsonl.d.ts +1 -1
  10. package/dist/exporters/jsonl.js +2 -2
  11. package/dist/exporters/jsonl.js.map +1 -1
  12. package/dist/exporters/otlp-http.d.ts +2 -2
  13. package/dist/exporters/otlp-http.d.ts.map +1 -1
  14. package/dist/exporters/otlp-http.js +6 -4
  15. package/dist/exporters/otlp-http.js.map +1 -1
  16. package/dist/exporters/types.d.ts +2 -2
  17. package/dist/exporters/types.js +1 -1
  18. package/dist/exporters/types.js.map +1 -1
  19. package/dist/exporters/with-validation.d.ts +1 -1
  20. package/dist/exporters/with-validation.js.map +1 -1
  21. package/dist/gen-ai/emit.d.ts +1 -1
  22. package/dist/gen-ai/emit.js +1 -1
  23. package/dist/gen-ai/emit.js.map +1 -1
  24. package/dist/gen-ai/operation-mapping.d.ts +1 -1
  25. package/dist/gen-ai/operation-mapping.d.ts.map +1 -1
  26. package/dist/gen-ai/operation-mapping.js +3 -3
  27. package/dist/gen-ai/operation-mapping.js.map +1 -1
  28. package/dist/gen-ai/types.d.ts +2 -2
  29. package/dist/gen-ai/types.d.ts.map +1 -1
  30. package/dist/index.d.ts +2 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +4 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/logger/logger.js +1 -1
  35. package/dist/logger/logger.js.map +1 -1
  36. package/dist/openinference/index.d.ts +2 -2
  37. package/dist/openinference/index.js +2 -2
  38. package/dist/openinference/index.js.map +1 -1
  39. package/dist/package.js +6 -0
  40. package/dist/package.js.map +1 -0
  41. package/dist/redaction/errors.js +1 -1
  42. package/dist/redaction/errors.js.map +1 -1
  43. package/dist/redaction/imperative-patterns.d.ts +3 -3
  44. package/dist/redaction/imperative-patterns.js +2 -2
  45. package/dist/redaction/imperative-patterns.js.map +1 -1
  46. package/dist/redaction/patterns.d.ts +6 -6
  47. package/dist/redaction/patterns.d.ts.map +1 -1
  48. package/dist/redaction/patterns.js +5 -5
  49. package/dist/redaction/patterns.js.map +1 -1
  50. package/dist/redaction/types.d.ts +1 -1
  51. package/dist/redaction/validator.js +2 -2
  52. package/dist/redaction/validator.js.map +1 -1
  53. package/dist/replay/config.d.ts +1 -1
  54. package/dist/replay/config.js.map +1 -1
  55. package/dist/replay/log.js +1 -1
  56. package/dist/replay/log.js.map +1 -1
  57. package/dist/replay/replay.js.map +1 -1
  58. package/dist/replay/types.d.ts +2 -2
  59. package/dist/telemetry/index.js +2 -2
  60. package/dist/telemetry/index.js.map +1 -1
  61. package/dist/tracer/sampling.d.ts +2 -2
  62. package/dist/tracer/sampling.js +1 -1
  63. package/dist/tracer/sampling.js.map +1 -1
  64. package/dist/tracer/span-names.d.ts +1 -1
  65. package/dist/tracer/span-names.d.ts.map +1 -1
  66. package/dist/tracer/span-names.js +25 -2
  67. package/dist/tracer/span-names.js.map +1 -1
  68. package/dist/tracer/span.js.map +1 -1
  69. package/dist/tracer/tracer.d.ts +1 -1
  70. package/dist/tracer/tracer.js +2 -2
  71. package/dist/tracer/tracer.js.map +1 -1
  72. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @graphorin/observability
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#140](https://github.com/o-stepper/graphorin/pull/140) [`826ee59`](https://github.com/o-stepper/graphorin/commit/826ee5933ad38693b0dd2f20a110abfecba7d23d) Thanks [@o-stepper](https://github.com/o-stepper)! - Fix the default-on `graphorin-token` redaction pattern: it was hardcoded to a stale `kru_(dev|test|prod)` token shape and never matched real framework tokens, which use the `gph` default prefix with `live|test|local` environment labels (`@graphorin/security` `DEFAULT_TOKEN_PREFIX`). The pattern now matches `gph_<env>_v1_<entropy>_<crc32>` with a loose env label (operators can extend `acceptEnvironments`); deployments that configure a custom token prefix must register their own pattern.
8
+
9
+ - [#142](https://github.com/o-stepper/graphorin/pull/142) [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430) Thanks [@o-stepper](https://github.com/o-stepper)! - Version constants and version-bearing strings now derive from each package's manifest at build time (`VERSION = pkg.version`; writer ids, client/server info, OTLP framework attributes, build-info metrics interpolate it). No behavioral change at the current version: the rendered strings are byte-identical. A release bump no longer edits source; the new `check-version-consistency` gate fails any reintroduced hardcoded framework version.
10
+
11
+ - Updated dependencies [[`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430)]:
12
+ - @graphorin/core@0.6.1
13
+
14
+ ## 0.6.0
15
+
16
+ ### Minor Changes
17
+
18
+ - [#138](https://github.com/o-stepper/graphorin/pull/138) [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a) Thanks [@o-stepper](https://github.com/o-stepper)! - OTel GenAI alignment + honest eval statistics (audit 2026-07-04 Wave E, cluster E8).
19
+
20
+ - Span names follow the GenAI semconv `{operation} {target}` shape when attributes carry the target (`chat <model>`, `execute_tool <tool>`, `invoke_agent <agent>`); the operation-mapping table and `GenAIOperationName` gain `invoke_agent` to match what the runtime actually emits; `agent.step` spans carry `gen_ai.agent.name`; pricing's `listMissingModels` reads the current `gen_ai.provider.name` attribute first (deprecated `gen_ai.system` kept as fallback).
21
+ - New `@graphorin/evals` stats module: `mean`/`sampleStddev`, Wilson 95% interval, `passHatK` over `-iter-N` outcomes, and McNemar paired significance. `runEvals` summaries always carry `passRateCi` and (under `iterations > 1`) `passHatK`; `detectRegressions` annotates pass-rate-drop findings with the paired regressed/improved counts + p-value, and opt-in `requireSignificance` vetoes drops the paired test cannot distinguish from noise - the fixed tolerance alone was sample-size blind.
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [[`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627)]:
26
+ - @graphorin/core@0.6.0
27
+
28
+ ## 0.5.0
29
+
30
+ First version published to the npm registry (with Sigstore build
31
+ provenance). The 0.2.0, 0.3.0, and 0.4.0 versions were internal lockstep
32
+ milestones and were never published. All `@graphorin/*` packages release
33
+ lockstep at the same version; the full release notes for 0.2.0-0.5.0 live
34
+ in the repository-level
35
+ [CHANGELOG](https://github.com/o-stepper/graphorin/blob/main/CHANGELOG.md).
36
+
3
37
  ## 0.1.0
4
38
 
5
39
  Initial release. See the workspace root `CHANGELOG.md` for the full
package/README.md CHANGED
@@ -19,18 +19,22 @@ outbound network calls without an explicit user action.
19
19
  - **Mandatory `withValidation(...)` wrapper.** Every exporter passed to
20
20
  `createTracer({ exporters })` is wrapped through the configured
21
21
  `RedactionValidator`. Registering a raw exporter while
22
- `validation: 'off'` triggers `UnvalidatedExporterError` at startup
22
+ `validation: 'off'` triggers `UnvalidatedExporterError` at startup -
23
23
  there is no silent path.
24
24
  - **`RedactionValidator` with 14 built-in patterns.** API key / JWT /
25
25
  PEM private key / GitHub PAT / AWS access key / Graphorin token /
26
26
  bearer header / basic-auth header / email / credit card / US SSN /
27
- E.164 phone / IBAN all on by default. Three additional patterns
27
+ E.164 phone / IBAN - all on by default. Three additional patterns
28
28
  (IPv4, IPv6, GCP service account) are opt-in.
29
29
  - **OpenTelemetry GenAI semantic-conventions conformance.**
30
30
  `emitGenAIAttributes(span, {...})`, `emitGenAIMessageEvents(span, [...])`,
31
31
  and `deriveGenAISystem(...)` ship the canonical
32
32
  `gen_ai.*` attribute family alongside the existing
33
- `graphorin.*` attributes additive, never replacing.
33
+ `graphorin.*` attributes - additive, never replacing. Span names
34
+ follow the semconv `{operation} {target}` shape (`chat <model>`,
35
+ `execute_tool <tool>`, `invoke_agent <agent>`) via the exported
36
+ `spanNameFor(type, attrs)` helper, applied by the tracer
37
+ automatically.
34
38
  - **OpenInference span-kind layer.** `emitOpenInferenceKind(span)` emits
35
39
  the `openinference.span.kind` attribute via the canonical
36
40
  per-`SpanType` mapping (`agent.*` → `AGENT`,
@@ -64,7 +68,7 @@ outbound network calls without an explicit user action.
64
68
 
65
69
  ```bash
66
70
  pnpm add @graphorin/observability
67
- # Optional peer deps for OTLP export install only when you need them:
71
+ # Optional peer deps for OTLP export - install only when you need them:
68
72
  pnpm add @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http
69
73
  ```
70
74
 
@@ -83,7 +87,7 @@ const tracer = createTracer({
83
87
  exporters: [
84
88
  // Auto-wrapped via the tracer-managed validator.
85
89
  createConsoleExporter({ pretty: true }),
86
- // Manually wrapped useful when each exporter needs its own policy.
90
+ // Manually wrapped - useful when each exporter needs its own policy.
87
91
  withValidation(createJSONLExporter({ path: './traces' }), {
88
92
  minTier: 'internal',
89
93
  }),
@@ -112,4 +116,4 @@ MIT © 2026 [Oleksiy Stepurenko](https://github.com/o-stepper).
112
116
 
113
117
  ---
114
118
 
115
- **Project Graphorin** · v0.5.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
119
+ **Project Graphorin** · v0.6.1 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
@@ -1 +1 @@
1
- {"version":3,"file":"cost-tracker.js","names":["cost: Cost | null","ZERO: CostSnapshot","budgets: CostBudgets","byScope: Record<CostScope, Map<string, AggregateBucket>>"],"sources":["../../src/cost/cost-tracker.ts"],"sourcesContent":["/**\n * `createCostTracker(...)` — aggregate token + cost figures across\n * spans and provide per-scope queries.\n *\n * The tracker preserves parent-child relationships: when\n * `record({ parentSpanId })` is supplied, the recorded usage is also\n * attributed to the ancestor span via the configured spans-by-id\n * lookup. Aggregations across runs / sessions / agents / users are\n * computed on demand.\n *\n * @packageDocumentation\n */\n\nimport type { Cost } from '@graphorin/core';\n\nimport type {\n CostBudgetExceededCallback,\n CostBudgets,\n CostRecordInput,\n CostScope,\n CostSnapshot,\n CostTrackerOptions,\n} from './types.js';\n\n/**\n * @stable\n */\nexport interface CostTracker {\n /** Record a single LLM-call usage / cost figure. */\n record(input: CostRecordInput): void;\n /** Snapshot for a given scope id. Returns zero figures when unknown. */\n usage(scope: CostScope, id: string): CostSnapshot;\n /** Snapshot for a single span id (carries nested attributions). */\n usageForSpan(spanId: string): CostSnapshot;\n /** Reset every counter back to zero. */\n reset(): void;\n /** Subscribe to per-scope rollup notifications. Returns an unsubscribe. */\n onRollup(listener: (input: CostRecordInput) => void): () => void;\n}\n\ninterface AggregateBucket {\n promptTokens: number;\n completionTokens: number;\n reasoningTokens: number;\n callCount: number;\n costAmount: number;\n costCurrency: string | null;\n /** RP-22: set when records carrying differing currencies are aggregated. */\n mixedCurrency: boolean;\n byModel: Map<string, AggregateBucket>;\n exceeded: boolean;\n}\n\nfunction freshBucket(): AggregateBucket {\n return {\n promptTokens: 0,\n completionTokens: 0,\n reasoningTokens: 0,\n callCount: 0,\n costAmount: 0,\n costCurrency: null,\n mixedCurrency: false,\n byModel: new Map(),\n exceeded: false,\n };\n}\n\n// RP-22: accumulate a cost without silently overwriting the currency. The\n// first currency seen is kept; a record in a different currency flags the\n// bucket as `mixedCurrency` so a USD + EUR total is never reported as one\n// clean figure.\nfunction addCost(bucket: AggregateBucket, cost: Cost): void {\n if (bucket.costCurrency !== null && bucket.costCurrency !== cost.currency) {\n bucket.mixedCurrency = true;\n } else if (bucket.costCurrency === null) {\n bucket.costCurrency = cost.currency;\n }\n bucket.costAmount += cost.amount;\n}\n\nfunction combine(target: AggregateBucket, input: CostRecordInput): void {\n target.promptTokens += input.promptTokens;\n target.completionTokens += input.completionTokens;\n target.reasoningTokens += input.reasoningTokens ?? 0;\n target.callCount += 1;\n if (input.cost !== undefined) addCost(target, input.cost);\n const modelBucket = target.byModel.get(input.model) ?? freshBucket();\n modelBucket.promptTokens += input.promptTokens;\n modelBucket.completionTokens += input.completionTokens;\n modelBucket.reasoningTokens += input.reasoningTokens ?? 0;\n modelBucket.callCount += 1;\n if (input.cost !== undefined) addCost(modelBucket, input.cost);\n target.byModel.set(input.model, modelBucket);\n}\n\nfunction snapshotOf(bucket: AggregateBucket): CostSnapshot {\n const cost: Cost | null =\n bucket.costCurrency === null\n ? null\n : { amount: bucket.costAmount, currency: bucket.costCurrency };\n return {\n promptTokens: bucket.promptTokens,\n completionTokens: bucket.completionTokens,\n reasoningTokens: bucket.reasoningTokens,\n totalTokens: bucket.promptTokens + bucket.completionTokens + bucket.reasoningTokens,\n callCount: bucket.callCount,\n cost,\n mixedCurrency: bucket.mixedCurrency,\n byModel: [...bucket.byModel.entries()].map(([model, b]) => ({\n model,\n promptTokens: b.promptTokens,\n completionTokens: b.completionTokens,\n reasoningTokens: b.reasoningTokens,\n callCount: b.callCount,\n cost: b.costCurrency === null ? null : { amount: b.costAmount, currency: b.costCurrency },\n mixedCurrency: b.mixedCurrency,\n })),\n };\n}\n\nconst ZERO: CostSnapshot = Object.freeze({\n promptTokens: 0,\n completionTokens: 0,\n reasoningTokens: 0,\n totalTokens: 0,\n callCount: 0,\n cost: null,\n mixedCurrency: false,\n byModel: [],\n});\n\n/**\n * Build a {@link CostTracker} configured with the supplied budgets.\n *\n * @stable\n */\nexport function createCostTracker(opts: CostTrackerOptions = {}): CostTracker {\n const budgets: CostBudgets = opts.budgets ?? {};\n const currency = budgets.currency ?? 'USD';\n const onExceed = opts.onExceed;\n\n const bySpan = new Map<string, AggregateBucket>();\n const byScope: Record<CostScope, Map<string, AggregateBucket>> = {\n run: new Map(),\n session: new Map(),\n agent: new Map(),\n user: new Map(),\n };\n\n // Parent index: child span -> parent span. Used to roll attributions\n // up the ancestor chain on every record call.\n const parents = new Map<string, string>();\n const listeners = new Set<(input: CostRecordInput) => void>();\n\n function bump(scope: CostScope, id: string | undefined, input: CostRecordInput): void {\n if (id === undefined) return;\n const map = byScope[scope];\n const bucket = map.get(id) ?? freshBucket();\n combine(bucket, input);\n map.set(id, bucket);\n enforceBudget(scope, id, bucket, onExceed);\n }\n\n function enforceBudget(\n scope: CostScope,\n id: string,\n bucket: AggregateBucket,\n cb?: CostBudgetExceededCallback,\n ): void {\n const limit = pickBudget(budgets, scope);\n if (limit === undefined) return;\n if (bucket.exceeded) return;\n if (bucket.costAmount > limit) {\n bucket.exceeded = true;\n cb?.({\n scope,\n id,\n budget: limit,\n actual: bucket.costAmount,\n currency: bucket.costCurrency ?? currency,\n });\n }\n }\n\n return {\n record(input: CostRecordInput): void {\n // Record the raw span figures.\n const spanBucket = bySpan.get(input.spanId) ?? freshBucket();\n combine(spanBucket, input);\n bySpan.set(input.spanId, spanBucket);\n\n // Walk the ancestor chain so that the figures roll up to every\n // parent span. The walker breaks if it encounters a cycle.\n if (input.parentSpanId !== undefined) {\n parents.set(input.spanId, input.parentSpanId);\n }\n const seen = new Set<string>();\n let ancestor = parents.get(input.spanId);\n while (ancestor !== undefined && !seen.has(ancestor)) {\n seen.add(ancestor);\n const ancestorBucket = bySpan.get(ancestor) ?? freshBucket();\n combine(ancestorBucket, input);\n bySpan.set(ancestor, ancestorBucket);\n ancestor = parents.get(ancestor);\n }\n\n // Roll up to the requested aggregation scopes.\n bump('run', input.runId, input);\n bump('session', input.sessionId, input);\n bump('agent', input.agentId, input);\n bump('user', input.userId, input);\n\n for (const listener of listeners) {\n try {\n listener(input);\n } catch {\n // Listeners must never break the tracker.\n }\n }\n },\n usage(scope: CostScope, id: string): CostSnapshot {\n const bucket = byScope[scope].get(id);\n if (bucket === undefined) return ZERO;\n return snapshotOf(bucket);\n },\n usageForSpan(spanId: string): CostSnapshot {\n const bucket = bySpan.get(spanId);\n if (bucket === undefined) return ZERO;\n return snapshotOf(bucket);\n },\n reset(): void {\n bySpan.clear();\n for (const map of Object.values(byScope)) {\n map.clear();\n }\n parents.clear();\n },\n onRollup(listener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\nfunction pickBudget(budgets: CostBudgets, scope: CostScope): number | undefined {\n switch (scope) {\n case 'session':\n return budgets.perSession;\n case 'user':\n return budgets.perUser;\n case 'agent':\n return budgets.perAgent;\n case 'run':\n return budgets.perRun;\n }\n}\n"],"mappings":";AAqDA,SAAS,cAA+B;AACtC,QAAO;EACL,cAAc;EACd,kBAAkB;EAClB,iBAAiB;EACjB,WAAW;EACX,YAAY;EACZ,cAAc;EACd,eAAe;EACf,yBAAS,IAAI,KAAK;EAClB,UAAU;EACX;;AAOH,SAAS,QAAQ,QAAyB,MAAkB;AAC1D,KAAI,OAAO,iBAAiB,QAAQ,OAAO,iBAAiB,KAAK,SAC/D,QAAO,gBAAgB;UACd,OAAO,iBAAiB,KACjC,QAAO,eAAe,KAAK;AAE7B,QAAO,cAAc,KAAK;;AAG5B,SAAS,QAAQ,QAAyB,OAA8B;AACtE,QAAO,gBAAgB,MAAM;AAC7B,QAAO,oBAAoB,MAAM;AACjC,QAAO,mBAAmB,MAAM,mBAAmB;AACnD,QAAO,aAAa;AACpB,KAAI,MAAM,SAAS,OAAW,SAAQ,QAAQ,MAAM,KAAK;CACzD,MAAM,cAAc,OAAO,QAAQ,IAAI,MAAM,MAAM,IAAI,aAAa;AACpE,aAAY,gBAAgB,MAAM;AAClC,aAAY,oBAAoB,MAAM;AACtC,aAAY,mBAAmB,MAAM,mBAAmB;AACxD,aAAY,aAAa;AACzB,KAAI,MAAM,SAAS,OAAW,SAAQ,aAAa,MAAM,KAAK;AAC9D,QAAO,QAAQ,IAAI,MAAM,OAAO,YAAY;;AAG9C,SAAS,WAAW,QAAuC;CACzD,MAAMA,OACJ,OAAO,iBAAiB,OACpB,OACA;EAAE,QAAQ,OAAO;EAAY,UAAU,OAAO;EAAc;AAClE,QAAO;EACL,cAAc,OAAO;EACrB,kBAAkB,OAAO;EACzB,iBAAiB,OAAO;EACxB,aAAa,OAAO,eAAe,OAAO,mBAAmB,OAAO;EACpE,WAAW,OAAO;EAClB;EACA,eAAe,OAAO;EACtB,SAAS,CAAC,GAAG,OAAO,QAAQ,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ;GAC1D;GACA,cAAc,EAAE;GAChB,kBAAkB,EAAE;GACpB,iBAAiB,EAAE;GACnB,WAAW,EAAE;GACb,MAAM,EAAE,iBAAiB,OAAO,OAAO;IAAE,QAAQ,EAAE;IAAY,UAAU,EAAE;IAAc;GACzF,eAAe,EAAE;GAClB,EAAE;EACJ;;AAGH,MAAMC,OAAqB,OAAO,OAAO;CACvC,cAAc;CACd,kBAAkB;CAClB,iBAAiB;CACjB,aAAa;CACb,WAAW;CACX,MAAM;CACN,eAAe;CACf,SAAS,EAAE;CACZ,CAAC;;;;;;AAOF,SAAgB,kBAAkB,OAA2B,EAAE,EAAe;CAC5E,MAAMC,UAAuB,KAAK,WAAW,EAAE;CAC/C,MAAM,WAAW,QAAQ,YAAY;CACrC,MAAM,WAAW,KAAK;CAEtB,MAAM,yBAAS,IAAI,KAA8B;CACjD,MAAMC,UAA2D;EAC/D,qBAAK,IAAI,KAAK;EACd,yBAAS,IAAI,KAAK;EAClB,uBAAO,IAAI,KAAK;EAChB,sBAAM,IAAI,KAAK;EAChB;CAID,MAAM,0BAAU,IAAI,KAAqB;CACzC,MAAM,4BAAY,IAAI,KAAuC;CAE7D,SAAS,KAAK,OAAkB,IAAwB,OAA8B;AACpF,MAAI,OAAO,OAAW;EACtB,MAAM,MAAM,QAAQ;EACpB,MAAM,SAAS,IAAI,IAAI,GAAG,IAAI,aAAa;AAC3C,UAAQ,QAAQ,MAAM;AACtB,MAAI,IAAI,IAAI,OAAO;AACnB,gBAAc,OAAO,IAAI,QAAQ,SAAS;;CAG5C,SAAS,cACP,OACA,IACA,QACA,IACM;EACN,MAAM,QAAQ,WAAW,SAAS,MAAM;AACxC,MAAI,UAAU,OAAW;AACzB,MAAI,OAAO,SAAU;AACrB,MAAI,OAAO,aAAa,OAAO;AAC7B,UAAO,WAAW;AAClB,QAAK;IACH;IACA;IACA,QAAQ;IACR,QAAQ,OAAO;IACf,UAAU,OAAO,gBAAgB;IAClC,CAAC;;;AAIN,QAAO;EACL,OAAO,OAA8B;GAEnC,MAAM,aAAa,OAAO,IAAI,MAAM,OAAO,IAAI,aAAa;AAC5D,WAAQ,YAAY,MAAM;AAC1B,UAAO,IAAI,MAAM,QAAQ,WAAW;AAIpC,OAAI,MAAM,iBAAiB,OACzB,SAAQ,IAAI,MAAM,QAAQ,MAAM,aAAa;GAE/C,MAAM,uBAAO,IAAI,KAAa;GAC9B,IAAI,WAAW,QAAQ,IAAI,MAAM,OAAO;AACxC,UAAO,aAAa,UAAa,CAAC,KAAK,IAAI,SAAS,EAAE;AACpD,SAAK,IAAI,SAAS;IAClB,MAAM,iBAAiB,OAAO,IAAI,SAAS,IAAI,aAAa;AAC5D,YAAQ,gBAAgB,MAAM;AAC9B,WAAO,IAAI,UAAU,eAAe;AACpC,eAAW,QAAQ,IAAI,SAAS;;AAIlC,QAAK,OAAO,MAAM,OAAO,MAAM;AAC/B,QAAK,WAAW,MAAM,WAAW,MAAM;AACvC,QAAK,SAAS,MAAM,SAAS,MAAM;AACnC,QAAK,QAAQ,MAAM,QAAQ,MAAM;AAEjC,QAAK,MAAM,YAAY,UACrB,KAAI;AACF,aAAS,MAAM;WACT;;EAKZ,MAAM,OAAkB,IAA0B;GAChD,MAAM,SAAS,QAAQ,OAAO,IAAI,GAAG;AACrC,OAAI,WAAW,OAAW,QAAO;AACjC,UAAO,WAAW,OAAO;;EAE3B,aAAa,QAA8B;GACzC,MAAM,SAAS,OAAO,IAAI,OAAO;AACjC,OAAI,WAAW,OAAW,QAAO;AACjC,UAAO,WAAW,OAAO;;EAE3B,QAAc;AACZ,UAAO,OAAO;AACd,QAAK,MAAM,OAAO,OAAO,OAAO,QAAQ,CACtC,KAAI,OAAO;AAEb,WAAQ,OAAO;;EAEjB,SAAS,UAAsB;AAC7B,aAAU,IAAI,SAAS;AACvB,gBAAa;AACX,cAAU,OAAO,SAAS;;;EAG/B;;AAGH,SAAS,WAAW,SAAsB,OAAsC;AAC9E,SAAQ,OAAR;EACE,KAAK,UACH,QAAO,QAAQ;EACjB,KAAK,OACH,QAAO,QAAQ;EACjB,KAAK,QACH,QAAO,QAAQ;EACjB,KAAK,MACH,QAAO,QAAQ"}
1
+ {"version":3,"file":"cost-tracker.js","names":["cost: Cost | null","ZERO: CostSnapshot","budgets: CostBudgets","byScope: Record<CostScope, Map<string, AggregateBucket>>"],"sources":["../../src/cost/cost-tracker.ts"],"sourcesContent":["/**\n * `createCostTracker(...)` - aggregate token + cost figures across\n * spans and provide per-scope queries.\n *\n * The tracker preserves parent-child relationships: when\n * `record({ parentSpanId })` is supplied, the recorded usage is also\n * attributed to the ancestor span via the configured spans-by-id\n * lookup. Aggregations across runs / sessions / agents / users are\n * computed on demand.\n *\n * @packageDocumentation\n */\n\nimport type { Cost } from '@graphorin/core';\n\nimport type {\n CostBudgetExceededCallback,\n CostBudgets,\n CostRecordInput,\n CostScope,\n CostSnapshot,\n CostTrackerOptions,\n} from './types.js';\n\n/**\n * @stable\n */\nexport interface CostTracker {\n /** Record a single LLM-call usage / cost figure. */\n record(input: CostRecordInput): void;\n /** Snapshot for a given scope id. Returns zero figures when unknown. */\n usage(scope: CostScope, id: string): CostSnapshot;\n /** Snapshot for a single span id (carries nested attributions). */\n usageForSpan(spanId: string): CostSnapshot;\n /** Reset every counter back to zero. */\n reset(): void;\n /** Subscribe to per-scope rollup notifications. Returns an unsubscribe. */\n onRollup(listener: (input: CostRecordInput) => void): () => void;\n}\n\ninterface AggregateBucket {\n promptTokens: number;\n completionTokens: number;\n reasoningTokens: number;\n callCount: number;\n costAmount: number;\n costCurrency: string | null;\n /** RP-22: set when records carrying differing currencies are aggregated. */\n mixedCurrency: boolean;\n byModel: Map<string, AggregateBucket>;\n exceeded: boolean;\n}\n\nfunction freshBucket(): AggregateBucket {\n return {\n promptTokens: 0,\n completionTokens: 0,\n reasoningTokens: 0,\n callCount: 0,\n costAmount: 0,\n costCurrency: null,\n mixedCurrency: false,\n byModel: new Map(),\n exceeded: false,\n };\n}\n\n// RP-22: accumulate a cost without silently overwriting the currency. The\n// first currency seen is kept; a record in a different currency flags the\n// bucket as `mixedCurrency` so a USD + EUR total is never reported as one\n// clean figure.\nfunction addCost(bucket: AggregateBucket, cost: Cost): void {\n if (bucket.costCurrency !== null && bucket.costCurrency !== cost.currency) {\n bucket.mixedCurrency = true;\n } else if (bucket.costCurrency === null) {\n bucket.costCurrency = cost.currency;\n }\n bucket.costAmount += cost.amount;\n}\n\nfunction combine(target: AggregateBucket, input: CostRecordInput): void {\n target.promptTokens += input.promptTokens;\n target.completionTokens += input.completionTokens;\n target.reasoningTokens += input.reasoningTokens ?? 0;\n target.callCount += 1;\n if (input.cost !== undefined) addCost(target, input.cost);\n const modelBucket = target.byModel.get(input.model) ?? freshBucket();\n modelBucket.promptTokens += input.promptTokens;\n modelBucket.completionTokens += input.completionTokens;\n modelBucket.reasoningTokens += input.reasoningTokens ?? 0;\n modelBucket.callCount += 1;\n if (input.cost !== undefined) addCost(modelBucket, input.cost);\n target.byModel.set(input.model, modelBucket);\n}\n\nfunction snapshotOf(bucket: AggregateBucket): CostSnapshot {\n const cost: Cost | null =\n bucket.costCurrency === null\n ? null\n : { amount: bucket.costAmount, currency: bucket.costCurrency };\n return {\n promptTokens: bucket.promptTokens,\n completionTokens: bucket.completionTokens,\n reasoningTokens: bucket.reasoningTokens,\n totalTokens: bucket.promptTokens + bucket.completionTokens + bucket.reasoningTokens,\n callCount: bucket.callCount,\n cost,\n mixedCurrency: bucket.mixedCurrency,\n byModel: [...bucket.byModel.entries()].map(([model, b]) => ({\n model,\n promptTokens: b.promptTokens,\n completionTokens: b.completionTokens,\n reasoningTokens: b.reasoningTokens,\n callCount: b.callCount,\n cost: b.costCurrency === null ? null : { amount: b.costAmount, currency: b.costCurrency },\n mixedCurrency: b.mixedCurrency,\n })),\n };\n}\n\nconst ZERO: CostSnapshot = Object.freeze({\n promptTokens: 0,\n completionTokens: 0,\n reasoningTokens: 0,\n totalTokens: 0,\n callCount: 0,\n cost: null,\n mixedCurrency: false,\n byModel: [],\n});\n\n/**\n * Build a {@link CostTracker} configured with the supplied budgets.\n *\n * @stable\n */\nexport function createCostTracker(opts: CostTrackerOptions = {}): CostTracker {\n const budgets: CostBudgets = opts.budgets ?? {};\n const currency = budgets.currency ?? 'USD';\n const onExceed = opts.onExceed;\n\n const bySpan = new Map<string, AggregateBucket>();\n const byScope: Record<CostScope, Map<string, AggregateBucket>> = {\n run: new Map(),\n session: new Map(),\n agent: new Map(),\n user: new Map(),\n };\n\n // Parent index: child span -> parent span. Used to roll attributions\n // up the ancestor chain on every record call.\n const parents = new Map<string, string>();\n const listeners = new Set<(input: CostRecordInput) => void>();\n\n function bump(scope: CostScope, id: string | undefined, input: CostRecordInput): void {\n if (id === undefined) return;\n const map = byScope[scope];\n const bucket = map.get(id) ?? freshBucket();\n combine(bucket, input);\n map.set(id, bucket);\n enforceBudget(scope, id, bucket, onExceed);\n }\n\n function enforceBudget(\n scope: CostScope,\n id: string,\n bucket: AggregateBucket,\n cb?: CostBudgetExceededCallback,\n ): void {\n const limit = pickBudget(budgets, scope);\n if (limit === undefined) return;\n if (bucket.exceeded) return;\n if (bucket.costAmount > limit) {\n bucket.exceeded = true;\n cb?.({\n scope,\n id,\n budget: limit,\n actual: bucket.costAmount,\n currency: bucket.costCurrency ?? currency,\n });\n }\n }\n\n return {\n record(input: CostRecordInput): void {\n // Record the raw span figures.\n const spanBucket = bySpan.get(input.spanId) ?? freshBucket();\n combine(spanBucket, input);\n bySpan.set(input.spanId, spanBucket);\n\n // Walk the ancestor chain so that the figures roll up to every\n // parent span. The walker breaks if it encounters a cycle.\n if (input.parentSpanId !== undefined) {\n parents.set(input.spanId, input.parentSpanId);\n }\n const seen = new Set<string>();\n let ancestor = parents.get(input.spanId);\n while (ancestor !== undefined && !seen.has(ancestor)) {\n seen.add(ancestor);\n const ancestorBucket = bySpan.get(ancestor) ?? freshBucket();\n combine(ancestorBucket, input);\n bySpan.set(ancestor, ancestorBucket);\n ancestor = parents.get(ancestor);\n }\n\n // Roll up to the requested aggregation scopes.\n bump('run', input.runId, input);\n bump('session', input.sessionId, input);\n bump('agent', input.agentId, input);\n bump('user', input.userId, input);\n\n for (const listener of listeners) {\n try {\n listener(input);\n } catch {\n // Listeners must never break the tracker.\n }\n }\n },\n usage(scope: CostScope, id: string): CostSnapshot {\n const bucket = byScope[scope].get(id);\n if (bucket === undefined) return ZERO;\n return snapshotOf(bucket);\n },\n usageForSpan(spanId: string): CostSnapshot {\n const bucket = bySpan.get(spanId);\n if (bucket === undefined) return ZERO;\n return snapshotOf(bucket);\n },\n reset(): void {\n bySpan.clear();\n for (const map of Object.values(byScope)) {\n map.clear();\n }\n parents.clear();\n },\n onRollup(listener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\nfunction pickBudget(budgets: CostBudgets, scope: CostScope): number | undefined {\n switch (scope) {\n case 'session':\n return budgets.perSession;\n case 'user':\n return budgets.perUser;\n case 'agent':\n return budgets.perAgent;\n case 'run':\n return budgets.perRun;\n }\n}\n"],"mappings":"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"}
@@ -78,7 +78,7 @@ interface CostBudgets {
78
78
  }
79
79
  /**
80
80
  * Callback invoked when an aggregation scope crosses its configured
81
- * budget. The handler receives a sanitized payload the secret-free
81
+ * budget. The handler receives a sanitized payload - the secret-free
82
82
  * scope id + the breached numbers.
83
83
  *
84
84
  * @stable
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","names":["results: EvalCaseResult<I, O>[]","scores: EvalCaseResult<I, O>['scores'][number][]","byScorer: Record<\n string,\n { passed: number; failed: number; scoreSum: number; scoreCount: number }\n >","summaryByScorer: Record<\n string,\n { passed: number; failed: number; avgScore: number | null }\n >"],"sources":["../../src/eval/runner.ts"],"sourcesContent":["/**\n * `runEval(...)` minimal inline eval runner. Walks every case in\n * the dataset, calls `agent.run(case.input)`, applies every supplied\n * scorer, and returns an aggregated {@link EvalReport}.\n *\n * The runner is deliberately tiny it has no parallelism, no\n * retries, no reporters, no dataset loaders. Production scenarios\n * that need any of those should `pnpm add @graphorin/evals` (post-MVP).\n *\n * @packageDocumentation\n */\n\nimport type {\n Case,\n EvalCaseResult,\n EvalReport,\n RunEvalOptions,\n ScoreResult,\n Scorer,\n} from './types.js';\n\n/**\n * @stable\n */\nexport async function runEval<I, O>(opts: RunEvalOptions<I, O>): Promise<EvalReport<I, O>> {\n const iterations = Math.max(1, opts.iterations ?? 1);\n const signal = opts.signal;\n const results: EvalCaseResult<I, O>[] = [];\n\n for (let iter = 0; iter < iterations; iter++) {\n for (let idx = 0; idx < opts.dataset.cases.length; idx++) {\n throwIfAborted(signal);\n const sampleCase = opts.dataset.cases[idx];\n if (sampleCase === undefined) continue;\n // EB-6: disambiguate a caller-provided id per iteration too, not just the\n // synthetic fallback otherwise iterations>1 emits duplicate caseIds.\n const baseId = sampleCase.id ?? `case-${idx}`;\n const caseId = iterations === 1 ? baseId : `${baseId}-iter-${iter}`;\n\n const startedAt = Date.now();\n const output = await opts.agent.run(sampleCase.input);\n const durationMs = Date.now() - startedAt;\n\n const scores: EvalCaseResult<I, O>['scores'][number][] = [];\n for (const scorer of opts.scorers) {\n throwIfAborted(signal);\n const result = await safeScore(scorer, sampleCase, output, durationMs);\n scores.push({ scorer: scorer.name, result });\n }\n\n results.push({\n caseId,\n input: sampleCase.input,\n output,\n durationMs,\n scores,\n });\n }\n }\n\n return summarize(results, opts.scorers);\n}\n\nasync function safeScore<I, O>(\n scorer: Scorer<I, O>,\n c: Case<I, O>,\n output: O,\n durationMs: number,\n): Promise<ScoreResult> {\n try {\n return await scorer.score({ case: c, output, durationMs });\n } catch (err) {\n return {\n pass: false,\n reason: `Scorer \"${scorer.name}\" threw: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n}\n\nfunction summarize<I, O>(\n results: ReadonlyArray<EvalCaseResult<I, O>>,\n scorers: ReadonlyArray<Scorer<I, O>>,\n): EvalReport<I, O> {\n const total = results.length;\n let passed = 0;\n let failed = 0;\n let durationSum = 0;\n const byScorer: Record<\n string,\n { passed: number; failed: number; scoreSum: number; scoreCount: number }\n > = {};\n\n for (const scorer of scorers) {\n byScorer[scorer.name] = { passed: 0, failed: 0, scoreSum: 0, scoreCount: 0 };\n }\n\n for (const r of results) {\n durationSum += r.durationMs;\n let passEntire = true;\n for (const { scorer, result } of r.scores) {\n const bucket = byScorer[scorer] ?? { passed: 0, failed: 0, scoreSum: 0, scoreCount: 0 };\n if (result.pass) bucket.passed += 1;\n else {\n bucket.failed += 1;\n passEntire = false;\n }\n if (typeof result.score === 'number' && Number.isFinite(result.score)) {\n bucket.scoreSum += result.score;\n bucket.scoreCount += 1;\n }\n byScorer[scorer] = bucket;\n }\n if (passEntire) passed += 1;\n else failed += 1;\n }\n\n const summaryByScorer: Record<\n string,\n { passed: number; failed: number; avgScore: number | null }\n > = {};\n for (const [scorer, b] of Object.entries(byScorer)) {\n summaryByScorer[scorer] = {\n passed: b.passed,\n failed: b.failed,\n avgScore: b.scoreCount === 0 ? null : b.scoreSum / b.scoreCount,\n };\n }\n\n return {\n results,\n summary: {\n total,\n passed,\n failed,\n avgDurationMs: total === 0 ? 0 : durationSum / total,\n byScorer: Object.freeze(summaryByScorer),\n },\n };\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted === true) {\n const reason = signal.reason;\n throw reason instanceof Error ? reason : new Error('Eval run aborted');\n }\n}\n"],"mappings":";;;;AAwBA,eAAsB,QAAc,MAAuD;CACzF,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,cAAc,EAAE;CACpD,MAAM,SAAS,KAAK;CACpB,MAAMA,UAAkC,EAAE;AAE1C,MAAK,IAAI,OAAO,GAAG,OAAO,YAAY,OACpC,MAAK,IAAI,MAAM,GAAG,MAAM,KAAK,QAAQ,MAAM,QAAQ,OAAO;AACxD,iBAAe,OAAO;EACtB,MAAM,aAAa,KAAK,QAAQ,MAAM;AACtC,MAAI,eAAe,OAAW;EAG9B,MAAM,SAAS,WAAW,MAAM,QAAQ;EACxC,MAAM,SAAS,eAAe,IAAI,SAAS,GAAG,OAAO,QAAQ;EAE7D,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,SAAS,MAAM,KAAK,MAAM,IAAI,WAAW,MAAM;EACrD,MAAM,aAAa,KAAK,KAAK,GAAG;EAEhC,MAAMC,SAAmD,EAAE;AAC3D,OAAK,MAAM,UAAU,KAAK,SAAS;AACjC,kBAAe,OAAO;GACtB,MAAM,SAAS,MAAM,UAAU,QAAQ,YAAY,QAAQ,WAAW;AACtE,UAAO,KAAK;IAAE,QAAQ,OAAO;IAAM;IAAQ,CAAC;;AAG9C,UAAQ,KAAK;GACX;GACA,OAAO,WAAW;GAClB;GACA;GACA;GACD,CAAC;;AAIN,QAAO,UAAU,SAAS,KAAK,QAAQ;;AAGzC,eAAe,UACb,QACA,GACA,QACA,YACsB;AACtB,KAAI;AACF,SAAO,MAAM,OAAO,MAAM;GAAE,MAAM;GAAG;GAAQ;GAAY,CAAC;UACnD,KAAK;AACZ,SAAO;GACL,MAAM;GACN,QAAQ,WAAW,OAAO,KAAK,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GAC3F;;;AAIL,SAAS,UACP,SACA,SACkB;CAClB,MAAM,QAAQ,QAAQ;CACtB,IAAI,SAAS;CACb,IAAI,SAAS;CACb,IAAI,cAAc;CAClB,MAAMC,WAGF,EAAE;AAEN,MAAK,MAAM,UAAU,QACnB,UAAS,OAAO,QAAQ;EAAE,QAAQ;EAAG,QAAQ;EAAG,UAAU;EAAG,YAAY;EAAG;AAG9E,MAAK,MAAM,KAAK,SAAS;AACvB,iBAAe,EAAE;EACjB,IAAI,aAAa;AACjB,OAAK,MAAM,EAAE,QAAQ,YAAY,EAAE,QAAQ;GACzC,MAAM,SAAS,SAAS,WAAW;IAAE,QAAQ;IAAG,QAAQ;IAAG,UAAU;IAAG,YAAY;IAAG;AACvF,OAAI,OAAO,KAAM,QAAO,UAAU;QAC7B;AACH,WAAO,UAAU;AACjB,iBAAa;;AAEf,OAAI,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,OAAO,MAAM,EAAE;AACrE,WAAO,YAAY,OAAO;AAC1B,WAAO,cAAc;;AAEvB,YAAS,UAAU;;AAErB,MAAI,WAAY,WAAU;MACrB,WAAU;;CAGjB,MAAMC,kBAGF,EAAE;AACN,MAAK,MAAM,CAAC,QAAQ,MAAM,OAAO,QAAQ,SAAS,CAChD,iBAAgB,UAAU;EACxB,QAAQ,EAAE;EACV,QAAQ,EAAE;EACV,UAAU,EAAE,eAAe,IAAI,OAAO,EAAE,WAAW,EAAE;EACtD;AAGH,QAAO;EACL;EACA,SAAS;GACP;GACA;GACA;GACA,eAAe,UAAU,IAAI,IAAI,cAAc;GAC/C,UAAU,OAAO,OAAO,gBAAgB;GACzC;EACF;;AAGH,SAAS,eAAe,QAAuC;AAC7D,KAAI,QAAQ,YAAY,MAAM;EAC5B,MAAM,SAAS,OAAO;AACtB,QAAM,kBAAkB,QAAQ,yBAAS,IAAI,MAAM,mBAAmB"}
1
+ {"version":3,"file":"runner.js","names":["results: EvalCaseResult<I, O>[]","scores: EvalCaseResult<I, O>['scores'][number][]","byScorer: Record<\n string,\n { passed: number; failed: number; scoreSum: number; scoreCount: number }\n >","summaryByScorer: Record<\n string,\n { passed: number; failed: number; avgScore: number | null }\n >"],"sources":["../../src/eval/runner.ts"],"sourcesContent":["/**\n * `runEval(...)` - minimal inline eval runner. Walks every case in\n * the dataset, calls `agent.run(case.input)`, applies every supplied\n * scorer, and returns an aggregated {@link EvalReport}.\n *\n * The runner is deliberately tiny - it has no parallelism, no\n * retries, no reporters, no dataset loaders. Production scenarios\n * that need any of those should `pnpm add @graphorin/evals` (post-MVP).\n *\n * @packageDocumentation\n */\n\nimport type {\n Case,\n EvalCaseResult,\n EvalReport,\n RunEvalOptions,\n ScoreResult,\n Scorer,\n} from './types.js';\n\n/**\n * @stable\n */\nexport async function runEval<I, O>(opts: RunEvalOptions<I, O>): Promise<EvalReport<I, O>> {\n const iterations = Math.max(1, opts.iterations ?? 1);\n const signal = opts.signal;\n const results: EvalCaseResult<I, O>[] = [];\n\n for (let iter = 0; iter < iterations; iter++) {\n for (let idx = 0; idx < opts.dataset.cases.length; idx++) {\n throwIfAborted(signal);\n const sampleCase = opts.dataset.cases[idx];\n if (sampleCase === undefined) continue;\n // EB-6: disambiguate a caller-provided id per iteration too, not just the\n // synthetic fallback - otherwise iterations>1 emits duplicate caseIds.\n const baseId = sampleCase.id ?? `case-${idx}`;\n const caseId = iterations === 1 ? baseId : `${baseId}-iter-${iter}`;\n\n const startedAt = Date.now();\n const output = await opts.agent.run(sampleCase.input);\n const durationMs = Date.now() - startedAt;\n\n const scores: EvalCaseResult<I, O>['scores'][number][] = [];\n for (const scorer of opts.scorers) {\n throwIfAborted(signal);\n const result = await safeScore(scorer, sampleCase, output, durationMs);\n scores.push({ scorer: scorer.name, result });\n }\n\n results.push({\n caseId,\n input: sampleCase.input,\n output,\n durationMs,\n scores,\n });\n }\n }\n\n return summarize(results, opts.scorers);\n}\n\nasync function safeScore<I, O>(\n scorer: Scorer<I, O>,\n c: Case<I, O>,\n output: O,\n durationMs: number,\n): Promise<ScoreResult> {\n try {\n return await scorer.score({ case: c, output, durationMs });\n } catch (err) {\n return {\n pass: false,\n reason: `Scorer \"${scorer.name}\" threw: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n}\n\nfunction summarize<I, O>(\n results: ReadonlyArray<EvalCaseResult<I, O>>,\n scorers: ReadonlyArray<Scorer<I, O>>,\n): EvalReport<I, O> {\n const total = results.length;\n let passed = 0;\n let failed = 0;\n let durationSum = 0;\n const byScorer: Record<\n string,\n { passed: number; failed: number; scoreSum: number; scoreCount: number }\n > = {};\n\n for (const scorer of scorers) {\n byScorer[scorer.name] = { passed: 0, failed: 0, scoreSum: 0, scoreCount: 0 };\n }\n\n for (const r of results) {\n durationSum += r.durationMs;\n let passEntire = true;\n for (const { scorer, result } of r.scores) {\n const bucket = byScorer[scorer] ?? { passed: 0, failed: 0, scoreSum: 0, scoreCount: 0 };\n if (result.pass) bucket.passed += 1;\n else {\n bucket.failed += 1;\n passEntire = false;\n }\n if (typeof result.score === 'number' && Number.isFinite(result.score)) {\n bucket.scoreSum += result.score;\n bucket.scoreCount += 1;\n }\n byScorer[scorer] = bucket;\n }\n if (passEntire) passed += 1;\n else failed += 1;\n }\n\n const summaryByScorer: Record<\n string,\n { passed: number; failed: number; avgScore: number | null }\n > = {};\n for (const [scorer, b] of Object.entries(byScorer)) {\n summaryByScorer[scorer] = {\n passed: b.passed,\n failed: b.failed,\n avgScore: b.scoreCount === 0 ? null : b.scoreSum / b.scoreCount,\n };\n }\n\n return {\n results,\n summary: {\n total,\n passed,\n failed,\n avgDurationMs: total === 0 ? 0 : durationSum / total,\n byScorer: Object.freeze(summaryByScorer),\n },\n };\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted === true) {\n const reason = signal.reason;\n throw reason instanceof Error ? reason : new Error('Eval run aborted');\n }\n}\n"],"mappings":";;;;AAwBA,eAAsB,QAAc,MAAuD;CACzF,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,cAAc,EAAE;CACpD,MAAM,SAAS,KAAK;CACpB,MAAMA,UAAkC,EAAE;AAE1C,MAAK,IAAI,OAAO,GAAG,OAAO,YAAY,OACpC,MAAK,IAAI,MAAM,GAAG,MAAM,KAAK,QAAQ,MAAM,QAAQ,OAAO;AACxD,iBAAe,OAAO;EACtB,MAAM,aAAa,KAAK,QAAQ,MAAM;AACtC,MAAI,eAAe,OAAW;EAG9B,MAAM,SAAS,WAAW,MAAM,QAAQ;EACxC,MAAM,SAAS,eAAe,IAAI,SAAS,GAAG,OAAO,QAAQ;EAE7D,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,SAAS,MAAM,KAAK,MAAM,IAAI,WAAW,MAAM;EACrD,MAAM,aAAa,KAAK,KAAK,GAAG;EAEhC,MAAMC,SAAmD,EAAE;AAC3D,OAAK,MAAM,UAAU,KAAK,SAAS;AACjC,kBAAe,OAAO;GACtB,MAAM,SAAS,MAAM,UAAU,QAAQ,YAAY,QAAQ,WAAW;AACtE,UAAO,KAAK;IAAE,QAAQ,OAAO;IAAM;IAAQ,CAAC;;AAG9C,UAAQ,KAAK;GACX;GACA,OAAO,WAAW;GAClB;GACA;GACA;GACD,CAAC;;AAIN,QAAO,UAAU,SAAS,KAAK,QAAQ;;AAGzC,eAAe,UACb,QACA,GACA,QACA,YACsB;AACtB,KAAI;AACF,SAAO,MAAM,OAAO,MAAM;GAAE,MAAM;GAAG;GAAQ;GAAY,CAAC;UACnD,KAAK;AACZ,SAAO;GACL,MAAM;GACN,QAAQ,WAAW,OAAO,KAAK,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GAC3F;;;AAIL,SAAS,UACP,SACA,SACkB;CAClB,MAAM,QAAQ,QAAQ;CACtB,IAAI,SAAS;CACb,IAAI,SAAS;CACb,IAAI,cAAc;CAClB,MAAMC,WAGF,EAAE;AAEN,MAAK,MAAM,UAAU,QACnB,UAAS,OAAO,QAAQ;EAAE,QAAQ;EAAG,QAAQ;EAAG,UAAU;EAAG,YAAY;EAAG;AAG9E,MAAK,MAAM,KAAK,SAAS;AACvB,iBAAe,EAAE;EACjB,IAAI,aAAa;AACjB,OAAK,MAAM,EAAE,QAAQ,YAAY,EAAE,QAAQ;GACzC,MAAM,SAAS,SAAS,WAAW;IAAE,QAAQ;IAAG,QAAQ;IAAG,UAAU;IAAG,YAAY;IAAG;AACvF,OAAI,OAAO,KAAM,QAAO,UAAU;QAC7B;AACH,WAAO,UAAU;AACjB,iBAAa;;AAEf,OAAI,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,OAAO,MAAM,EAAE;AACrE,WAAO,YAAY,OAAO;AAC1B,WAAO,cAAc;;AAEvB,YAAS,UAAU;;AAErB,MAAI,WAAY,WAAU;MACrB,WAAU;;CAGjB,MAAMC,kBAGF,EAAE;AACN,MAAK,MAAM,CAAC,QAAQ,MAAM,OAAO,QAAQ,SAAS,CAChD,iBAAgB,UAAU;EACxB,QAAQ,EAAE;EACV,QAAQ,EAAE;EACV,UAAU,EAAE,eAAe,IAAI,OAAO,EAAE,WAAW,EAAE;EACtD;AAGH,QAAO;EACL;EACA,SAAS;GACP;GACA;GACA;GACA,eAAe,UAAU,IAAI,IAAI,cAAc;GAC/C,UAAU,OAAO,OAAO,gBAAgB;GACzC;EACF;;AAGH,SAAS,eAAe,QAAuC;AAC7D,KAAI,QAAQ,YAAY,MAAM;EAC5B,MAAM,SAAS,OAAO;AACtB,QAAM,kBAAkB,QAAQ,yBAAS,IAAI,MAAM,mBAAmB"}
@@ -83,9 +83,27 @@ interface EvalReport<I, O> {
83
83
  readonly failed: number;
84
84
  readonly avgScore: number | null;
85
85
  }>>;
86
+ /**
87
+ * 95% Wilson score interval on the overall pass rate (E8 / evals-05).
88
+ * Always present on reports produced by `runEvals`; optional so older
89
+ * persisted reports keep parsing.
90
+ */
91
+ readonly passRateCi?: {
92
+ readonly lo: number;
93
+ readonly hi: number;
94
+ };
95
+ /**
96
+ * pass^k stability metric - fraction of base cases whose EVERY repeat
97
+ * iteration passed. Present only when the run used `iterations > 1`.
98
+ */
99
+ readonly passHatK?: {
100
+ readonly k: number;
101
+ readonly baseCases: number;
102
+ readonly value: number;
103
+ };
86
104
  };
87
105
  /**
88
- * `true` when the run was cut short by an aborted signal `results` and
106
+ * `true` when the run was cut short by an aborted signal - `results` and
89
107
  * `summary` then cover only the cases that finished before the abort (a
90
108
  * partial report). Absent on a normal full run. See `runEvals`.
91
109
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/eval/types.ts"],"sourcesContent":[],"mappings":";;AAaA;;;;;;;AAUA;;;;AACwC,UAXvB,IAWuB,CAAA,CAAA,EAAA,IAAA,OAAA,EAAA,IAXE,QAWF,CAXW,MAWX,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,CAAA;EAAG,SAAA,EAAA,CAAA,EAAA,MAAA;EAAX,SAAA,KAAA,EATd,CASc;EAAd,SAAA,QAAA,CAAA,EARI,CAQJ;EAIO,SAAA,QAAA,CAAA,EAXH,CAWG;;AASzB;AAWA;;AAG2B,UA5BV,OA4BU,CAAA,CAAA,EAAA,IAAA,OAAA,EAAA,IA5BkB,QA4BlB,CA5B2B,MA4B3B,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,CAAA;EAAR,SAAA,KAAA,EA3BD,aA2BC,CA3Ba,IA2Bb,CA3BkB,CA2BlB,EA3BqB,CA2BrB,EA3BwB,CA2BxB,CAAA,CAAA;EACE,SAAA,QAAA,CAAA,EAAA;IAEP,SAAA,IAAA,CAAA,EAAA,MAAA;IAAR,SAAA,WAAA,CAAA,EAAA,MAAA;IAAO,SAAA,SAAA,CAAA,EA1BY,IA0BZ;EAQI,CAAA;;;;;;AAajB;AACiD,UAvChC,WAAA,CAuCgC;EAAG,SAAA,IAAA,EAAA,OAAA;EAAlB;EAAd,SAAA,KAAA,CAAA,EAAA,MAAA;EAOd,SAAA,MAAA,CAAA,EAAA,MAAA;EADiB,SAAA,QAAA,CAAA,EAxCD,QAwCC,CAxCQ,MAwCR,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA;;AAkBvB;;;AACgD,UArD/B,MAqD+B,CAAA,CAAA,EAAA,IAAA,OAAA,CAAA,CAAA;EACpB,SAAA,IAAA,EAAA,MAAA;EAAG,KAAA,CAAA,IAAA,EAAA;IAAX,SAAA,IAAA,EAnDD,IAmDC,CAnDI,CAmDJ,EAnDO,CAmDP,CAAA;IACqB,SAAA,MAAA,EAnDpB,CAmDoB;IAAG,SAAA,UAAA,EAAA,MAAA;EAAV,CAAA,CAAA,EAjD5B,OAiD4B,CAjDpB,WAiDoB,CAAA;;;;;;;UAzCjB;;kBAEC;mBACC;;mBAEA;;qBAA0D;;;;;;;;UAQ5D;oBACG,cAAc,eAAe,GAAG;;;;;;uBAM7B,SACjB;;;;;;;;;;;;;;;;UAiBW;;0BACyB,MAAM,QAAQ;;oBACpC,QAAQ,GAAG;oBACX,cAAc,OAAO,GAAG;;oBAExB"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/eval/types.ts"],"sourcesContent":[],"mappings":";;AAaA;;;;;;;AAUA;;;;AACwC,UAXvB,IAWuB,CAAA,CAAA,EAAA,IAAA,OAAA,EAAA,IAXE,QAWF,CAXW,MAWX,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,CAAA;EAAG,SAAA,EAAA,CAAA,EAAA,MAAA;EAAX,SAAA,KAAA,EATd,CASc;EAAd,SAAA,QAAA,CAAA,EARI,CAQJ;EAIO,SAAA,QAAA,CAAA,EAXH,CAWG;;AASzB;AAWA;;AAG2B,UA5BV,OA4BU,CAAA,CAAA,EAAA,IAAA,OAAA,EAAA,IA5BkB,QA4BlB,CA5B2B,MA4B3B,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,CAAA;EAAR,SAAA,KAAA,EA3BD,aA2BC,CA3Ba,IA2Bb,CA3BkB,CA2BlB,EA3BqB,CA2BrB,EA3BwB,CA2BxB,CAAA,CAAA;EACE,SAAA,QAAA,CAAA,EAAA;IAEP,SAAA,IAAA,CAAA,EAAA,MAAA;IAAR,SAAA,WAAA,CAAA,EAAA,MAAA;IAAO,SAAA,SAAA,CAAA,EA1BY,IA0BZ;EAQI,CAAA;;;;;;AAajB;AACiD,UAvChC,WAAA,CAuCgC;EAAG,SAAA,IAAA,EAAA,OAAA;EAAlB;EAAd,SAAA,KAAA,CAAA,EAAA,MAAA;EAOd,SAAA,MAAA,CAAA,EAAA,MAAA;EADiB,SAAA,QAAA,CAAA,EAxCD,QAwCC,CAxCQ,MAwCR,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA;;AAiCvB;;;AACgD,UApE/B,MAoE+B,CAAA,CAAA,EAAA,IAAA,OAAA,CAAA,CAAA;EACpB,SAAA,IAAA,EAAA,MAAA;EAAG,KAAA,CAAA,IAAA,EAAA;IAAX,SAAA,IAAA,EAlED,IAkEC,CAlEI,CAkEJ,EAlEO,CAkEP,CAAA;IACqB,SAAA,MAAA,EAlEpB,CAkEoB;IAAG,SAAA,UAAA,EAAA,MAAA;EAAV,CAAA,CAAA,EAhE5B,OAgE4B,CAhEpB,WAgEoB,CAAA;;;;;;;UAxDjB;;kBAEC;mBACC;;mBAEA;;qBAA0D;;;;;;;;UAQ5D;oBACG,cAAc,eAAe,GAAG;;;;;;uBAM7B,SACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAgCW;;0BACyB,MAAM,QAAQ;;oBACpC,QAAQ,GAAG;oBACX,cAAc,OAAO,GAAG;;oBAExB"}
@@ -1 +1 @@
1
- {"version":3,"file":"console.js","names":[],"sources":["../../src/exporters/console.ts"],"sourcesContent":["/**\n * `ConsoleExporter` pretty-prints finished spans to `console.log`.\n *\n * Useful for development and unit testing. Production deployments\n * should use {@link JSONLExporter} (for replay) or\n * {@link OTLPHttpExporter} (for remote OTLP collectors).\n *\n * @packageDocumentation\n */\n\nimport type { SpanRecord, TraceExporter } from './types.js';\n\n/**\n * Configuration shape for {@link createConsoleExporter}.\n *\n * @stable\n */\nexport interface ConsoleExporterOptions {\n /** Identifier reported via `exporter.id`. Defaults to `'console'`. */\n readonly id?: string;\n /** When `true`, emit JSON pretty-printed across multiple lines. */\n readonly pretty?: boolean;\n /** Custom sink. Defaults to `console.log`. */\n readonly sink?: (line: string) => void;\n}\n\n/**\n * Build a console-based trace exporter. Call `withValidation(exporter)`\n * before passing the result to `createTracer({ exporters })`.\n *\n * @stable\n */\nexport function createConsoleExporter(opts: ConsoleExporterOptions = {}): TraceExporter {\n const id = opts.id ?? 'console';\n const pretty = opts.pretty === true;\n const sink = opts.sink ?? ((line: string) => console.log(line));\n\n let closed = false;\n return {\n id,\n async export(record: SpanRecord): Promise<void> {\n if (closed) return;\n const payload = pretty\n ? JSON.stringify(serializableRecord(record), null, 2)\n : JSON.stringify(serializableRecord(record));\n sink(payload);\n },\n async flush(): Promise<void> {\n // Console.log is synchronous on Node; nothing to flush.\n },\n async shutdown(): Promise<void> {\n closed = true;\n },\n };\n}\n\n/**\n * @internal\n */\nexport function serializableRecord(record: SpanRecord): Record<string, unknown> {\n return {\n type: record.type,\n id: record.id,\n traceId: record.traceId,\n ...(record.parentId === undefined ? {} : { parentId: record.parentId }),\n name: record.name,\n startUnixNano: record.startUnixNano,\n endUnixNano: record.endUnixNano,\n status: record.status,\n ...(record.statusMessage === undefined ? {} : { statusMessage: record.statusMessage }),\n attributes: record.attributes,\n events: record.events,\n ...(record.droppedReason === undefined ? {} : { droppedReason: record.droppedReason }),\n };\n}\n"],"mappings":";;;;;;;AAgCA,SAAgB,sBAAsB,OAA+B,EAAE,EAAiB;CACtF,MAAM,KAAK,KAAK,MAAM;CACtB,MAAM,SAAS,KAAK,WAAW;CAC/B,MAAM,OAAO,KAAK,UAAU,SAAiB,QAAQ,IAAI,KAAK;CAE9D,IAAI,SAAS;AACb,QAAO;EACL;EACA,MAAM,OAAO,QAAmC;AAC9C,OAAI,OAAQ;AAIZ,QAHgB,SACZ,KAAK,UAAU,mBAAmB,OAAO,EAAE,MAAM,EAAE,GACnD,KAAK,UAAU,mBAAmB,OAAO,CAAC,CACjC;;EAEf,MAAM,QAAuB;EAG7B,MAAM,WAA0B;AAC9B,YAAS;;EAEZ;;;;;AAMH,SAAgB,mBAAmB,QAA6C;AAC9E,QAAO;EACL,MAAM,OAAO;EACb,IAAI,OAAO;EACX,SAAS,OAAO;EAChB,GAAI,OAAO,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,OAAO,UAAU;EACtE,MAAM,OAAO;EACb,eAAe,OAAO;EACtB,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,GAAI,OAAO,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe,OAAO,eAAe;EACrF,YAAY,OAAO;EACnB,QAAQ,OAAO;EACf,GAAI,OAAO,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe,OAAO,eAAe;EACtF"}
1
+ {"version":3,"file":"console.js","names":[],"sources":["../../src/exporters/console.ts"],"sourcesContent":["/**\n * `ConsoleExporter` - pretty-prints finished spans to `console.log`.\n *\n * Useful for development and unit testing. Production deployments\n * should use {@link JSONLExporter} (for replay) or\n * {@link OTLPHttpExporter} (for remote OTLP collectors).\n *\n * @packageDocumentation\n */\n\nimport type { SpanRecord, TraceExporter } from './types.js';\n\n/**\n * Configuration shape for {@link createConsoleExporter}.\n *\n * @stable\n */\nexport interface ConsoleExporterOptions {\n /** Identifier reported via `exporter.id`. Defaults to `'console'`. */\n readonly id?: string;\n /** When `true`, emit JSON pretty-printed across multiple lines. */\n readonly pretty?: boolean;\n /** Custom sink. Defaults to `console.log`. */\n readonly sink?: (line: string) => void;\n}\n\n/**\n * Build a console-based trace exporter. Call `withValidation(exporter)`\n * before passing the result to `createTracer({ exporters })`.\n *\n * @stable\n */\nexport function createConsoleExporter(opts: ConsoleExporterOptions = {}): TraceExporter {\n const id = opts.id ?? 'console';\n const pretty = opts.pretty === true;\n const sink = opts.sink ?? ((line: string) => console.log(line));\n\n let closed = false;\n return {\n id,\n async export(record: SpanRecord): Promise<void> {\n if (closed) return;\n const payload = pretty\n ? JSON.stringify(serializableRecord(record), null, 2)\n : JSON.stringify(serializableRecord(record));\n sink(payload);\n },\n async flush(): Promise<void> {\n // Console.log is synchronous on Node; nothing to flush.\n },\n async shutdown(): Promise<void> {\n closed = true;\n },\n };\n}\n\n/**\n * @internal\n */\nexport function serializableRecord(record: SpanRecord): Record<string, unknown> {\n return {\n type: record.type,\n id: record.id,\n traceId: record.traceId,\n ...(record.parentId === undefined ? {} : { parentId: record.parentId }),\n name: record.name,\n startUnixNano: record.startUnixNano,\n endUnixNano: record.endUnixNano,\n status: record.status,\n ...(record.statusMessage === undefined ? {} : { statusMessage: record.statusMessage }),\n attributes: record.attributes,\n events: record.events,\n ...(record.droppedReason === undefined ? {} : { droppedReason: record.droppedReason }),\n };\n}\n"],"mappings":";;;;;;;AAgCA,SAAgB,sBAAsB,OAA+B,EAAE,EAAiB;CACtF,MAAM,KAAK,KAAK,MAAM;CACtB,MAAM,SAAS,KAAK,WAAW;CAC/B,MAAM,OAAO,KAAK,UAAU,SAAiB,QAAQ,IAAI,KAAK;CAE9D,IAAI,SAAS;AACb,QAAO;EACL;EACA,MAAM,OAAO,QAAmC;AAC9C,OAAI,OAAQ;AAIZ,QAHgB,SACZ,KAAK,UAAU,mBAAmB,OAAO,EAAE,MAAM,EAAE,GACnD,KAAK,UAAU,mBAAmB,OAAO,CAAC,CACjC;;EAEf,MAAM,QAAuB;EAG7B,MAAM,WAA0B;AAC9B,YAAS;;EAEZ;;;;;AAMH,SAAgB,mBAAmB,QAA6C;AAC9E,QAAO;EACL,MAAM,OAAO;EACb,IAAI,OAAO;EACX,SAAS,OAAO;EAChB,GAAI,OAAO,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,OAAO,UAAU;EACtE,MAAM,OAAO;EACb,eAAe,OAAO;EACtB,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,GAAI,OAAO,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe,OAAO,eAAe;EACrF,YAAY,OAAO;EACnB,QAAQ,OAAO;EACf,GAAI,OAAO,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe,OAAO,eAAe;EACtF"}
@@ -2,7 +2,7 @@ import { SpanRecord, TraceExporter } from "./types.js";
2
2
 
3
3
  //#region src/exporters/jsonl.d.ts
4
4
 
5
- /** @internal used by tests to override the date provider. */
5
+ /** @internal - used by tests to override the date provider. */
6
6
  type DateProvider = () => Date;
7
7
  /**
8
8
  * Configuration shape for {@link createJSONLExporter}.
@@ -4,7 +4,7 @@ import { dirname, join } from "node:path";
4
4
 
5
5
  //#region src/exporters/jsonl.ts
6
6
  /**
7
- * `JSONLExporter` append-only newline-delimited JSON output. Used as
7
+ * `JSONLExporter` - append-only newline-delimited JSON output. Used as
8
8
  * the default trace replay log; encryption-at-rest is opt-in via the
9
9
  * encryption hook (Phase 16).
10
10
  *
@@ -15,7 +15,7 @@ import { dirname, join } from "node:path";
15
15
  * - flushes every write (no internal buffer) so the file is suitable
16
16
  * for tail-style replay even after a crash.
17
17
  *
18
- * The output is deterministic see {@link serializableRecord}.
18
+ * The output is deterministic - see {@link serializableRecord}.
19
19
  *
20
20
  * @packageDocumentation
21
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"jsonl.js","names":["entry: FileHandleEntry"],"sources":["../../src/exporters/jsonl.ts"],"sourcesContent":["/**\n * `JSONLExporter` append-only newline-delimited JSON output. Used as\n * the default trace replay log; encryption-at-rest is opt-in via the\n * encryption hook (Phase 16).\n *\n * The exporter:\n * - creates the target directory with mode `0700`,\n * - opens the per-session file with mode `0600`,\n * - rotates per UTC month + per session id (`YYYY-MM/<sessionId>.jsonl`),\n * - flushes every write (no internal buffer) so the file is suitable\n * for tail-style replay even after a crash.\n *\n * The output is deterministic see {@link serializableRecord}.\n *\n * @packageDocumentation\n */\n\nimport { mkdir, open } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { serializableRecord } from './console.js';\nimport type { SpanRecord, TraceExporter } from './types.js';\n\n/** @internal used by tests to override the date provider. */\nexport type DateProvider = () => Date;\n\n/**\n * Configuration shape for {@link createJSONLExporter}.\n *\n * @stable\n */\nexport interface JSONLExporterOptions {\n /** Identifier reported via `exporter.id`. Defaults to `'jsonl'`. */\n readonly id?: string;\n /** Root directory for the trace files. Created if missing. */\n readonly path: string;\n /**\n * Resolver that picks the file path for a given span record. Defaults\n * to `<rootPath>/<YYYY-MM>/<sessionId>.jsonl` keyed off the\n * `graphorin.session.id` attribute (or `unsessioned.jsonl` when\n * absent).\n */\n readonly resolveFilePath?: (record: SpanRecord, root: string) => string;\n /**\n * Override the date used when picking the rotation directory. Defaults\n * to `() => new Date()`.\n *\n * @internal\n */\n readonly now?: DateProvider;\n /**\n * Upper bound on simultaneously-open file handles (RP-20). The pool keys\n * by `(session, UTC-month)`, so a long-living server would otherwise hold\n * one fd per pair forever; the least-recently-used handle is closed once\n * the cap is reached. Defaults to `64`.\n */\n readonly maxOpenHandles?: number;\n}\n\n/**\n * A JSONL exporter, plus an introspection seam for the bounded handle pool.\n *\n * @stable\n */\nexport interface JSONLExporter extends TraceExporter {\n /** Number of file handles currently open in the pool (RP-20). */\n openHandleCount(): number;\n}\n\n/**\n * Build a JSONL trace exporter. Call `withValidation(exporter)` before\n * passing the result to `createTracer({ exporters })`.\n *\n * @stable\n */\nexport function createJSONLExporter(opts: JSONLExporterOptions): JSONLExporter {\n const id = opts.id ?? 'jsonl';\n const root = opts.path;\n const now = opts.now ?? (() => new Date());\n const resolveFilePath = opts.resolveFilePath ?? defaultPathResolver(now);\n const maxOpenHandles = Math.max(1, opts.maxOpenHandles ?? 64);\n\n // Pool of file handles, keyed by absolute path. Each handle is opened\n // in append mode the first time we touch the path; subsequent exports\n // re-use the existing handle. The pool is LRU-bounded (RP-20) the Map's\n // insertion order is the recency order, so the first entry is the LRU.\n const handles = new Map<string, FileHandleEntry>();\n let closed = false;\n\n return {\n id,\n async export(record: SpanRecord): Promise<void> {\n if (closed) return;\n const filePath = resolveFilePath(record, root);\n const entry = await openOrReuse(handles, filePath, maxOpenHandles);\n const line = `${JSON.stringify(serializableRecord(record))}\\n`;\n await entry.handle.write(line, null, 'utf8');\n },\n async flush(): Promise<void> {\n for (const entry of handles.values()) {\n await entry.handle.datasync();\n }\n },\n async shutdown(): Promise<void> {\n closed = true;\n for (const entry of handles.values()) {\n await entry.handle.close();\n }\n handles.clear();\n },\n openHandleCount(): number {\n return handles.size;\n },\n };\n}\n\ninterface FileHandleEntry {\n readonly handle: import('node:fs/promises').FileHandle;\n}\n\nasync function openOrReuse(\n pool: Map<string, FileHandleEntry>,\n filePath: string,\n maxOpenHandles: number,\n): Promise<FileHandleEntry> {\n const cached = pool.get(filePath);\n if (cached !== undefined) {\n // Mark most-recently-used by re-inserting at the tail of the Map.\n pool.delete(filePath);\n pool.set(filePath, cached);\n return cached;\n }\n\n // RP-20: evict least-recently-used handles before exceeding the cap so the\n // open-fd count stays bounded. An evicted path simply re-opens (append mode)\n // on its next export no data is lost.\n while (pool.size >= maxOpenHandles) {\n const lruKey = pool.keys().next().value;\n if (lruKey === undefined) break;\n const lru = pool.get(lruKey);\n pool.delete(lruKey);\n if (lru !== undefined) {\n try {\n await lru.handle.close();\n } catch {\n // Best-effort a failed close must not block the new open.\n }\n }\n }\n\n await mkdir(dirname(filePath), { recursive: true, mode: 0o700 });\n const handle = await open(filePath, 'a', 0o600);\n const entry: FileHandleEntry = { handle };\n pool.set(filePath, entry);\n return entry;\n}\n\nfunction defaultPathResolver(now: DateProvider) {\n return (record: SpanRecord, root: string): string => {\n const sessionAttr = record.attributes['graphorin.session.id'];\n const sessionId = typeof sessionAttr === 'string' ? sessionAttr : 'unsessioned';\n const date = now();\n const yyyy = String(date.getUTCFullYear());\n const mm = String(date.getUTCMonth() + 1).padStart(2, '0');\n const safeSession = sessionId.replace(/[^A-Za-z0-9._-]/g, '_');\n return join(root, `${yyyy}-${mm}`, `${safeSession}.jsonl`);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,oBAAoB,MAA2C;CAC7E,MAAM,KAAK,KAAK,MAAM;CACtB,MAAM,OAAO,KAAK;CAClB,MAAM,MAAM,KAAK,8BAAc,IAAI,MAAM;CACzC,MAAM,kBAAkB,KAAK,mBAAmB,oBAAoB,IAAI;CACxE,MAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,kBAAkB,GAAG;CAM7D,MAAM,0BAAU,IAAI,KAA8B;CAClD,IAAI,SAAS;AAEb,QAAO;EACL;EACA,MAAM,OAAO,QAAmC;AAC9C,OAAI,OAAQ;GAEZ,MAAM,QAAQ,MAAM,YAAY,SADf,gBAAgB,QAAQ,KAAK,EACK,eAAe;GAClE,MAAM,OAAO,GAAG,KAAK,UAAU,mBAAmB,OAAO,CAAC,CAAC;AAC3D,SAAM,MAAM,OAAO,MAAM,MAAM,MAAM,OAAO;;EAE9C,MAAM,QAAuB;AAC3B,QAAK,MAAM,SAAS,QAAQ,QAAQ,CAClC,OAAM,MAAM,OAAO,UAAU;;EAGjC,MAAM,WAA0B;AAC9B,YAAS;AACT,QAAK,MAAM,SAAS,QAAQ,QAAQ,CAClC,OAAM,MAAM,OAAO,OAAO;AAE5B,WAAQ,OAAO;;EAEjB,kBAA0B;AACxB,UAAO,QAAQ;;EAElB;;AAOH,eAAe,YACb,MACA,UACA,gBAC0B;CAC1B,MAAM,SAAS,KAAK,IAAI,SAAS;AACjC,KAAI,WAAW,QAAW;AAExB,OAAK,OAAO,SAAS;AACrB,OAAK,IAAI,UAAU,OAAO;AAC1B,SAAO;;AAMT,QAAO,KAAK,QAAQ,gBAAgB;EAClC,MAAM,SAAS,KAAK,MAAM,CAAC,MAAM,CAAC;AAClC,MAAI,WAAW,OAAW;EAC1B,MAAM,MAAM,KAAK,IAAI,OAAO;AAC5B,OAAK,OAAO,OAAO;AACnB,MAAI,QAAQ,OACV,KAAI;AACF,SAAM,IAAI,OAAO,OAAO;UAClB;;AAMZ,OAAM,MAAM,QAAQ,SAAS,EAAE;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;CAEhE,MAAMA,QAAyB,EAAE,QADlB,MAAM,KAAK,UAAU,KAAK,IAAM,EACN;AACzC,MAAK,IAAI,UAAU,MAAM;AACzB,QAAO;;AAGT,SAAS,oBAAoB,KAAmB;AAC9C,SAAQ,QAAoB,SAAyB;EACnD,MAAM,cAAc,OAAO,WAAW;EACtC,MAAM,YAAY,OAAO,gBAAgB,WAAW,cAAc;EAClE,MAAM,OAAO,KAAK;EAClB,MAAM,OAAO,OAAO,KAAK,gBAAgB,CAAC;EAC1C,MAAM,KAAK,OAAO,KAAK,aAAa,GAAG,EAAE,CAAC,SAAS,GAAG,IAAI;EAC1D,MAAM,cAAc,UAAU,QAAQ,oBAAoB,IAAI;AAC9D,SAAO,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,YAAY,QAAQ"}
1
+ {"version":3,"file":"jsonl.js","names":["entry: FileHandleEntry"],"sources":["../../src/exporters/jsonl.ts"],"sourcesContent":["/**\n * `JSONLExporter` - append-only newline-delimited JSON output. Used as\n * the default trace replay log; encryption-at-rest is opt-in via the\n * encryption hook (Phase 16).\n *\n * The exporter:\n * - creates the target directory with mode `0700`,\n * - opens the per-session file with mode `0600`,\n * - rotates per UTC month + per session id (`YYYY-MM/<sessionId>.jsonl`),\n * - flushes every write (no internal buffer) so the file is suitable\n * for tail-style replay even after a crash.\n *\n * The output is deterministic - see {@link serializableRecord}.\n *\n * @packageDocumentation\n */\n\nimport { mkdir, open } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { serializableRecord } from './console.js';\nimport type { SpanRecord, TraceExporter } from './types.js';\n\n/** @internal - used by tests to override the date provider. */\nexport type DateProvider = () => Date;\n\n/**\n * Configuration shape for {@link createJSONLExporter}.\n *\n * @stable\n */\nexport interface JSONLExporterOptions {\n /** Identifier reported via `exporter.id`. Defaults to `'jsonl'`. */\n readonly id?: string;\n /** Root directory for the trace files. Created if missing. */\n readonly path: string;\n /**\n * Resolver that picks the file path for a given span record. Defaults\n * to `<rootPath>/<YYYY-MM>/<sessionId>.jsonl` keyed off the\n * `graphorin.session.id` attribute (or `unsessioned.jsonl` when\n * absent).\n */\n readonly resolveFilePath?: (record: SpanRecord, root: string) => string;\n /**\n * Override the date used when picking the rotation directory. Defaults\n * to `() => new Date()`.\n *\n * @internal\n */\n readonly now?: DateProvider;\n /**\n * Upper bound on simultaneously-open file handles (RP-20). The pool keys\n * by `(session, UTC-month)`, so a long-living server would otherwise hold\n * one fd per pair forever; the least-recently-used handle is closed once\n * the cap is reached. Defaults to `64`.\n */\n readonly maxOpenHandles?: number;\n}\n\n/**\n * A JSONL exporter, plus an introspection seam for the bounded handle pool.\n *\n * @stable\n */\nexport interface JSONLExporter extends TraceExporter {\n /** Number of file handles currently open in the pool (RP-20). */\n openHandleCount(): number;\n}\n\n/**\n * Build a JSONL trace exporter. Call `withValidation(exporter)` before\n * passing the result to `createTracer({ exporters })`.\n *\n * @stable\n */\nexport function createJSONLExporter(opts: JSONLExporterOptions): JSONLExporter {\n const id = opts.id ?? 'jsonl';\n const root = opts.path;\n const now = opts.now ?? (() => new Date());\n const resolveFilePath = opts.resolveFilePath ?? defaultPathResolver(now);\n const maxOpenHandles = Math.max(1, opts.maxOpenHandles ?? 64);\n\n // Pool of file handles, keyed by absolute path. Each handle is opened\n // in append mode the first time we touch the path; subsequent exports\n // re-use the existing handle. The pool is LRU-bounded (RP-20) - the Map's\n // insertion order is the recency order, so the first entry is the LRU.\n const handles = new Map<string, FileHandleEntry>();\n let closed = false;\n\n return {\n id,\n async export(record: SpanRecord): Promise<void> {\n if (closed) return;\n const filePath = resolveFilePath(record, root);\n const entry = await openOrReuse(handles, filePath, maxOpenHandles);\n const line = `${JSON.stringify(serializableRecord(record))}\\n`;\n await entry.handle.write(line, null, 'utf8');\n },\n async flush(): Promise<void> {\n for (const entry of handles.values()) {\n await entry.handle.datasync();\n }\n },\n async shutdown(): Promise<void> {\n closed = true;\n for (const entry of handles.values()) {\n await entry.handle.close();\n }\n handles.clear();\n },\n openHandleCount(): number {\n return handles.size;\n },\n };\n}\n\ninterface FileHandleEntry {\n readonly handle: import('node:fs/promises').FileHandle;\n}\n\nasync function openOrReuse(\n pool: Map<string, FileHandleEntry>,\n filePath: string,\n maxOpenHandles: number,\n): Promise<FileHandleEntry> {\n const cached = pool.get(filePath);\n if (cached !== undefined) {\n // Mark most-recently-used by re-inserting at the tail of the Map.\n pool.delete(filePath);\n pool.set(filePath, cached);\n return cached;\n }\n\n // RP-20: evict least-recently-used handles before exceeding the cap so the\n // open-fd count stays bounded. An evicted path simply re-opens (append mode)\n // on its next export - no data is lost.\n while (pool.size >= maxOpenHandles) {\n const lruKey = pool.keys().next().value;\n if (lruKey === undefined) break;\n const lru = pool.get(lruKey);\n pool.delete(lruKey);\n if (lru !== undefined) {\n try {\n await lru.handle.close();\n } catch {\n // Best-effort - a failed close must not block the new open.\n }\n }\n }\n\n await mkdir(dirname(filePath), { recursive: true, mode: 0o700 });\n const handle = await open(filePath, 'a', 0o600);\n const entry: FileHandleEntry = { handle };\n pool.set(filePath, entry);\n return entry;\n}\n\nfunction defaultPathResolver(now: DateProvider) {\n return (record: SpanRecord, root: string): string => {\n const sessionAttr = record.attributes['graphorin.session.id'];\n const sessionId = typeof sessionAttr === 'string' ? sessionAttr : 'unsessioned';\n const date = now();\n const yyyy = String(date.getUTCFullYear());\n const mm = String(date.getUTCMonth() + 1).padStart(2, '0');\n const safeSession = sessionId.replace(/[^A-Za-z0-9._-]/g, '_');\n return join(root, `${yyyy}-${mm}`, `${safeSession}.jsonl`);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,oBAAoB,MAA2C;CAC7E,MAAM,KAAK,KAAK,MAAM;CACtB,MAAM,OAAO,KAAK;CAClB,MAAM,MAAM,KAAK,8BAAc,IAAI,MAAM;CACzC,MAAM,kBAAkB,KAAK,mBAAmB,oBAAoB,IAAI;CACxE,MAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,kBAAkB,GAAG;CAM7D,MAAM,0BAAU,IAAI,KAA8B;CAClD,IAAI,SAAS;AAEb,QAAO;EACL;EACA,MAAM,OAAO,QAAmC;AAC9C,OAAI,OAAQ;GAEZ,MAAM,QAAQ,MAAM,YAAY,SADf,gBAAgB,QAAQ,KAAK,EACK,eAAe;GAClE,MAAM,OAAO,GAAG,KAAK,UAAU,mBAAmB,OAAO,CAAC,CAAC;AAC3D,SAAM,MAAM,OAAO,MAAM,MAAM,MAAM,OAAO;;EAE9C,MAAM,QAAuB;AAC3B,QAAK,MAAM,SAAS,QAAQ,QAAQ,CAClC,OAAM,MAAM,OAAO,UAAU;;EAGjC,MAAM,WAA0B;AAC9B,YAAS;AACT,QAAK,MAAM,SAAS,QAAQ,QAAQ,CAClC,OAAM,MAAM,OAAO,OAAO;AAE5B,WAAQ,OAAO;;EAEjB,kBAA0B;AACxB,UAAO,QAAQ;;EAElB;;AAOH,eAAe,YACb,MACA,UACA,gBAC0B;CAC1B,MAAM,SAAS,KAAK,IAAI,SAAS;AACjC,KAAI,WAAW,QAAW;AAExB,OAAK,OAAO,SAAS;AACrB,OAAK,IAAI,UAAU,OAAO;AAC1B,SAAO;;AAMT,QAAO,KAAK,QAAQ,gBAAgB;EAClC,MAAM,SAAS,KAAK,MAAM,CAAC,MAAM,CAAC;AAClC,MAAI,WAAW,OAAW;EAC1B,MAAM,MAAM,KAAK,IAAI,OAAO;AAC5B,OAAK,OAAO,OAAO;AACnB,MAAI,QAAQ,OACV,KAAI;AACF,SAAM,IAAI,OAAO,OAAO;UAClB;;AAMZ,OAAM,MAAM,QAAQ,SAAS,EAAE;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;CAEhE,MAAMA,QAAyB,EAAE,QADlB,MAAM,KAAK,UAAU,KAAK,IAAM,EACN;AACzC,MAAK,IAAI,UAAU,MAAM;AACzB,QAAO;;AAGT,SAAS,oBAAoB,KAAmB;AAC9C,SAAQ,QAAoB,SAAyB;EACnD,MAAM,cAAc,OAAO,WAAW;EACtC,MAAM,YAAY,OAAO,gBAAgB,WAAW,cAAc;EAClE,MAAM,OAAO,KAAK;EAClB,MAAM,OAAO,OAAO,KAAK,gBAAgB,CAAC;EAC1C,MAAM,KAAK,OAAO,KAAK,aAAa,GAAG,EAAE,CAAC,SAAS,GAAG,IAAI;EAC1D,MAAM,cAAc,UAAU,QAAQ,oBAAoB,IAAI;AAC9D,SAAO,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,YAAY,QAAQ"}
@@ -16,7 +16,7 @@ interface OTLPHttpExporterOptions {
16
16
  readonly headers?: Readonly<Record<string, string>>;
17
17
  /** Service name embedded in the OTLP `Resource`. */
18
18
  readonly serviceName?: string;
19
- /** Optional `fetch` override useful for testing without the network. */
19
+ /** Optional `fetch` override - useful for testing without the network. */
20
20
  readonly fetchImpl?: typeof fetch;
21
21
  /** Optional timeout (ms). Defaults to 10000. */
22
22
  readonly timeoutMs?: number;
@@ -26,7 +26,7 @@ interface OTLPHttpExporterOptions {
26
26
  * `withValidation(exporter)` before passing the result to
27
27
  * `createTracer({ exporters })`.
28
28
  *
29
- * The implementation issues one `POST` per finished span no
29
+ * The implementation issues one `POST` per finished span - no
30
30
  * batching. Operators wanting batching should wrap this exporter
31
31
  * with their own queue or use a sidecar collector.
32
32
  *
@@ -1 +1 @@
1
- {"version":3,"file":"otlp-http.d.ts","names":[],"sources":["../../src/exporters/otlp-http.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UA4BiB,uBAAA;;;;;;qBAMI,SAAS;;;;8BAIA;;;;;;;;;;;;;;;iBAgBd,sBAAA,OAA6B,0BAA0B"}
1
+ {"version":3,"file":"otlp-http.d.ts","names":[],"sources":["../../src/exporters/otlp-http.ts"],"sourcesContent":[],"mappings":";;;;;;AA6BA;;;AAU8B,UAVb,uBAAA,CAUa;EAAK;EAgBnB,SAAA,EAAA,CAAA,EAAA,MAAA;;;;qBApBK,SAAS;;;;8BAIA;;;;;;;;;;;;;;;iBAgBd,sBAAA,OAA6B,0BAA0B"}
@@ -1,10 +1,12 @@
1
+ import { version } from "../package.js";
2
+
1
3
  //#region src/exporters/otlp-http.ts
2
4
  /**
3
5
  * Build a minimal OTLP-over-HTTP trace exporter. Call
4
6
  * `withValidation(exporter)` before passing the result to
5
7
  * `createTracer({ exporters })`.
6
8
  *
7
- * The implementation issues one `POST` per finished span no
9
+ * The implementation issues one `POST` per finished span - no
8
10
  * batching. Operators wanting batching should wrap this exporter
9
11
  * with their own queue or use a sidecar collector.
10
12
  *
@@ -48,7 +50,7 @@ function createOTLPHttpExporter(opts) {
48
50
  };
49
51
  }
50
52
  /**
51
- * @internal exposed for unit tests
53
+ * @internal - exposed for unit tests
52
54
  */
53
55
  function toOtlpEnvelope(record, serviceName) {
54
56
  const otelStatus = record.status === "ok" ? 1 : record.status === "error" ? 2 : 0;
@@ -58,12 +60,12 @@ function toOtlpEnvelope(record, serviceName) {
58
60
  value: { stringValue: serviceName }
59
61
  }, {
60
62
  key: "graphorin.framework.version",
61
- value: { stringValue: "0.5.0" }
63
+ value: { stringValue: version }
62
64
  }] },
63
65
  scopeSpans: [{
64
66
  scope: {
65
67
  name: "@graphorin/observability",
66
- version: "0.5.0"
68
+ version
67
69
  },
68
70
  spans: [{
69
71
  traceId: record.traceId,
@@ -1 +1 @@
1
- {"version":3,"file":"otlp-http.js","names":[],"sources":["../../src/exporters/otlp-http.ts"],"sourcesContent":["/**\n * `OTLPHttpExporter` reference implementation that POSTs span\n * records to an OpenTelemetry HTTP endpoint.\n *\n * The exporter is deliberately minimal: production hardening (retry\n * logic, batching policy tuning, gzip compression, bearer-token auth\n * rotation) is left to operators. The wire format follows the OTLP\n * `traces` resource shape and is compatible with collectors that accept\n * the `application/json` content type.\n *\n * Operators wanting the upstream OpenTelemetry SDK exporter\n * (`@opentelemetry/exporter-trace-otlp-http`) can adapt\n * {@link toOtlpEnvelope} into their own wrapper the helper is\n * exported so the OTel adapter does not need to duplicate the wire\n * shape.\n *\n * @packageDocumentation\n */\n\nimport type { SpanAttributes, SpanAttributeValue, SpanType } from '@graphorin/core';\n\nimport type { SpanRecord, SpanRecordEvent, TraceExporter } from './types.js';\n\n/**\n * Configuration shape for {@link createOTLPHttpExporter}.\n *\n * @stable\n */\nexport interface OTLPHttpExporterOptions {\n /** Identifier reported via `exporter.id`. Defaults to `'otlp-http'`. */\n readonly id?: string;\n /** OTLP collector URL (e.g. `http://localhost:4318/v1/traces`). */\n readonly url: string;\n /** Optional headers (auth, tenant id, …). Avoid passing secrets in plain. */\n readonly headers?: Readonly<Record<string, string>>;\n /** Service name embedded in the OTLP `Resource`. */\n readonly serviceName?: string;\n /** Optional `fetch` override useful for testing without the network. */\n readonly fetchImpl?: typeof fetch;\n /** Optional timeout (ms). Defaults to 10000. */\n readonly timeoutMs?: number;\n}\n\n/**\n * Build a minimal OTLP-over-HTTP trace exporter. Call\n * `withValidation(exporter)` before passing the result to\n * `createTracer({ exporters })`.\n *\n * The implementation issues one `POST` per finished span no\n * batching. Operators wanting batching should wrap this exporter\n * with their own queue or use a sidecar collector.\n *\n * @stable\n */\nexport function createOTLPHttpExporter(opts: OTLPHttpExporterOptions): TraceExporter {\n const id = opts.id ?? 'otlp-http';\n const url = opts.url;\n const headers = opts.headers ?? {};\n const serviceName = opts.serviceName ?? 'graphorin';\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n const timeoutMs = opts.timeoutMs ?? 10_000;\n let closed = false;\n\n if (typeof fetchImpl !== 'function') {\n throw new TypeError(\n 'createOTLPHttpExporter: no fetch implementation available. Provide ' +\n '`fetchImpl` explicitly or run on a Node.js version with global fetch ' +\n '(>= 18).',\n );\n }\n\n return {\n id,\n async export(record: SpanRecord): Promise<void> {\n if (closed) return;\n const body = toOtlpEnvelope(record, serviceName);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetchImpl(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...headers },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new Error(`OTLP exporter received HTTP ${res.status} ${res.statusText}`);\n }\n } finally {\n clearTimeout(timer);\n }\n },\n async flush(): Promise<void> {\n // No internal queue; each export() awaits the POST already.\n },\n async shutdown(): Promise<void> {\n closed = true;\n },\n };\n}\n\n/**\n * @internal exposed for unit tests\n */\nexport function toOtlpEnvelope(record: SpanRecord, serviceName: string): unknown {\n const otelStatus = record.status === 'ok' ? 1 : record.status === 'error' ? 2 : 0;\n return {\n resourceSpans: [\n {\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: serviceName } },\n { key: 'graphorin.framework.version', value: { stringValue: '0.5.0' } },\n ],\n },\n scopeSpans: [\n {\n scope: { name: '@graphorin/observability', version: '0.5.0' },\n spans: [\n {\n traceId: record.traceId,\n spanId: record.id,\n ...(record.parentId === undefined ? {} : { parentSpanId: record.parentId }),\n name: record.name,\n kind: spanKindFor(record.type),\n startTimeUnixNano: String(record.startUnixNano),\n endTimeUnixNano: String(record.endUnixNano),\n attributes: toOtlpAttributes(record.attributes),\n events: record.events.map(toOtlpEvent),\n status: {\n code: otelStatus,\n ...(record.statusMessage === undefined ? {} : { message: record.statusMessage }),\n },\n },\n ],\n },\n ],\n },\n ],\n };\n}\n\nfunction spanKindFor(type: SpanType): number {\n // OTel SpanKind enum: 0 unspecified, 1 internal, 2 server, 3 client, 4 producer, 5 consumer.\n if (type === 'provider.generate' || type === 'provider.stream' || type === 'mcp.call') return 3;\n if (type === 'tool.execute' || type === 'memory.embed') return 1;\n return 1;\n}\n\nfunction toOtlpAttributes(attrs: SpanAttributes): unknown[] {\n return Object.entries(attrs).map(([k, v]) => ({ key: k, value: toOtlpAttributeValue(v) }));\n}\n\nfunction toOtlpEvent(event: SpanRecordEvent): unknown {\n return {\n timeUnixNano: String(event.timeUnixNano),\n name: event.name,\n attributes: toOtlpAttributes(event.attributes),\n };\n}\n\nfunction toOtlpAttributeValue(v: SpanAttributeValue): unknown {\n if (typeof v === 'string') return { stringValue: v };\n if (typeof v === 'number') {\n return Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v };\n }\n if (typeof v === 'boolean') return { boolValue: v };\n if (v === null) return { stringValue: '' };\n if (Array.isArray(v)) {\n return {\n arrayValue: {\n values: v.map((item) => toOtlpAttributeValue(item as SpanAttributeValue)),\n },\n };\n }\n return { stringValue: String(v) };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"otlp-http.js","names":["pkg.version"],"sources":["../../src/exporters/otlp-http.ts"],"sourcesContent":["import pkg from '../../package.json' with { type: 'json' };\n/**\n * `OTLPHttpExporter` - reference implementation that POSTs span\n * records to an OpenTelemetry HTTP endpoint.\n *\n * The exporter is deliberately minimal: production hardening (retry\n * logic, batching policy tuning, gzip compression, bearer-token auth\n * rotation) is left to operators. The wire format follows the OTLP\n * `traces` resource shape and is compatible with collectors that accept\n * the `application/json` content type.\n *\n * Operators wanting the upstream OpenTelemetry SDK exporter\n * (`@opentelemetry/exporter-trace-otlp-http`) can adapt\n * {@link toOtlpEnvelope} into their own wrapper - the helper is\n * exported so the OTel adapter does not need to duplicate the wire\n * shape.\n *\n * @packageDocumentation\n */\n\nimport type { SpanAttributes, SpanAttributeValue, SpanType } from '@graphorin/core';\n\nimport type { SpanRecord, SpanRecordEvent, TraceExporter } from './types.js';\n\n/**\n * Configuration shape for {@link createOTLPHttpExporter}.\n *\n * @stable\n */\nexport interface OTLPHttpExporterOptions {\n /** Identifier reported via `exporter.id`. Defaults to `'otlp-http'`. */\n readonly id?: string;\n /** OTLP collector URL (e.g. `http://localhost:4318/v1/traces`). */\n readonly url: string;\n /** Optional headers (auth, tenant id, …). Avoid passing secrets in plain. */\n readonly headers?: Readonly<Record<string, string>>;\n /** Service name embedded in the OTLP `Resource`. */\n readonly serviceName?: string;\n /** Optional `fetch` override - useful for testing without the network. */\n readonly fetchImpl?: typeof fetch;\n /** Optional timeout (ms). Defaults to 10000. */\n readonly timeoutMs?: number;\n}\n\n/**\n * Build a minimal OTLP-over-HTTP trace exporter. Call\n * `withValidation(exporter)` before passing the result to\n * `createTracer({ exporters })`.\n *\n * The implementation issues one `POST` per finished span - no\n * batching. Operators wanting batching should wrap this exporter\n * with their own queue or use a sidecar collector.\n *\n * @stable\n */\nexport function createOTLPHttpExporter(opts: OTLPHttpExporterOptions): TraceExporter {\n const id = opts.id ?? 'otlp-http';\n const url = opts.url;\n const headers = opts.headers ?? {};\n const serviceName = opts.serviceName ?? 'graphorin';\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n const timeoutMs = opts.timeoutMs ?? 10_000;\n let closed = false;\n\n if (typeof fetchImpl !== 'function') {\n throw new TypeError(\n 'createOTLPHttpExporter: no fetch implementation available. Provide ' +\n '`fetchImpl` explicitly or run on a Node.js version with global fetch ' +\n '(>= 18).',\n );\n }\n\n return {\n id,\n async export(record: SpanRecord): Promise<void> {\n if (closed) return;\n const body = toOtlpEnvelope(record, serviceName);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetchImpl(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...headers },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new Error(`OTLP exporter received HTTP ${res.status} ${res.statusText}`);\n }\n } finally {\n clearTimeout(timer);\n }\n },\n async flush(): Promise<void> {\n // No internal queue; each export() awaits the POST already.\n },\n async shutdown(): Promise<void> {\n closed = true;\n },\n };\n}\n\n/**\n * @internal - exposed for unit tests\n */\nexport function toOtlpEnvelope(record: SpanRecord, serviceName: string): unknown {\n const otelStatus = record.status === 'ok' ? 1 : record.status === 'error' ? 2 : 0;\n return {\n resourceSpans: [\n {\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: serviceName } },\n { key: 'graphorin.framework.version', value: { stringValue: pkg.version } },\n ],\n },\n scopeSpans: [\n {\n scope: { name: '@graphorin/observability', version: pkg.version },\n spans: [\n {\n traceId: record.traceId,\n spanId: record.id,\n ...(record.parentId === undefined ? {} : { parentSpanId: record.parentId }),\n name: record.name,\n kind: spanKindFor(record.type),\n startTimeUnixNano: String(record.startUnixNano),\n endTimeUnixNano: String(record.endUnixNano),\n attributes: toOtlpAttributes(record.attributes),\n events: record.events.map(toOtlpEvent),\n status: {\n code: otelStatus,\n ...(record.statusMessage === undefined ? {} : { message: record.statusMessage }),\n },\n },\n ],\n },\n ],\n },\n ],\n };\n}\n\nfunction spanKindFor(type: SpanType): number {\n // OTel SpanKind enum: 0 unspecified, 1 internal, 2 server, 3 client, 4 producer, 5 consumer.\n if (type === 'provider.generate' || type === 'provider.stream' || type === 'mcp.call') return 3;\n if (type === 'tool.execute' || type === 'memory.embed') return 1;\n return 1;\n}\n\nfunction toOtlpAttributes(attrs: SpanAttributes): unknown[] {\n return Object.entries(attrs).map(([k, v]) => ({ key: k, value: toOtlpAttributeValue(v) }));\n}\n\nfunction toOtlpEvent(event: SpanRecordEvent): unknown {\n return {\n timeUnixNano: String(event.timeUnixNano),\n name: event.name,\n attributes: toOtlpAttributes(event.attributes),\n };\n}\n\nfunction toOtlpAttributeValue(v: SpanAttributeValue): unknown {\n if (typeof v === 'string') return { stringValue: v };\n if (typeof v === 'number') {\n return Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v };\n }\n if (typeof v === 'boolean') return { boolValue: v };\n if (v === null) return { stringValue: '' };\n if (Array.isArray(v)) {\n return {\n arrayValue: {\n values: v.map((item) => toOtlpAttributeValue(item as SpanAttributeValue)),\n },\n };\n }\n return { stringValue: String(v) };\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { SpanAttributeValue, SpanAttributes, SpanStatus, SpanType } from "@graph
3
3
  //#region src/exporters/types.d.ts
4
4
 
5
5
  /**
6
- * Discriminator marker every exporter that has been wrapped via
6
+ * Discriminator marker - every exporter that has been wrapped via
7
7
  * `withValidation(...)` is branded with this symbol so the tracer can
8
8
  * fail-fast at startup when a raw exporter is registered.
9
9
  *
@@ -29,7 +29,7 @@ interface SpanRecord<T extends SpanType = SpanType> {
29
29
  readonly statusMessage?: string;
30
30
  readonly attributes: SpanAttributes;
31
31
  readonly events: ReadonlyArray<SpanRecordEvent>;
32
- /** Optional per-attribute sensitivity map see `setAttribute({ sensitivity })`. */
32
+ /** Optional per-attribute sensitivity map - see `setAttribute({ sensitivity })`. */
33
33
  readonly sensitivityByAttribute?: Readonly<Record<string, SpanAttributeValue>>;
34
34
  /** Set when the validator dropped the span entirely (replay marker). */
35
35
  readonly droppedReason?: string;
@@ -1,6 +1,6 @@
1
1
  //#region src/exporters/types.ts
2
2
  /**
3
- * Discriminator marker every exporter that has been wrapped via
3
+ * Discriminator marker - every exporter that has been wrapped via
4
4
  * `withValidation(...)` is branded with this symbol so the tracer can
5
5
  * fail-fast at startup when a raw exporter is registered.
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["VALIDATED_EXPORTER_BRAND: unique symbol"],"sources":["../../src/exporters/types.ts"],"sourcesContent":["/**\n * Shared types for the exporter surface.\n *\n * @packageDocumentation\n */\n\nimport type { SpanAttributes, SpanAttributeValue, SpanStatus, SpanType } from '@graphorin/core';\n\n/**\n * Discriminator marker every exporter that has been wrapped via\n * `withValidation(...)` is branded with this symbol so the tracer can\n * fail-fast at startup when a raw exporter is registered.\n *\n * @stable\n */\nexport const VALIDATED_EXPORTER_BRAND: unique symbol = Symbol.for(\n 'graphorin.observability.exporter.validated',\n);\n\n/**\n * Sanitized, JSON-serialisable representation of a finished span. The\n * exporters never see the live OTel span; the tracer materialises this\n * record once the span ends and runs it through the validator.\n *\n * @stable\n */\nexport interface SpanRecord<T extends SpanType = SpanType> {\n readonly type: T;\n readonly id: string;\n readonly traceId: string;\n readonly parentId?: string;\n readonly name: string;\n readonly startUnixNano: number;\n readonly endUnixNano: number;\n readonly status: SpanStatus;\n readonly statusMessage?: string;\n readonly attributes: SpanAttributes;\n readonly events: ReadonlyArray<SpanRecordEvent>;\n /** Optional per-attribute sensitivity map see `setAttribute({ sensitivity })`. */\n readonly sensitivityByAttribute?: Readonly<Record<string, SpanAttributeValue>>;\n /** Set when the validator dropped the span entirely (replay marker). */\n readonly droppedReason?: string;\n}\n\n/**\n * Single span event carried alongside the span record.\n *\n * @stable\n */\nexport interface SpanRecordEvent {\n readonly name: string;\n readonly timeUnixNano: number;\n readonly attributes: SpanAttributes;\n}\n\n/**\n * A trace exporter contract. Exporters consume a stream of finished\n * spans and forward them to a sink (console, file, OTLP wire, …).\n *\n * @stable\n */\nexport interface TraceExporter {\n readonly id: string;\n /** Forward a finished span record. Implementations should be cheap. */\n export(record: SpanRecord): Promise<void>;\n /** Flush any buffered spans. Called on `tracer.shutdown()`. */\n flush(): Promise<void>;\n /** Close any underlying resources. Idempotent. */\n shutdown(): Promise<void>;\n /**\n * Branded-marker stub. Set by `withValidation(...)` to signal that the\n * exporter has been wrapped. Direct exporters omit the brand and the\n * tracer fails fast at startup.\n *\n * @internal\n */\n readonly [VALIDATED_EXPORTER_BRAND]?: true;\n}\n"],"mappings":";;;;;;;;AAeA,MAAaA,2BAA0C,OAAO,IAC5D,6CACD"}
1
+ {"version":3,"file":"types.js","names":["VALIDATED_EXPORTER_BRAND: unique symbol"],"sources":["../../src/exporters/types.ts"],"sourcesContent":["/**\n * Shared types for the exporter surface.\n *\n * @packageDocumentation\n */\n\nimport type { SpanAttributes, SpanAttributeValue, SpanStatus, SpanType } from '@graphorin/core';\n\n/**\n * Discriminator marker - every exporter that has been wrapped via\n * `withValidation(...)` is branded with this symbol so the tracer can\n * fail-fast at startup when a raw exporter is registered.\n *\n * @stable\n */\nexport const VALIDATED_EXPORTER_BRAND: unique symbol = Symbol.for(\n 'graphorin.observability.exporter.validated',\n);\n\n/**\n * Sanitized, JSON-serialisable representation of a finished span. The\n * exporters never see the live OTel span; the tracer materialises this\n * record once the span ends and runs it through the validator.\n *\n * @stable\n */\nexport interface SpanRecord<T extends SpanType = SpanType> {\n readonly type: T;\n readonly id: string;\n readonly traceId: string;\n readonly parentId?: string;\n readonly name: string;\n readonly startUnixNano: number;\n readonly endUnixNano: number;\n readonly status: SpanStatus;\n readonly statusMessage?: string;\n readonly attributes: SpanAttributes;\n readonly events: ReadonlyArray<SpanRecordEvent>;\n /** Optional per-attribute sensitivity map - see `setAttribute({ sensitivity })`. */\n readonly sensitivityByAttribute?: Readonly<Record<string, SpanAttributeValue>>;\n /** Set when the validator dropped the span entirely (replay marker). */\n readonly droppedReason?: string;\n}\n\n/**\n * Single span event carried alongside the span record.\n *\n * @stable\n */\nexport interface SpanRecordEvent {\n readonly name: string;\n readonly timeUnixNano: number;\n readonly attributes: SpanAttributes;\n}\n\n/**\n * A trace exporter contract. Exporters consume a stream of finished\n * spans and forward them to a sink (console, file, OTLP wire, …).\n *\n * @stable\n */\nexport interface TraceExporter {\n readonly id: string;\n /** Forward a finished span record. Implementations should be cheap. */\n export(record: SpanRecord): Promise<void>;\n /** Flush any buffered spans. Called on `tracer.shutdown()`. */\n flush(): Promise<void>;\n /** Close any underlying resources. Idempotent. */\n shutdown(): Promise<void>;\n /**\n * Branded-marker stub. Set by `withValidation(...)` to signal that the\n * exporter has been wrapped. Direct exporters omit the brand and the\n * tracer fails fast at startup.\n *\n * @internal\n */\n readonly [VALIDATED_EXPORTER_BRAND]?: true;\n}\n"],"mappings":";;;;;;;;AAeA,MAAaA,2BAA0C,OAAO,IAC5D,6CACD"}
@@ -13,7 +13,7 @@ interface WithValidationOptions extends RedactionValidatorOptions {
13
13
  /**
14
14
  * Optional pre-built validator. When supplied, the rest of the
15
15
  * options on this object are ignored and the supplied validator is
16
- * reused useful for sharing one validator across multiple exporters.
16
+ * reused - useful for sharing one validator across multiple exporters.
17
17
  */
18
18
  readonly validator?: RedactionValidatorInstance;
19
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"with-validation.js","names":["DEFAULT_ATTR_SENSITIVITY: Sensitivity","out: Record<string, SpanAttributeValue>","out: SpanRecord['events'][number][]"],"sources":["../../src/exporters/with-validation.ts"],"sourcesContent":["/**\n * `withValidation(exporter, opts)` the **mandatory wrapper** applied\n * to every exporter. Forwards each span record through a\n * `RedactionValidator` first; drops + counts when a value exceeds the\n * configured tier floor or matches a secret / PII pattern.\n *\n * @packageDocumentation\n */\n\nimport type { Sensitivity, SpanAttributes, SpanAttributeValue } from '@graphorin/core';\nimport type {\n RedactionCounters,\n RedactionValidatorInstance,\n RedactionValidatorOptions,\n} from '../redaction/types.js';\nimport { createRedactionValidator } from '../redaction/validator.js';\n\nimport { type SpanRecord, type TraceExporter, VALIDATED_EXPORTER_BRAND } from './types.js';\n\nconst DEFAULT_ATTR_SENSITIVITY: Sensitivity = 'internal';\n\n/**\n * Options for {@link withValidation}.\n *\n * @stable\n */\nexport interface WithValidationOptions extends RedactionValidatorOptions {\n /**\n * Optional pre-built validator. When supplied, the rest of the\n * options on this object are ignored and the supplied validator is\n * reused useful for sharing one validator across multiple exporters.\n */\n readonly validator?: RedactionValidatorInstance;\n}\n\n/**\n * Wrap an exporter so every span flows through a {@link RedactionValidator}\n * before reaching the sink. Exporters that are not wrapped are rejected\n * by the tracer at startup.\n *\n * @stable\n */\nexport function withValidation<E extends TraceExporter>(\n exporter: E,\n opts: WithValidationOptions = {},\n): TraceExporter {\n const validator = opts.validator ?? createRedactionValidator(opts);\n const wrapped: TraceExporter = {\n id: `${exporter.id}+validated`,\n [VALIDATED_EXPORTER_BRAND]: true,\n async export(record: SpanRecord): Promise<void> {\n // RP-18: the validator strips offending attributes (counting each drop)\n // and always returns an exportable record a single untagged or\n // over-tier attribute no longer makes the whole span vanish from every\n // exporter.\n await exporter.export(sanitizeRecord(record, validator));\n },\n flush: () => exporter.flush(),\n shutdown: () => exporter.shutdown(),\n };\n return wrapped;\n}\n\n/**\n * @internal\n */\nexport function sanitizeRecord(\n record: SpanRecord,\n validator: RedactionValidatorInstance,\n): SpanRecord {\n const sensitivities = record.sensitivityByAttribute ?? {};\n // RP-18: attribute-granular sanitization. Offending attributes are stripped\n // (and counted by the validator); the span itself always survives so\n // framework spans carrying untagged attributes still reach every exporter.\n const sanitizedAttrs = sanitizeAttributes(\n record.attributes,\n sensitivities,\n validator,\n record.type,\n );\n const sanitizedEvents = sanitizeEvents(record.events, validator, record.type);\n return {\n ...record,\n attributes: sanitizedAttrs,\n events: sanitizedEvents,\n };\n}\n\n/**\n * @internal\n */\nexport function sanitizeAttributes(\n attrs: SpanAttributes,\n sensitivities: Readonly<Record<string, SpanAttributeValue>>,\n validator: RedactionValidatorInstance,\n spanType?: string,\n): SpanAttributes {\n const out: Record<string, SpanAttributeValue> = {};\n for (const [key, value] of Object.entries(attrs)) {\n const tier = readTier(sensitivities[key]);\n const result = validator.validate({\n value,\n tier,\n context: spanType === undefined ? { attribute: key } : { attribute: key, spanType },\n });\n if (result === null) {\n // RP-18: this single attribute exceeded the floor (or matched a secret\n // pattern). Strip it the validator has already counted the drop and\n // keep the rest of the span rather than discarding the whole record.\n continue;\n }\n out[key] = result.value as SpanAttributeValue;\n }\n return Object.freeze(out) as SpanAttributes;\n}\n\n/**\n * @internal\n */\nexport function sanitizeEvents(\n events: SpanRecord['events'],\n validator: RedactionValidatorInstance,\n spanType?: string,\n): SpanRecord['events'] {\n const out: SpanRecord['events'][number][] = [];\n for (const event of events) {\n const sanitizedAttrs = sanitizeAttributes(event.attributes, {}, validator, spanType);\n out.push({ name: event.name, timeUnixNano: event.timeUnixNano, attributes: sanitizedAttrs });\n }\n return out;\n}\n\nfunction readTier(value: SpanAttributeValue | undefined): Sensitivity {\n if (value === 'public' || value === 'internal' || value === 'secret') return value;\n return DEFAULT_ATTR_SENSITIVITY;\n}\n\n/**\n * Quickly check whether an exporter is the result of a previous\n * {@link withValidation} call. The tracer uses this to fail fast at\n * startup.\n *\n * @stable\n */\nexport function isValidatedExporter(exporter: TraceExporter): boolean {\n return exporter[VALIDATED_EXPORTER_BRAND] === true;\n}\n\n/**\n * Pull the counters out of any exporter wrapped by {@link withValidation}.\n * Returns `null` for exporters that were never wrapped.\n *\n * @stable\n */\nexport function tryGetValidatorCounters(validator: RedactionValidatorInstance): RedactionCounters {\n return validator.counters();\n}\n"],"mappings":";;;;AAmBA,MAAMA,2BAAwC;;;;;;;;AAuB9C,SAAgB,eACd,UACA,OAA8B,EAAE,EACjB;CACf,MAAM,YAAY,KAAK,aAAa,yBAAyB,KAAK;AAclE,QAb+B;EAC7B,IAAI,GAAG,SAAS,GAAG;GAClB,2BAA2B;EAC5B,MAAM,OAAO,QAAmC;AAK9C,SAAM,SAAS,OAAO,eAAe,QAAQ,UAAU,CAAC;;EAE1D,aAAa,SAAS,OAAO;EAC7B,gBAAgB,SAAS,UAAU;EACpC;;;;;AAOH,SAAgB,eACd,QACA,WACY;CACZ,MAAM,gBAAgB,OAAO,0BAA0B,EAAE;CAIzD,MAAM,iBAAiB,mBACrB,OAAO,YACP,eACA,WACA,OAAO,KACR;CACD,MAAM,kBAAkB,eAAe,OAAO,QAAQ,WAAW,OAAO,KAAK;AAC7E,QAAO;EACL,GAAG;EACH,YAAY;EACZ,QAAQ;EACT;;;;;AAMH,SAAgB,mBACd,OACA,eACA,WACA,UACgB;CAChB,MAAMC,MAA0C,EAAE;AAClD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;EAChD,MAAM,OAAO,SAAS,cAAc,KAAK;EACzC,MAAM,SAAS,UAAU,SAAS;GAChC;GACA;GACA,SAAS,aAAa,SAAY,EAAE,WAAW,KAAK,GAAG;IAAE,WAAW;IAAK;IAAU;GACpF,CAAC;AACF,MAAI,WAAW,KAIb;AAEF,MAAI,OAAO,OAAO;;AAEpB,QAAO,OAAO,OAAO,IAAI;;;;;AAM3B,SAAgB,eACd,QACA,WACA,UACsB;CACtB,MAAMC,MAAsC,EAAE;AAC9C,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,iBAAiB,mBAAmB,MAAM,YAAY,EAAE,EAAE,WAAW,SAAS;AACpF,MAAI,KAAK;GAAE,MAAM,MAAM;GAAM,cAAc,MAAM;GAAc,YAAY;GAAgB,CAAC;;AAE9F,QAAO;;AAGT,SAAS,SAAS,OAAoD;AACpE,KAAI,UAAU,YAAY,UAAU,cAAc,UAAU,SAAU,QAAO;AAC7E,QAAO;;;;;;;;;AAUT,SAAgB,oBAAoB,UAAkC;AACpE,QAAO,SAAS,8BAA8B;;;;;;;;AAShD,SAAgB,wBAAwB,WAA0D;AAChG,QAAO,UAAU,UAAU"}
1
+ {"version":3,"file":"with-validation.js","names":["DEFAULT_ATTR_SENSITIVITY: Sensitivity","out: Record<string, SpanAttributeValue>","out: SpanRecord['events'][number][]"],"sources":["../../src/exporters/with-validation.ts"],"sourcesContent":["/**\n * `withValidation(exporter, opts)` - the **mandatory wrapper** applied\n * to every exporter. Forwards each span record through a\n * `RedactionValidator` first; drops + counts when a value exceeds the\n * configured tier floor or matches a secret / PII pattern.\n *\n * @packageDocumentation\n */\n\nimport type { Sensitivity, SpanAttributes, SpanAttributeValue } from '@graphorin/core';\nimport type {\n RedactionCounters,\n RedactionValidatorInstance,\n RedactionValidatorOptions,\n} from '../redaction/types.js';\nimport { createRedactionValidator } from '../redaction/validator.js';\n\nimport { type SpanRecord, type TraceExporter, VALIDATED_EXPORTER_BRAND } from './types.js';\n\nconst DEFAULT_ATTR_SENSITIVITY: Sensitivity = 'internal';\n\n/**\n * Options for {@link withValidation}.\n *\n * @stable\n */\nexport interface WithValidationOptions extends RedactionValidatorOptions {\n /**\n * Optional pre-built validator. When supplied, the rest of the\n * options on this object are ignored and the supplied validator is\n * reused - useful for sharing one validator across multiple exporters.\n */\n readonly validator?: RedactionValidatorInstance;\n}\n\n/**\n * Wrap an exporter so every span flows through a {@link RedactionValidator}\n * before reaching the sink. Exporters that are not wrapped are rejected\n * by the tracer at startup.\n *\n * @stable\n */\nexport function withValidation<E extends TraceExporter>(\n exporter: E,\n opts: WithValidationOptions = {},\n): TraceExporter {\n const validator = opts.validator ?? createRedactionValidator(opts);\n const wrapped: TraceExporter = {\n id: `${exporter.id}+validated`,\n [VALIDATED_EXPORTER_BRAND]: true,\n async export(record: SpanRecord): Promise<void> {\n // RP-18: the validator strips offending attributes (counting each drop)\n // and always returns an exportable record - a single untagged or\n // over-tier attribute no longer makes the whole span vanish from every\n // exporter.\n await exporter.export(sanitizeRecord(record, validator));\n },\n flush: () => exporter.flush(),\n shutdown: () => exporter.shutdown(),\n };\n return wrapped;\n}\n\n/**\n * @internal\n */\nexport function sanitizeRecord(\n record: SpanRecord,\n validator: RedactionValidatorInstance,\n): SpanRecord {\n const sensitivities = record.sensitivityByAttribute ?? {};\n // RP-18: attribute-granular sanitization. Offending attributes are stripped\n // (and counted by the validator); the span itself always survives so\n // framework spans carrying untagged attributes still reach every exporter.\n const sanitizedAttrs = sanitizeAttributes(\n record.attributes,\n sensitivities,\n validator,\n record.type,\n );\n const sanitizedEvents = sanitizeEvents(record.events, validator, record.type);\n return {\n ...record,\n attributes: sanitizedAttrs,\n events: sanitizedEvents,\n };\n}\n\n/**\n * @internal\n */\nexport function sanitizeAttributes(\n attrs: SpanAttributes,\n sensitivities: Readonly<Record<string, SpanAttributeValue>>,\n validator: RedactionValidatorInstance,\n spanType?: string,\n): SpanAttributes {\n const out: Record<string, SpanAttributeValue> = {};\n for (const [key, value] of Object.entries(attrs)) {\n const tier = readTier(sensitivities[key]);\n const result = validator.validate({\n value,\n tier,\n context: spanType === undefined ? { attribute: key } : { attribute: key, spanType },\n });\n if (result === null) {\n // RP-18: this single attribute exceeded the floor (or matched a secret\n // pattern). Strip it - the validator has already counted the drop - and\n // keep the rest of the span rather than discarding the whole record.\n continue;\n }\n out[key] = result.value as SpanAttributeValue;\n }\n return Object.freeze(out) as SpanAttributes;\n}\n\n/**\n * @internal\n */\nexport function sanitizeEvents(\n events: SpanRecord['events'],\n validator: RedactionValidatorInstance,\n spanType?: string,\n): SpanRecord['events'] {\n const out: SpanRecord['events'][number][] = [];\n for (const event of events) {\n const sanitizedAttrs = sanitizeAttributes(event.attributes, {}, validator, spanType);\n out.push({ name: event.name, timeUnixNano: event.timeUnixNano, attributes: sanitizedAttrs });\n }\n return out;\n}\n\nfunction readTier(value: SpanAttributeValue | undefined): Sensitivity {\n if (value === 'public' || value === 'internal' || value === 'secret') return value;\n return DEFAULT_ATTR_SENSITIVITY;\n}\n\n/**\n * Quickly check whether an exporter is the result of a previous\n * {@link withValidation} call. The tracer uses this to fail fast at\n * startup.\n *\n * @stable\n */\nexport function isValidatedExporter(exporter: TraceExporter): boolean {\n return exporter[VALIDATED_EXPORTER_BRAND] === true;\n}\n\n/**\n * Pull the counters out of any exporter wrapped by {@link withValidation}.\n * Returns `null` for exporters that were never wrapped.\n *\n * @stable\n */\nexport function tryGetValidatorCounters(validator: RedactionValidatorInstance): RedactionCounters {\n return validator.counters();\n}\n"],"mappings":";;;;AAmBA,MAAMA,2BAAwC;;;;;;;;AAuB9C,SAAgB,eACd,UACA,OAA8B,EAAE,EACjB;CACf,MAAM,YAAY,KAAK,aAAa,yBAAyB,KAAK;AAclE,QAb+B;EAC7B,IAAI,GAAG,SAAS,GAAG;GAClB,2BAA2B;EAC5B,MAAM,OAAO,QAAmC;AAK9C,SAAM,SAAS,OAAO,eAAe,QAAQ,UAAU,CAAC;;EAE1D,aAAa,SAAS,OAAO;EAC7B,gBAAgB,SAAS,UAAU;EACpC;;;;;AAOH,SAAgB,eACd,QACA,WACY;CACZ,MAAM,gBAAgB,OAAO,0BAA0B,EAAE;CAIzD,MAAM,iBAAiB,mBACrB,OAAO,YACP,eACA,WACA,OAAO,KACR;CACD,MAAM,kBAAkB,eAAe,OAAO,QAAQ,WAAW,OAAO,KAAK;AAC7E,QAAO;EACL,GAAG;EACH,YAAY;EACZ,QAAQ;EACT;;;;;AAMH,SAAgB,mBACd,OACA,eACA,WACA,UACgB;CAChB,MAAMC,MAA0C,EAAE;AAClD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;EAChD,MAAM,OAAO,SAAS,cAAc,KAAK;EACzC,MAAM,SAAS,UAAU,SAAS;GAChC;GACA;GACA,SAAS,aAAa,SAAY,EAAE,WAAW,KAAK,GAAG;IAAE,WAAW;IAAK;IAAU;GACpF,CAAC;AACF,MAAI,WAAW,KAIb;AAEF,MAAI,OAAO,OAAO;;AAEpB,QAAO,OAAO,OAAO,IAAI;;;;;AAM3B,SAAgB,eACd,QACA,WACA,UACsB;CACtB,MAAMC,MAAsC,EAAE;AAC9C,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,iBAAiB,mBAAmB,MAAM,YAAY,EAAE,EAAE,WAAW,SAAS;AACpF,MAAI,KAAK;GAAE,MAAM,MAAM;GAAM,cAAc,MAAM;GAAc,YAAY;GAAgB,CAAC;;AAE9F,QAAO;;AAGT,SAAS,SAAS,OAAoD;AACpE,KAAI,UAAU,YAAY,UAAU,cAAc,UAAU,SAAU,QAAO;AAC7E,QAAO;;;;;;;;;AAUT,SAAgB,oBAAoB,UAAkC;AACpE,QAAO,SAAS,8BAA8B;;;;;;;;AAShD,SAAgB,wBAAwB,WAA0D;AAChG,QAAO,UAAU,UAAU"}
@@ -13,7 +13,7 @@ import { AISpan, SpanType } from "@graphorin/core";
13
13
  declare function emitGenAIAttributes<T extends SpanType>(span: AISpan<T>, attrs: GenAIAttributes): void;
14
14
  /**
15
15
  * Emit per-message OpenTelemetry GenAI span events. The helper records
16
- * one event per message the per-message-event emission shape per the
16
+ * one event per message - the per-message-event emission shape per the
17
17
  * OTel semconv discipline (size-bounded individually; safer than the
18
18
  * aggregate-attribute shape on large prompts).
19
19
  *
@@ -47,7 +47,7 @@ function emitGenAIAttributes(span, attrs) {
47
47
  }
48
48
  /**
49
49
  * Emit per-message OpenTelemetry GenAI span events. The helper records
50
- * one event per message the per-message-event emission shape per the
50
+ * one event per message - the per-message-event emission shape per the
51
51
  * OTel semconv discipline (size-bounded individually; safer than the
52
52
  * aggregate-attribute shape on large prompts).
53
53
  *
@@ -1 +1 @@
1
- {"version":3,"file":"emit.js","names":["out: Record<string, SpanAttributeValue>"],"sources":["../../src/gen-ai/emit.ts"],"sourcesContent":["/**\n * Emission helpers that attach OpenTelemetry GenAI semantic-convention\n * attributes / events to an existing span.\n *\n * The helpers are deliberately additive they NEVER remove or rename\n * existing attributes. The new `gen_ai.*` family sits on top of the\n * Graphorin-prefixed family. Sensitivity defaults follow the catalogue\n * documented alongside the canonical mapping table:\n *\n * - `gen_ai.system` / `gen_ai.request.model` / `gen_ai.response.model` /\n * `gen_ai.response.id` / `gen_ai.usage.{input,output}_tokens` /\n * `gen_ai.response.finish_reasons` / `gen_ai.tool.{name,type,call.id,description}` /\n * `gen_ai.agent.{id,name}` / `gen_ai.session.id` /\n * `gen_ai.operation.name` → `'public'`\n * - `gen_ai.request.messages` / per-message events / `gen_ai.response.content`\n * → `'internal'` (default-deny non-public per DEC-141)\n * - `gen_ai.tool.output` → inherits per-tool `Tool.sensitivity`\n *\n * @packageDocumentation\n */\n\nimport type { AISpan, SpanAttributes, SpanAttributeValue, SpanType } from '@graphorin/core';\n\nimport type { GraphorinSpan } from '../tracer/span.js';\nimport { asGraphorinSpan } from '../tracer/tracer.js';\n\nimport { operationNameFor } from './operation-mapping.js';\nimport type { GenAIAttributes, GenAIMessage, GenAIMessageRole } from './types.js';\n\nconst PUBLIC_KEY_PREFIXES = [\n 'gen_ai.system',\n 'gen_ai.request.model',\n 'gen_ai.response.model',\n 'gen_ai.response.id',\n 'gen_ai.response.finish_reasons',\n 'gen_ai.usage.input_tokens',\n 'gen_ai.usage.output_tokens',\n 'gen_ai.tool.name',\n 'gen_ai.tool.type',\n 'gen_ai.tool.call.id',\n 'gen_ai.tool.description',\n 'gen_ai.agent.id',\n 'gen_ai.agent.name',\n 'gen_ai.session.id',\n 'gen_ai.operation.name',\n] as const;\n\n/**\n * Attach the canonical `gen_ai.*` attribute set to a span. The helper\n * is additive on the existing Graphorin-prefixed attributes and applies\n * the per-attribute sensitivity defaults catalogue.\n *\n * @stable\n */\nexport function emitGenAIAttributes<T extends SpanType>(\n span: AISpan<T>,\n attrs: GenAIAttributes,\n): void {\n const gs = asGraphorinSpan(span);\n const operation = attrs.operation ?? operationNameFor(span.type);\n if (operation !== undefined) setPublic(gs, span, 'gen_ai.operation.name', operation);\n\n if (attrs.system !== undefined) setPublic(gs, span, 'gen_ai.system', attrs.system);\n if (attrs.requestModel !== undefined)\n setPublic(gs, span, 'gen_ai.request.model', attrs.requestModel);\n if (attrs.responseModel !== undefined)\n setPublic(gs, span, 'gen_ai.response.model', attrs.responseModel);\n if (attrs.responseId !== undefined) setPublic(gs, span, 'gen_ai.response.id', attrs.responseId);\n if (attrs.inputTokens !== undefined)\n setPublic(gs, span, 'gen_ai.usage.input_tokens', attrs.inputTokens);\n if (attrs.outputTokens !== undefined)\n setPublic(gs, span, 'gen_ai.usage.output_tokens', attrs.outputTokens);\n if (attrs.finishReasons !== undefined)\n setPublic(gs, span, 'gen_ai.response.finish_reasons', [...attrs.finishReasons]);\n if (attrs.agentId !== undefined) setPublic(gs, span, 'gen_ai.agent.id', attrs.agentId);\n if (attrs.agentName !== undefined) setPublic(gs, span, 'gen_ai.agent.name', attrs.agentName);\n if (attrs.sessionId !== undefined) setPublic(gs, span, 'gen_ai.session.id', attrs.sessionId);\n if (attrs.toolName !== undefined) setPublic(gs, span, 'gen_ai.tool.name', attrs.toolName);\n if (attrs.toolType !== undefined) setPublic(gs, span, 'gen_ai.tool.type', attrs.toolType);\n if (attrs.toolCallId !== undefined) setPublic(gs, span, 'gen_ai.tool.call.id', attrs.toolCallId);\n if (attrs.toolDescription !== undefined)\n setPublic(gs, span, 'gen_ai.tool.description', attrs.toolDescription);\n}\n\n/**\n * Emit per-message OpenTelemetry GenAI span events. The helper records\n * one event per message the per-message-event emission shape per the\n * OTel semconv discipline (size-bounded individually; safer than the\n * aggregate-attribute shape on large prompts).\n *\n * @stable\n */\nexport function emitGenAIMessageEvents<T extends SpanType>(\n span: AISpan<T>,\n messages: ReadonlyArray<GenAIMessage>,\n opts: { readonly system?: string } = {},\n): void {\n for (const message of messages) {\n span.addEvent(eventNameFor(message.role), buildEventAttrs(message, opts.system));\n }\n}\n\nfunction eventNameFor(role: GenAIMessageRole): string {\n switch (role) {\n case 'system':\n return 'gen_ai.system.message';\n case 'user':\n return 'gen_ai.user.message';\n case 'assistant':\n return 'gen_ai.assistant.message';\n case 'tool':\n return 'gen_ai.tool.message';\n }\n}\n\nfunction buildEventAttrs(message: GenAIMessage, system?: string): SpanAttributes {\n const out: Record<string, SpanAttributeValue> = {\n 'gen_ai.message.role': message.role,\n content: message.content,\n };\n if (system !== undefined) out['gen_ai.system'] = system;\n if (message.name !== undefined) out['gen_ai.message.name'] = message.name;\n if (message.toolCallId !== undefined) out['gen_ai.tool.call.id'] = message.toolCallId;\n if (message.toolCalls !== undefined && message.toolCalls.length > 0) {\n out['gen_ai.tool.calls'] = JSON.stringify(message.toolCalls);\n }\n return Object.freeze(out) as SpanAttributes;\n}\n\nfunction setPublic(\n gs: GraphorinSpan | null,\n span: AISpan,\n key: string,\n value: SpanAttributeValue,\n): void {\n if (\n gs !== null &&\n PUBLIC_KEY_PREFIXES.some((prefix) => key === prefix || key.startsWith(`${prefix}.`))\n ) {\n gs.setAttribute(key, value, { sensitivity: 'public' });\n return;\n }\n span.setAttributes({ [key]: value });\n}\n"],"mappings":";;;;AA6BA,MAAM,sBAAsB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,SAAgB,oBACd,MACA,OACM;CACN,MAAM,KAAK,gBAAgB,KAAK;CAChC,MAAM,YAAY,MAAM,aAAa,iBAAiB,KAAK,KAAK;AAChE,KAAI,cAAc,OAAW,WAAU,IAAI,MAAM,yBAAyB,UAAU;AAEpF,KAAI,MAAM,WAAW,OAAW,WAAU,IAAI,MAAM,iBAAiB,MAAM,OAAO;AAClF,KAAI,MAAM,iBAAiB,OACzB,WAAU,IAAI,MAAM,wBAAwB,MAAM,aAAa;AACjE,KAAI,MAAM,kBAAkB,OAC1B,WAAU,IAAI,MAAM,yBAAyB,MAAM,cAAc;AACnE,KAAI,MAAM,eAAe,OAAW,WAAU,IAAI,MAAM,sBAAsB,MAAM,WAAW;AAC/F,KAAI,MAAM,gBAAgB,OACxB,WAAU,IAAI,MAAM,6BAA6B,MAAM,YAAY;AACrE,KAAI,MAAM,iBAAiB,OACzB,WAAU,IAAI,MAAM,8BAA8B,MAAM,aAAa;AACvE,KAAI,MAAM,kBAAkB,OAC1B,WAAU,IAAI,MAAM,kCAAkC,CAAC,GAAG,MAAM,cAAc,CAAC;AACjF,KAAI,MAAM,YAAY,OAAW,WAAU,IAAI,MAAM,mBAAmB,MAAM,QAAQ;AACtF,KAAI,MAAM,cAAc,OAAW,WAAU,IAAI,MAAM,qBAAqB,MAAM,UAAU;AAC5F,KAAI,MAAM,cAAc,OAAW,WAAU,IAAI,MAAM,qBAAqB,MAAM,UAAU;AAC5F,KAAI,MAAM,aAAa,OAAW,WAAU,IAAI,MAAM,oBAAoB,MAAM,SAAS;AACzF,KAAI,MAAM,aAAa,OAAW,WAAU,IAAI,MAAM,oBAAoB,MAAM,SAAS;AACzF,KAAI,MAAM,eAAe,OAAW,WAAU,IAAI,MAAM,uBAAuB,MAAM,WAAW;AAChG,KAAI,MAAM,oBAAoB,OAC5B,WAAU,IAAI,MAAM,2BAA2B,MAAM,gBAAgB;;;;;;;;;;AAWzE,SAAgB,uBACd,MACA,UACA,OAAqC,EAAE,EACjC;AACN,MAAK,MAAM,WAAW,SACpB,MAAK,SAAS,aAAa,QAAQ,KAAK,EAAE,gBAAgB,SAAS,KAAK,OAAO,CAAC;;AAIpF,SAAS,aAAa,MAAgC;AACpD,SAAQ,MAAR;EACE,KAAK,SACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,OACH,QAAO;;;AAIb,SAAS,gBAAgB,SAAuB,QAAiC;CAC/E,MAAMA,MAA0C;EAC9C,uBAAuB,QAAQ;EAC/B,SAAS,QAAQ;EAClB;AACD,KAAI,WAAW,OAAW,KAAI,mBAAmB;AACjD,KAAI,QAAQ,SAAS,OAAW,KAAI,yBAAyB,QAAQ;AACrE,KAAI,QAAQ,eAAe,OAAW,KAAI,yBAAyB,QAAQ;AAC3E,KAAI,QAAQ,cAAc,UAAa,QAAQ,UAAU,SAAS,EAChE,KAAI,uBAAuB,KAAK,UAAU,QAAQ,UAAU;AAE9D,QAAO,OAAO,OAAO,IAAI;;AAG3B,SAAS,UACP,IACA,MACA,KACA,OACM;AACN,KACE,OAAO,QACP,oBAAoB,MAAM,WAAW,QAAQ,UAAU,IAAI,WAAW,GAAG,OAAO,GAAG,CAAC,EACpF;AACA,KAAG,aAAa,KAAK,OAAO,EAAE,aAAa,UAAU,CAAC;AACtD;;AAEF,MAAK,cAAc,GAAG,MAAM,OAAO,CAAC"}
1
+ {"version":3,"file":"emit.js","names":["out: Record<string, SpanAttributeValue>"],"sources":["../../src/gen-ai/emit.ts"],"sourcesContent":["/**\n * Emission helpers that attach OpenTelemetry GenAI semantic-convention\n * attributes / events to an existing span.\n *\n * The helpers are deliberately additive - they NEVER remove or rename\n * existing attributes. The new `gen_ai.*` family sits on top of the\n * Graphorin-prefixed family. Sensitivity defaults follow the catalogue\n * documented alongside the canonical mapping table:\n *\n * - `gen_ai.system` / `gen_ai.request.model` / `gen_ai.response.model` /\n * `gen_ai.response.id` / `gen_ai.usage.{input,output}_tokens` /\n * `gen_ai.response.finish_reasons` / `gen_ai.tool.{name,type,call.id,description}` /\n * `gen_ai.agent.{id,name}` / `gen_ai.session.id` /\n * `gen_ai.operation.name` → `'public'`\n * - `gen_ai.request.messages` / per-message events / `gen_ai.response.content`\n * → `'internal'` (default-deny non-public per DEC-141)\n * - `gen_ai.tool.output` → inherits per-tool `Tool.sensitivity`\n *\n * @packageDocumentation\n */\n\nimport type { AISpan, SpanAttributes, SpanAttributeValue, SpanType } from '@graphorin/core';\n\nimport type { GraphorinSpan } from '../tracer/span.js';\nimport { asGraphorinSpan } from '../tracer/tracer.js';\n\nimport { operationNameFor } from './operation-mapping.js';\nimport type { GenAIAttributes, GenAIMessage, GenAIMessageRole } from './types.js';\n\nconst PUBLIC_KEY_PREFIXES = [\n 'gen_ai.system',\n 'gen_ai.request.model',\n 'gen_ai.response.model',\n 'gen_ai.response.id',\n 'gen_ai.response.finish_reasons',\n 'gen_ai.usage.input_tokens',\n 'gen_ai.usage.output_tokens',\n 'gen_ai.tool.name',\n 'gen_ai.tool.type',\n 'gen_ai.tool.call.id',\n 'gen_ai.tool.description',\n 'gen_ai.agent.id',\n 'gen_ai.agent.name',\n 'gen_ai.session.id',\n 'gen_ai.operation.name',\n] as const;\n\n/**\n * Attach the canonical `gen_ai.*` attribute set to a span. The helper\n * is additive on the existing Graphorin-prefixed attributes and applies\n * the per-attribute sensitivity defaults catalogue.\n *\n * @stable\n */\nexport function emitGenAIAttributes<T extends SpanType>(\n span: AISpan<T>,\n attrs: GenAIAttributes,\n): void {\n const gs = asGraphorinSpan(span);\n const operation = attrs.operation ?? operationNameFor(span.type);\n if (operation !== undefined) setPublic(gs, span, 'gen_ai.operation.name', operation);\n\n if (attrs.system !== undefined) setPublic(gs, span, 'gen_ai.system', attrs.system);\n if (attrs.requestModel !== undefined)\n setPublic(gs, span, 'gen_ai.request.model', attrs.requestModel);\n if (attrs.responseModel !== undefined)\n setPublic(gs, span, 'gen_ai.response.model', attrs.responseModel);\n if (attrs.responseId !== undefined) setPublic(gs, span, 'gen_ai.response.id', attrs.responseId);\n if (attrs.inputTokens !== undefined)\n setPublic(gs, span, 'gen_ai.usage.input_tokens', attrs.inputTokens);\n if (attrs.outputTokens !== undefined)\n setPublic(gs, span, 'gen_ai.usage.output_tokens', attrs.outputTokens);\n if (attrs.finishReasons !== undefined)\n setPublic(gs, span, 'gen_ai.response.finish_reasons', [...attrs.finishReasons]);\n if (attrs.agentId !== undefined) setPublic(gs, span, 'gen_ai.agent.id', attrs.agentId);\n if (attrs.agentName !== undefined) setPublic(gs, span, 'gen_ai.agent.name', attrs.agentName);\n if (attrs.sessionId !== undefined) setPublic(gs, span, 'gen_ai.session.id', attrs.sessionId);\n if (attrs.toolName !== undefined) setPublic(gs, span, 'gen_ai.tool.name', attrs.toolName);\n if (attrs.toolType !== undefined) setPublic(gs, span, 'gen_ai.tool.type', attrs.toolType);\n if (attrs.toolCallId !== undefined) setPublic(gs, span, 'gen_ai.tool.call.id', attrs.toolCallId);\n if (attrs.toolDescription !== undefined)\n setPublic(gs, span, 'gen_ai.tool.description', attrs.toolDescription);\n}\n\n/**\n * Emit per-message OpenTelemetry GenAI span events. The helper records\n * one event per message - the per-message-event emission shape per the\n * OTel semconv discipline (size-bounded individually; safer than the\n * aggregate-attribute shape on large prompts).\n *\n * @stable\n */\nexport function emitGenAIMessageEvents<T extends SpanType>(\n span: AISpan<T>,\n messages: ReadonlyArray<GenAIMessage>,\n opts: { readonly system?: string } = {},\n): void {\n for (const message of messages) {\n span.addEvent(eventNameFor(message.role), buildEventAttrs(message, opts.system));\n }\n}\n\nfunction eventNameFor(role: GenAIMessageRole): string {\n switch (role) {\n case 'system':\n return 'gen_ai.system.message';\n case 'user':\n return 'gen_ai.user.message';\n case 'assistant':\n return 'gen_ai.assistant.message';\n case 'tool':\n return 'gen_ai.tool.message';\n }\n}\n\nfunction buildEventAttrs(message: GenAIMessage, system?: string): SpanAttributes {\n const out: Record<string, SpanAttributeValue> = {\n 'gen_ai.message.role': message.role,\n content: message.content,\n };\n if (system !== undefined) out['gen_ai.system'] = system;\n if (message.name !== undefined) out['gen_ai.message.name'] = message.name;\n if (message.toolCallId !== undefined) out['gen_ai.tool.call.id'] = message.toolCallId;\n if (message.toolCalls !== undefined && message.toolCalls.length > 0) {\n out['gen_ai.tool.calls'] = JSON.stringify(message.toolCalls);\n }\n return Object.freeze(out) as SpanAttributes;\n}\n\nfunction setPublic(\n gs: GraphorinSpan | null,\n span: AISpan,\n key: string,\n value: SpanAttributeValue,\n): void {\n if (\n gs !== null &&\n PUBLIC_KEY_PREFIXES.some((prefix) => key === prefix || key.startsWith(`${prefix}.`))\n ) {\n gs.setAttribute(key, value, { sensitivity: 'public' });\n return;\n }\n span.setAttributes({ [key]: value });\n}\n"],"mappings":";;;;AA6BA,MAAM,sBAAsB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,SAAgB,oBACd,MACA,OACM;CACN,MAAM,KAAK,gBAAgB,KAAK;CAChC,MAAM,YAAY,MAAM,aAAa,iBAAiB,KAAK,KAAK;AAChE,KAAI,cAAc,OAAW,WAAU,IAAI,MAAM,yBAAyB,UAAU;AAEpF,KAAI,MAAM,WAAW,OAAW,WAAU,IAAI,MAAM,iBAAiB,MAAM,OAAO;AAClF,KAAI,MAAM,iBAAiB,OACzB,WAAU,IAAI,MAAM,wBAAwB,MAAM,aAAa;AACjE,KAAI,MAAM,kBAAkB,OAC1B,WAAU,IAAI,MAAM,yBAAyB,MAAM,cAAc;AACnE,KAAI,MAAM,eAAe,OAAW,WAAU,IAAI,MAAM,sBAAsB,MAAM,WAAW;AAC/F,KAAI,MAAM,gBAAgB,OACxB,WAAU,IAAI,MAAM,6BAA6B,MAAM,YAAY;AACrE,KAAI,MAAM,iBAAiB,OACzB,WAAU,IAAI,MAAM,8BAA8B,MAAM,aAAa;AACvE,KAAI,MAAM,kBAAkB,OAC1B,WAAU,IAAI,MAAM,kCAAkC,CAAC,GAAG,MAAM,cAAc,CAAC;AACjF,KAAI,MAAM,YAAY,OAAW,WAAU,IAAI,MAAM,mBAAmB,MAAM,QAAQ;AACtF,KAAI,MAAM,cAAc,OAAW,WAAU,IAAI,MAAM,qBAAqB,MAAM,UAAU;AAC5F,KAAI,MAAM,cAAc,OAAW,WAAU,IAAI,MAAM,qBAAqB,MAAM,UAAU;AAC5F,KAAI,MAAM,aAAa,OAAW,WAAU,IAAI,MAAM,oBAAoB,MAAM,SAAS;AACzF,KAAI,MAAM,aAAa,OAAW,WAAU,IAAI,MAAM,oBAAoB,MAAM,SAAS;AACzF,KAAI,MAAM,eAAe,OAAW,WAAU,IAAI,MAAM,uBAAuB,MAAM,WAAW;AAChG,KAAI,MAAM,oBAAoB,OAC5B,WAAU,IAAI,MAAM,2BAA2B,MAAM,gBAAgB;;;;;;;;;;AAWzE,SAAgB,uBACd,MACA,UACA,OAAqC,EAAE,EACjC;AACN,MAAK,MAAM,WAAW,SACpB,MAAK,SAAS,aAAa,QAAQ,KAAK,EAAE,gBAAgB,SAAS,KAAK,OAAO,CAAC;;AAIpF,SAAS,aAAa,MAAgC;AACpD,SAAQ,MAAR;EACE,KAAK,SACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,OACH,QAAO;;;AAIb,SAAS,gBAAgB,SAAuB,QAAiC;CAC/E,MAAMA,MAA0C;EAC9C,uBAAuB,QAAQ;EAC/B,SAAS,QAAQ;EAClB;AACD,KAAI,WAAW,OAAW,KAAI,mBAAmB;AACjD,KAAI,QAAQ,SAAS,OAAW,KAAI,yBAAyB,QAAQ;AACrE,KAAI,QAAQ,eAAe,OAAW,KAAI,yBAAyB,QAAQ;AAC3E,KAAI,QAAQ,cAAc,UAAa,QAAQ,UAAU,SAAS,EAChE,KAAI,uBAAuB,KAAK,UAAU,QAAQ,UAAU;AAE9D,QAAO,OAAO,OAAO,IAAI;;AAG3B,SAAS,UACP,IACA,MACA,KACA,OACM;AACN,KACE,OAAO,QACP,oBAAoB,MAAM,WAAW,QAAQ,UAAU,IAAI,WAAW,GAAG,OAAO,GAAG,CAAC,EACpF;AACA,KAAG,aAAa,KAAK,OAAO,EAAE,aAAa,UAAU,CAAC;AACtD;;AAEF,MAAK,cAAc,GAAG,MAAM,OAAO,CAAC"}
@@ -12,7 +12,7 @@ import { SpanType } from "@graphorin/core";
12
12
  */
13
13
  declare function operationNameFor(type: SpanType): GenAIOperationName | undefined;
14
14
  /**
15
- * Full canonical span-to-operation table exposed for tooling
15
+ * Full canonical span-to-operation table - exposed for tooling
16
16
  * (documentation generators, fixture tests) that need to introspect
17
17
  * the mapping.
18
18
  *
@@ -1 +1 @@
1
- {"version":3,"file":"operation-mapping.d.ts","names":[],"sources":["../../src/gen-ai/operation-mapping.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;iBA0EgB,gBAAA,OAAuB,WAAW;;;;;;;;cAWrC,sBAAsB,wBAAwB,UAAU"}
1
+ {"version":3,"file":"operation-mapping.d.ts","names":[],"sources":["../../src/gen-ai/operation-mapping.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;iBA6EgB,gBAAA,OAAuB,WAAW;;;;;;;;cAWrC,sBAAsB,wBAAwB,UAAU"}