@graphorin/observability 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +24 -4
  3. package/dist/cost/cost-tracker.d.ts.map +1 -1
  4. package/dist/cost/cost-tracker.js +34 -0
  5. package/dist/cost/cost-tracker.js.map +1 -1
  6. package/dist/cost/delegate.d.ts +40 -0
  7. package/dist/cost/delegate.d.ts.map +1 -0
  8. package/dist/cost/delegate.js +31 -0
  9. package/dist/cost/delegate.js.map +1 -0
  10. package/dist/cost/index.d.ts +2 -1
  11. package/dist/cost/index.js +2 -1
  12. package/dist/cost/types.d.ts +39 -0
  13. package/dist/cost/types.d.ts.map +1 -1
  14. package/dist/exporters/types.d.ts +7 -0
  15. package/dist/exporters/types.d.ts.map +1 -1
  16. package/dist/exporters/types.js.map +1 -1
  17. package/dist/exporters/with-validation.d.ts.map +1 -1
  18. package/dist/exporters/with-validation.js +5 -4
  19. package/dist/exporters/with-validation.js.map +1 -1
  20. package/dist/gen-ai/emit.js +9 -1
  21. package/dist/gen-ai/emit.js.map +1 -1
  22. package/dist/index.d.ts +2 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -8
  25. package/dist/index.js.map +1 -1
  26. package/dist/package.js +1 -1
  27. package/dist/package.js.map +1 -1
  28. package/dist/redaction/imperative-patterns.d.ts +3 -3
  29. package/dist/redaction/imperative-patterns.d.ts.map +1 -1
  30. package/dist/redaction/imperative-patterns.js +7 -0
  31. package/dist/redaction/imperative-patterns.js.map +1 -1
  32. package/dist/tracer/sampling.d.ts +6 -1
  33. package/dist/tracer/sampling.d.ts.map +1 -1
  34. package/dist/tracer/sampling.js +7 -1
  35. package/dist/tracer/sampling.js.map +1 -1
  36. package/dist/tracer/span.d.ts.map +1 -1
  37. package/dist/tracer/span.js +12 -3
  38. package/dist/tracer/span.js.map +1 -1
  39. package/package.json +18 -35
  40. package/src/cost/cost-tracker.ts +315 -0
  41. package/src/cost/delegate.ts +79 -0
  42. package/src/cost/index.ts +23 -0
  43. package/src/cost/types.ts +151 -0
  44. package/src/eval/index.ts +16 -0
  45. package/src/eval/runner.ts +146 -0
  46. package/src/eval/types.ts +122 -0
  47. package/src/exporters/console.ts +75 -0
  48. package/src/exporters/index.ts +36 -0
  49. package/src/exporters/jsonl.ts +167 -0
  50. package/src/exporters/otlp-http.ts +178 -0
  51. package/src/exporters/types.ts +85 -0
  52. package/src/exporters/with-validation.ts +176 -0
  53. package/src/gen-ai/emit.ts +157 -0
  54. package/src/gen-ai/index.ts +26 -0
  55. package/src/gen-ai/operation-mapping.ts +89 -0
  56. package/src/gen-ai/system-derivation.ts +84 -0
  57. package/src/gen-ai/types.ts +143 -0
  58. package/src/index.ts +36 -0
  59. package/src/logger/index.ts +13 -0
  60. package/src/logger/logger.ts +178 -0
  61. package/src/openinference/index.ts +133 -0
  62. package/src/redaction/config.ts +50 -0
  63. package/src/redaction/errors.ts +53 -0
  64. package/src/redaction/imperative-patterns.ts +268 -0
  65. package/src/redaction/index.ts +42 -0
  66. package/src/redaction/patterns.ts +263 -0
  67. package/src/redaction/types.ts +116 -0
  68. package/src/redaction/validator.ts +302 -0
  69. package/src/replay/config.ts +58 -0
  70. package/src/replay/index.ts +17 -0
  71. package/src/replay/log.ts +89 -0
  72. package/src/replay/replay.ts +196 -0
  73. package/src/replay/types.ts +112 -0
  74. package/src/telemetry/index.ts +94 -0
  75. package/src/tracer/ids.ts +27 -0
  76. package/src/tracer/index.ts +22 -0
  77. package/src/tracer/sampling.ts +170 -0
  78. package/src/tracer/span-names.ts +52 -0
  79. package/src/tracer/span.ts +175 -0
  80. package/src/tracer/tracer.ts +309 -0
@@ -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 // W-094: structural metadata (role, provider name, message name,\n // tool-call id) is 'public' so a message event survives the default\n // export floor as a marker; the CONTENT (and tool-call payloads)\n // stays 'internal' - exported only when the operator opted the\n // floor up, with the PII patterns applying there.\n span.addEvent(eventNameFor(message.role), buildEventAttrs(message, opts.system), {\n sensitivity: 'internal',\n sensitivityByAttribute: {\n 'gen_ai.message.role': 'public',\n 'gen_ai.system': 'public',\n 'gen_ai.message.name': 'public',\n 'gen_ai.tool.call.id': 'public',\n },\n });\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,SAMpB,MAAK,SAAS,aAAa,QAAQ,KAAK,EAAE,gBAAgB,SAAS,KAAK,OAAO,EAAE;EAC/E,aAAa;EACb,wBAAwB;GACtB,uBAAuB;GACvB,iBAAiB;GACjB,uBAAuB;GACvB,uBAAuB;GACxB;EACF,CAAC;;AAIN,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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { CostBudgetExceededCallback, CostBudgets, CostRecordInput, CostScope, CostSnapshot, CostTrackerOptions } from "./cost/types.js";
2
2
  import { CostTracker, createCostTracker } from "./cost/cost-tracker.js";
3
+ import { CostTrackerDelegateIds, ProviderUsageInfoLike, costTrackerUsageDelegate } from "./cost/delegate.js";
3
4
  import { Case, Dataset, EvalCaseResult, EvalReport, RunEvalOptions, ScoreResult, Scorer } from "./eval/types.js";
4
5
  import { runEval } from "./eval/runner.js";
5
6
  import { SpanRecord, SpanRecordEvent, TraceExporter, VALIDATED_EXPORTER_BRAND } from "./exporters/types.js";
@@ -53,5 +54,5 @@ import { GraphorinTracer, TracerOptions, asGraphorinSpan, createTracer } from ".
53
54
  */
54
55
  declare const VERSION: string;
55
56
  //#endregion
56
- export { ALL_BUILT_IN_PATTERNS, BUILT_IN_IMPERATIVE_PATTERNS, BUILT_IN_PATTERNS, BuiltInPatternName, Case, ConsoleExporterOptions, CostBudgetExceededCallback, CostBudgets, CostRecordInput, CostScope, CostSnapshot, CostTracker, CostTrackerOptions, DEFAULT_REPLAY_LOG_CONFIG, DEFAULT_VALIDATION_CONFIG, Dataset, EvalCaseResult, EvalReport, GenAIAttributes, GenAIMessage, GenAIMessageRole, GenAIOperationName, GenAISystem, GenAIToolType, GraphorinSpan, GraphorinTracer, IMPERATIVE_PREFILTER_SUBSTRINGS, ImperativePattern, ImperativePatternName, ScanResult as ImperativeScanResult, JSONLExporterOptions, LoggerFormat, LoggerOptions, OPEN_INFERENCE_EXCLUDED_TYPES, OPEN_INFERENCE_KIND_TABLE, OPERATION_NAME_TABLE, OPT_IN_PATTERNS, OTLPHttpExporterOptions, OpenInferenceSpanKind, PROVIDER_CLASS_TO_GEN_AI_SYSTEM, PatternCategory, PruneTracesOptions, RedactionCounters, RedactionInput, RedactionOutput, RedactionPattern, RedactionValidationError, RedactionValidator, RedactionValidatorInstance, RedactionValidatorOptions, RedactionViolation, RedactionViolationCallback, Replay, ReplayAuditBridge, ReplayAuditEvent, ReplayEvent, ReplayLogConfig, ReplayMode, ReplayOptions, ReplayRunInput, RunEvalOptions, Sampler, SamplingDecisionMaker, SamplingOptions, SamplingRule, ScoreResult, Scorer, SetAttributeOptions, SpanRecord, SpanRecordEvent, SpanTypeToOperationName, TelemetryStatus, TraceExporter, TracerOptions, UnvalidatedExporterError, VALIDATED_EXPORTER_BRAND, VERSION, ValidationConfig, WithValidationOptions, _resetGenAISystemWarningsForTesting, announceTelemetryPosture, asGraphorinSpan, compareSensitivityTiers, createConsoleExporter, createCostTracker, createJSONLExporter, createLogger, createOTLPHttpExporter, createRedactionValidator, createReplay, createSampler, createTracer, deriveGenAISystem, emitGenAIAttributes, emitGenAIMessageEvents, emitOpenInferenceKind, enableTelemetry, getCurrentSpanContext, getTelemetryStatus, getTraceLog, isValidatedExporter, newSpanId, newTraceId, openInferenceKindFor, operationNameFor, pruneTraces, runEval, scanImperativePatterns, setGenAISystemWarnSink, spanNameFor, stripImperativePatterns, tryGetValidatorCounters, withCurrentSpan, withValidation };
57
+ export { ALL_BUILT_IN_PATTERNS, BUILT_IN_IMPERATIVE_PATTERNS, BUILT_IN_PATTERNS, BuiltInPatternName, Case, ConsoleExporterOptions, CostBudgetExceededCallback, CostBudgets, CostRecordInput, CostScope, CostSnapshot, CostTracker, CostTrackerDelegateIds, CostTrackerOptions, DEFAULT_REPLAY_LOG_CONFIG, DEFAULT_VALIDATION_CONFIG, Dataset, EvalCaseResult, EvalReport, GenAIAttributes, GenAIMessage, GenAIMessageRole, GenAIOperationName, GenAISystem, GenAIToolType, GraphorinSpan, GraphorinTracer, IMPERATIVE_PREFILTER_SUBSTRINGS, ImperativePattern, ImperativePatternName, ScanResult as ImperativeScanResult, JSONLExporterOptions, LoggerFormat, LoggerOptions, OPEN_INFERENCE_EXCLUDED_TYPES, OPEN_INFERENCE_KIND_TABLE, OPERATION_NAME_TABLE, OPT_IN_PATTERNS, OTLPHttpExporterOptions, OpenInferenceSpanKind, PROVIDER_CLASS_TO_GEN_AI_SYSTEM, PatternCategory, ProviderUsageInfoLike, PruneTracesOptions, RedactionCounters, RedactionInput, RedactionOutput, RedactionPattern, RedactionValidationError, RedactionValidator, RedactionValidatorInstance, RedactionValidatorOptions, RedactionViolation, RedactionViolationCallback, Replay, ReplayAuditBridge, ReplayAuditEvent, ReplayEvent, ReplayLogConfig, ReplayMode, ReplayOptions, ReplayRunInput, RunEvalOptions, Sampler, SamplingDecisionMaker, SamplingOptions, SamplingRule, ScoreResult, Scorer, SetAttributeOptions, SpanRecord, SpanRecordEvent, SpanTypeToOperationName, TelemetryStatus, TraceExporter, TracerOptions, UnvalidatedExporterError, VALIDATED_EXPORTER_BRAND, VERSION, ValidationConfig, WithValidationOptions, _resetGenAISystemWarningsForTesting, announceTelemetryPosture, asGraphorinSpan, compareSensitivityTiers, costTrackerUsageDelegate, createConsoleExporter, createCostTracker, createJSONLExporter, createLogger, createOTLPHttpExporter, createRedactionValidator, createReplay, createSampler, createTracer, deriveGenAISystem, emitGenAIAttributes, emitGenAIMessageEvents, emitOpenInferenceKind, enableTelemetry, getCurrentSpanContext, getTelemetryStatus, getTraceLog, isValidatedExporter, newSpanId, newTraceId, openInferenceKindFor, operationNameFor, pruneTraces, runEval, scanImperativePatterns, setGenAISystemWarnSink, spanNameFor, stripImperativePatterns, tryGetValidatorCounters, withCurrentSpan, withValidation };
57
58
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAwBa"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAwBa"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { version } from "./package.js";
2
2
  import { createCostTracker } from "./cost/cost-tracker.js";
3
- import "./cost/index.js";
3
+ import { costTrackerUsageDelegate } from "./cost/delegate.js";
4
4
  import { runEval } from "./eval/runner.js";
5
5
  import { createConsoleExporter } from "./exporters/console.js";
6
6
  import { createJSONLExporter } from "./exporters/jsonl.js";
@@ -10,7 +10,6 @@ import { RedactionValidationError, UnvalidatedExporterError } from "./redaction/
10
10
  import { ALL_BUILT_IN_PATTERNS, BUILT_IN_PATTERNS, OPT_IN_PATTERNS } from "./redaction/patterns.js";
11
11
  import { compareSensitivityTiers, createRedactionValidator } from "./redaction/validator.js";
12
12
  import { isValidatedExporter, tryGetValidatorCounters, withValidation } from "./exporters/with-validation.js";
13
- import "./exporters/index.js";
14
13
  import { newSpanId, newTraceId } from "./tracer/ids.js";
15
14
  import { createSampler } from "./tracer/sampling.js";
16
15
  import { spanNameFor } from "./tracer/span-names.js";
@@ -18,19 +17,14 @@ import { asGraphorinSpan, createTracer } from "./tracer/tracer.js";
18
17
  import { OPERATION_NAME_TABLE, operationNameFor } from "./gen-ai/operation-mapping.js";
19
18
  import { emitGenAIAttributes, emitGenAIMessageEvents } from "./gen-ai/emit.js";
20
19
  import { PROVIDER_CLASS_TO_GEN_AI_SYSTEM, _resetGenAISystemWarningsForTesting, deriveGenAISystem, setGenAISystemWarnSink } from "./gen-ai/system-derivation.js";
21
- import "./gen-ai/index.js";
22
20
  import { createLogger, getCurrentSpanContext, withCurrentSpan } from "./logger/logger.js";
23
- import "./logger/index.js";
24
21
  import { OPEN_INFERENCE_EXCLUDED_TYPES, OPEN_INFERENCE_KIND_TABLE, emitOpenInferenceKind, openInferenceKindFor } from "./openinference/index.js";
25
22
  import { DEFAULT_VALIDATION_CONFIG } from "./redaction/config.js";
26
23
  import { BUILT_IN_IMPERATIVE_PATTERNS, IMPERATIVE_PREFILTER_SUBSTRINGS, scanImperativePatterns, stripImperativePatterns } from "./redaction/imperative-patterns.js";
27
- import "./redaction/index.js";
28
24
  import { DEFAULT_REPLAY_LOG_CONFIG } from "./replay/config.js";
29
25
  import { getTraceLog, pruneTraces } from "./replay/log.js";
30
26
  import { createReplay } from "./replay/replay.js";
31
- import "./replay/index.js";
32
27
  import { announceTelemetryPosture, enableTelemetry, getTelemetryStatus } from "./telemetry/index.js";
33
- import "./tracer/index.js";
34
28
 
35
29
  //#region src/index.ts
36
30
  /**
@@ -57,5 +51,5 @@ import "./tracer/index.js";
57
51
  const VERSION = version;
58
52
 
59
53
  //#endregion
60
- export { ALL_BUILT_IN_PATTERNS, BUILT_IN_IMPERATIVE_PATTERNS, BUILT_IN_PATTERNS, DEFAULT_REPLAY_LOG_CONFIG, DEFAULT_VALIDATION_CONFIG, IMPERATIVE_PREFILTER_SUBSTRINGS, OPEN_INFERENCE_EXCLUDED_TYPES, OPEN_INFERENCE_KIND_TABLE, OPERATION_NAME_TABLE, OPT_IN_PATTERNS, PROVIDER_CLASS_TO_GEN_AI_SYSTEM, RedactionValidationError, UnvalidatedExporterError, VALIDATED_EXPORTER_BRAND, VERSION, _resetGenAISystemWarningsForTesting, announceTelemetryPosture, asGraphorinSpan, compareSensitivityTiers, createConsoleExporter, createCostTracker, createJSONLExporter, createLogger, createOTLPHttpExporter, createRedactionValidator, createReplay, createSampler, createTracer, deriveGenAISystem, emitGenAIAttributes, emitGenAIMessageEvents, emitOpenInferenceKind, enableTelemetry, getCurrentSpanContext, getTelemetryStatus, getTraceLog, isValidatedExporter, newSpanId, newTraceId, openInferenceKindFor, operationNameFor, pruneTraces, runEval, scanImperativePatterns, setGenAISystemWarnSink, spanNameFor, stripImperativePatterns, tryGetValidatorCounters, withCurrentSpan, withValidation };
54
+ export { ALL_BUILT_IN_PATTERNS, BUILT_IN_IMPERATIVE_PATTERNS, BUILT_IN_PATTERNS, DEFAULT_REPLAY_LOG_CONFIG, DEFAULT_VALIDATION_CONFIG, IMPERATIVE_PREFILTER_SUBSTRINGS, OPEN_INFERENCE_EXCLUDED_TYPES, OPEN_INFERENCE_KIND_TABLE, OPERATION_NAME_TABLE, OPT_IN_PATTERNS, PROVIDER_CLASS_TO_GEN_AI_SYSTEM, RedactionValidationError, UnvalidatedExporterError, VALIDATED_EXPORTER_BRAND, VERSION, _resetGenAISystemWarningsForTesting, announceTelemetryPosture, asGraphorinSpan, compareSensitivityTiers, costTrackerUsageDelegate, createConsoleExporter, createCostTracker, createJSONLExporter, createLogger, createOTLPHttpExporter, createRedactionValidator, createReplay, createSampler, createTracer, deriveGenAISystem, emitGenAIAttributes, emitGenAIMessageEvents, emitOpenInferenceKind, enableTelemetry, getCurrentSpanContext, getTelemetryStatus, getTraceLog, isValidatedExporter, newSpanId, newTraceId, openInferenceKindFor, operationNameFor, pruneTraces, runEval, scanImperativePatterns, setGenAISystemWarnSink, spanNameFor, stripImperativePatterns, tryGetValidatorCounters, withCurrentSpan, withValidation };
61
55
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["VERSION: string","pkg.version"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @graphorin/observability - observability primitives for the\n * Graphorin framework. Ships:\n *\n * - the typed `AISpan<T>` tracer over OpenTelemetry,\n * - the **mandatory** `withValidation()` wrapper for every exporter,\n * - the sensitivity-aware `RedactionValidator` with 14 built-in PII /\n * secret detection patterns,\n * - OpenTelemetry GenAI semantic-conventions conformance helpers,\n * - OpenInference span-kind emission,\n * - the structured `Logger` with span correlation,\n * - the append-only `JSONLExporter` for replay,\n * - sanitized-by-default `Replay` primitives,\n * - the hierarchical `CostTracker`,\n * - and a minimal inline eval runner.\n *\n * The full documentation lives in the package `README.md`.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant, derived from `package.json` at build time. */\nimport pkg from '../package.json' with { type: 'json' };\n\nexport const VERSION: string = pkg.version;\n\nexport * from './cost/index.js';\nexport * from './eval/index.js';\nexport * from './exporters/index.js';\nexport * from './gen-ai/index.js';\nexport * from './logger/index.js';\nexport * from './openinference/index.js';\nexport * from './redaction/index.js';\nexport * from './replay/index.js';\nexport * from './telemetry/index.js';\nexport * from './tracer/index.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAaA,UAAkBC"}
1
+ {"version":3,"file":"index.js","names":["VERSION: string","pkg.version"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @graphorin/observability - observability primitives for the\n * Graphorin framework. Ships:\n *\n * - the typed `AISpan<T>` tracer over OpenTelemetry,\n * - the **mandatory** `withValidation()` wrapper for every exporter,\n * - the sensitivity-aware `RedactionValidator` with 14 built-in PII /\n * secret detection patterns,\n * - OpenTelemetry GenAI semantic-conventions conformance helpers,\n * - OpenInference span-kind emission,\n * - the structured `Logger` with span correlation,\n * - the append-only `JSONLExporter` for replay,\n * - sanitized-by-default `Replay` primitives,\n * - the hierarchical `CostTracker`,\n * - and a minimal inline eval runner.\n *\n * The full documentation lives in the package `README.md`.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant, derived from `package.json` at build time. */\nimport pkg from '../package.json' with { type: 'json' };\n\nexport const VERSION: string = pkg.version;\n\nexport * from './cost/index.js';\nexport * from './eval/index.js';\nexport * from './exporters/index.js';\nexport * from './gen-ai/index.js';\nexport * from './logger/index.js';\nexport * from './openinference/index.js';\nexport * from './redaction/index.js';\nexport * from './replay/index.js';\nexport * from './telemetry/index.js';\nexport * from './tracer/index.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAaA,UAAkBC"}
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.6.1";
2
+ var version = "0.7.0";
3
3
 
4
4
  //#endregion
5
5
  export { version };
@@ -1 +1 @@
1
- {"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@graphorin/observability\",\n \"version\": \"0.6.1\",\n \"description\": \"Observability primitives for the Graphorin framework: typed AISpan tracer over OpenTelemetry, mandatory withValidation wrapper for every exporter, sensitivity-aware RedactionValidator with built-in PII / secret patterns, GenAI Semantic Conventions conformance helpers, OpenInference span-kind emission, structured logger with span correlation, append-only JSONL exporter for replay, sanitized replay primitives, hierarchical CostTracker, and a minimal inline eval runner alongside a zero-default telemetry stub.\",\n \"license\": \"MIT\",\n \"author\": \"Oleksiy Stepurenko\",\n \"homepage\": \"https://github.com/o-stepper/graphorin/tree/main/packages/observability\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/o-stepper/graphorin.git\",\n \"directory\": \"packages/observability\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/o-stepper/graphorin/issues\"\n },\n \"keywords\": [\n \"graphorin\",\n \"ai\",\n \"agents\",\n \"framework\",\n \"observability\",\n \"tracing\",\n \"opentelemetry\",\n \"otel\",\n \"gen-ai\",\n \"openinference\",\n \"redaction\",\n \"pii\",\n \"cost-tracking\",\n \"replay\",\n \"evals\",\n \"logger\"\n ],\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./tracer\": {\n \"types\": \"./dist/tracer/index.d.ts\",\n \"import\": \"./dist/tracer/index.js\"\n },\n \"./redaction\": {\n \"types\": \"./dist/redaction/index.d.ts\",\n \"import\": \"./dist/redaction/index.js\"\n },\n \"./redaction/patterns\": {\n \"types\": \"./dist/redaction/patterns.d.ts\",\n \"import\": \"./dist/redaction/patterns.js\"\n },\n \"./redaction/imperative-patterns\": {\n \"types\": \"./dist/redaction/imperative-patterns.d.ts\",\n \"import\": \"./dist/redaction/imperative-patterns.js\"\n },\n \"./exporters\": {\n \"types\": \"./dist/exporters/index.d.ts\",\n \"import\": \"./dist/exporters/index.js\"\n },\n \"./gen-ai\": {\n \"types\": \"./dist/gen-ai/index.d.ts\",\n \"import\": \"./dist/gen-ai/index.js\"\n },\n \"./openinference\": {\n \"types\": \"./dist/openinference/index.d.ts\",\n \"import\": \"./dist/openinference/index.js\"\n },\n \"./cost\": {\n \"types\": \"./dist/cost/index.d.ts\",\n \"import\": \"./dist/cost/index.js\"\n },\n \"./replay\": {\n \"types\": \"./dist/replay/index.d.ts\",\n \"import\": \"./dist/replay/index.js\"\n },\n \"./eval\": {\n \"types\": \"./dist/eval/index.d.ts\",\n \"import\": \"./dist/eval/index.js\"\n },\n \"./logger\": {\n \"types\": \"./dist/logger/index.d.ts\",\n \"import\": \"./dist/logger/index.js\"\n },\n \"./telemetry\": {\n \"types\": \"./dist/telemetry/index.d.ts\",\n \"import\": \"./dist/telemetry/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit && tsc -p tsconfig.tests.json\",\n \"test\": \"vitest run\",\n \"lint\": \"biome check .\",\n \"clean\": \"rimraf dist .turbo *.tsbuildinfo\"\n },\n \"dependencies\": {\n \"@graphorin/core\": \"workspace:*\"\n },\n \"peerDependencies\": {\n \"@opentelemetry/api\": \"^1.9.0\",\n \"@opentelemetry/sdk-node\": \"^0.217.0\",\n \"@opentelemetry/exporter-trace-otlp-http\": \"^0.215.0\"\n },\n \"peerDependenciesMeta\": {\n \"@opentelemetry/api\": {\n \"optional\": false\n },\n \"@opentelemetry/sdk-node\": {\n \"optional\": true\n },\n \"@opentelemetry/exporter-trace-otlp-http\": {\n \"optional\": true\n }\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n },\n \"devDependencies\": {\n \"@opentelemetry/api\": \"^1.9.0\",\n \"@opentelemetry/exporter-trace-otlp-http\": \"^0.215.0\",\n \"@opentelemetry/sdk-node\": \"^0.217.0\",\n \"fast-check\": \"^3.23.0\"\n }\n}\n"],"mappings":";cAEa"}
1
+ {"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@graphorin/observability\",\n \"version\": \"0.7.0\",\n \"description\": \"Observability primitives for the Graphorin framework: typed AISpan tracer over OpenTelemetry, mandatory withValidation wrapper for every exporter, sensitivity-aware RedactionValidator with built-in PII / secret patterns, GenAI Semantic Conventions conformance helpers, OpenInference span-kind emission, structured logger with span correlation, append-only JSONL exporter for replay, sanitized replay primitives, hierarchical CostTracker, and a minimal inline eval runner alongside a zero-default telemetry stub.\",\n \"license\": \"MIT\",\n \"author\": \"Oleksiy Stepurenko\",\n \"homepage\": \"https://github.com/o-stepper/graphorin/tree/main/packages/observability\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/o-stepper/graphorin.git\",\n \"directory\": \"packages/observability\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/o-stepper/graphorin/issues\"\n },\n \"keywords\": [\n \"graphorin\",\n \"ai\",\n \"agents\",\n \"framework\",\n \"observability\",\n \"tracing\",\n \"opentelemetry\",\n \"otel\",\n \"gen-ai\",\n \"openinference\",\n \"redaction\",\n \"pii\",\n \"cost-tracking\",\n \"replay\",\n \"evals\",\n \"logger\"\n ],\n \"type\": \"module\",\n \"sideEffects\": false,\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"./tracer\": {\n \"types\": \"./dist/tracer/index.d.ts\",\n \"default\": \"./dist/tracer/index.js\"\n },\n \"./redaction\": {\n \"types\": \"./dist/redaction/index.d.ts\",\n \"default\": \"./dist/redaction/index.js\"\n },\n \"./redaction/patterns\": {\n \"types\": \"./dist/redaction/patterns.d.ts\",\n \"default\": \"./dist/redaction/patterns.js\"\n },\n \"./redaction/imperative-patterns\": {\n \"types\": \"./dist/redaction/imperative-patterns.d.ts\",\n \"default\": \"./dist/redaction/imperative-patterns.js\"\n },\n \"./exporters\": {\n \"types\": \"./dist/exporters/index.d.ts\",\n \"default\": \"./dist/exporters/index.js\"\n },\n \"./gen-ai\": {\n \"types\": \"./dist/gen-ai/index.d.ts\",\n \"default\": \"./dist/gen-ai/index.js\"\n },\n \"./openinference\": {\n \"types\": \"./dist/openinference/index.d.ts\",\n \"default\": \"./dist/openinference/index.js\"\n },\n \"./cost\": {\n \"types\": \"./dist/cost/index.d.ts\",\n \"default\": \"./dist/cost/index.js\"\n },\n \"./replay\": {\n \"types\": \"./dist/replay/index.d.ts\",\n \"default\": \"./dist/replay/index.js\"\n },\n \"./eval\": {\n \"types\": \"./dist/eval/index.d.ts\",\n \"default\": \"./dist/eval/index.js\"\n },\n \"./logger\": {\n \"types\": \"./dist/logger/index.d.ts\",\n \"default\": \"./dist/logger/index.js\"\n },\n \"./telemetry\": {\n \"types\": \"./dist/telemetry/index.d.ts\",\n \"default\": \"./dist/telemetry/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"src\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit && tsc -p tsconfig.tests.json\",\n \"test\": \"vitest run\",\n \"lint\": \"biome check .\",\n \"clean\": \"rimraf dist .turbo *.tsbuildinfo\"\n },\n \"dependencies\": {\n \"@graphorin/core\": \"workspace:*\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n },\n \"devDependencies\": {\n \"fast-check\": \"^3.23.0\"\n }\n}\n"],"mappings":";cAEa"}
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Imperative-pattern catalogue for inbound prompt-injection defence.
4
4
  *
5
- * Sibling to {@link BUILT_IN_PATTERNS} (PII / secrets) - the two
5
+ * Sibling to `BUILT_IN_PATTERNS` (PII / secrets) - the two
6
6
  * catalogues are disjoint by construction. The imperative catalogue
7
7
  * is consumed by the inbound sanitization layer in `@graphorin/tools`
8
8
  * to scan tool / MCP results before they reach the agent's message
@@ -27,10 +27,10 @@
27
27
  *
28
28
  * @stable
29
29
  */
30
- type ImperativePatternName = 'ignore-previous-instructions' | 'forget-instructions' | 'override-instructions' | 'system-prompt-leak' | 'role-reassignment' | 'developer-mode' | 'jailbreak-marker' | 'tool-call-injection' | 'role-tag-injection';
30
+ type ImperativePatternName = 'ignore-previous-instructions' | 'forget-instructions' | 'override-instructions' | 'system-prompt-leak' | 'role-reassignment' | 'developer-mode' | 'jailbreak-marker' | 'tool-call-injection' | 'role-tag-injection' | 'untrusted-content-delimiter-injection';
31
31
  /**
32
32
  * One entry in the imperative-pattern catalogue. The shape mirrors
33
- * {@link BUILT_IN_PATTERNS} so consumers can share scan / replace
33
+ * `BUILT_IN_PATTERNS` so consumers can share scan / replace
34
34
  * machinery, but the fields are typed as imperative-only so the two
35
35
  * catalogues do not accidentally merge.
36
36
  *
@@ -1 +1 @@
1
- {"version":3,"file":"imperative-patterns.d.ts","names":[],"sources":["../../src/redaction/imperative-patterns.ts"],"sourcesContent":[],"mappings":";;AA6BA;AAmBA;;;;;AAuGA;AASA;AAYA;AAaA;;;;;AA4DA;;;;;;;;;;;;;KAxNY,qBAAA;;;;;;;;;UAmBK,iBAAA;iBACA;;;;;;;sBAOK;;;;;kBAKJ;;;;;;;;;;;;cA0FL,uCAAuC;;;;;;;;cASvC,iCAAiC;;;;;;;;;UAY7B,UAAA;iBACA;;;;;;;;;;;;;;iBAYD,sBAAA,0BAEJ,cAAc,wCAEvB;;;;;;;;;iBAwDa,uBAAA,0BAEJ,cAAc"}
1
+ {"version":3,"file":"imperative-patterns.d.ts","names":[],"sources":["../../src/redaction/imperative-patterns.ts"],"sourcesContent":[],"mappings":";;AA6BA;AAoBA;;;;;AAmHA;AASA;AAYA;AAaA;;;;;AA4DA;;;;;;;;;;;;;KArOY,qBAAA;;;;;;;;;UAoBK,iBAAA;iBACA;;;;;;;sBAOK;;;;;kBAKJ;;;;;;;;;;;;cAsGL,uCAAuC;;;;;;;;cASvC,iCAAiC;;;;;;;;;UAY7B,UAAA;iBACA;;;;;;;;;;;;;;iBAYD,sBAAA,0BAEJ,cAAc,wCAEvB;;;;;;;;;iBAwDa,uBAAA,0BAEJ,cAAc"}
@@ -99,6 +99,13 @@ const PATTERNS = [
99
99
  ],
100
100
  regex: /<\|(?:im_start|system|assistant|user|im_end)\|>/gi,
101
101
  mask: "[REDACTED:imperative-pattern]"
102
+ },
103
+ {
104
+ name: "untrusted-content-delimiter-injection",
105
+ description: "Fabricated `<<<untrusted_content>>>` envelope delimiters inside untrusted content - an attempt to prematurely close, or spoof a nested opening of, the inbound trust envelope. The regex is scoped STRICTLY to the envelope markers (never bare `<<<` / `>>>` runs) so legitimate Python doctest / REPL `>>>` and shell heredoc fragments are untouched.",
106
+ prefilter: ["untrusted_content"],
107
+ regex: /<<<\s*\/?\s*untrusted_content/gi,
108
+ mask: "[REDACTED:imperative-pattern]"
102
109
  }
103
110
  ];
104
111
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"imperative-patterns.js","names":["PATTERNS: readonly ImperativePattern[]","BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[]","IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string>","hits: { pattern: string; matchCount: number }[]","match: RegExpExecArray | null"],"sources":["../../src/redaction/imperative-patterns.ts"],"sourcesContent":["/**\n * Imperative-pattern catalogue for inbound prompt-injection defence.\n *\n * Sibling to {@link BUILT_IN_PATTERNS} (PII / secrets) - the two\n * catalogues are disjoint by construction. The imperative catalogue\n * is consumed by the inbound sanitization layer in `@graphorin/tools`\n * to scan tool / MCP results before they reach the agent's message\n * store; the PII / secrets catalogue is consumed by the outbound\n * exporter validators to scan span attributes before they reach an\n * exporter.\n *\n * The patterns target the canonical English \"ignore previous\n * instructions\" / \"system override\" injection family - the concrete\n * surface that an untrusted-skill or MCP server result might use to\n * smuggle imperative content into the next provider call. The\n * catalogue is intentionally conservative: every entry has a fixed-\n * substring prefilter so the per-byte scan budget stays sub-millisecond\n * on typical 16 KB tool results.\n *\n * @packageDocumentation\n */\n\n/**\n * Stable name of an imperative pattern. The catalogue is curated;\n * user-supplied patterns can use any identifier they want and will be\n * passed through the sanitization layer alongside the built-ins.\n *\n * @stable\n */\nexport type ImperativePatternName =\n | 'ignore-previous-instructions'\n | 'forget-instructions'\n | 'override-instructions'\n | 'system-prompt-leak'\n | 'role-reassignment'\n | 'developer-mode'\n | 'jailbreak-marker'\n | 'tool-call-injection'\n | 'role-tag-injection';\n\n/**\n * One entry in the imperative-pattern catalogue. The shape mirrors\n * {@link BUILT_IN_PATTERNS} so consumers can share scan / replace\n * machinery, but the fields are typed as imperative-only so the two\n * catalogues do not accidentally merge.\n *\n * @stable\n */\nexport interface ImperativePattern {\n readonly name: ImperativePatternName | (string & {});\n readonly description: string;\n /**\n * Cheap substring prefilter applied before the regex; if the body\n * does not contain any of the prefilter substrings the regex is\n * skipped entirely. The prefilter is case-insensitive.\n */\n readonly prefilter: ReadonlyArray<string>;\n /**\n * Full regex applied when the prefilter matches. Always carries the\n * `g` and `i` flags; the catalogue construction validates this.\n */\n readonly regex: RegExp;\n /** Replacement string applied by the `'detect-and-strip*'` policies. */\n readonly mask: string;\n}\n\nconst PATTERNS: readonly ImperativePattern[] = [\n {\n name: 'ignore-previous-instructions',\n description:\n 'The canonical \"ignore previous instructions\" injection - the most common form of prompt injection seen across MCP results.',\n prefilter: ['ignore', 'disregard', 'forget'],\n regex:\n /\\b(?:ignore|disregard|forget)\\s+(?:all\\s+)?(?:the\\s+)?(?:previous|prior|above|earlier|preceding|original)\\s+(?:instructions?|prompts?|messages?|directives?|rules?)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'forget-instructions',\n description: 'Imperative \"forget what I told you\" / memory-wipe family.',\n prefilter: ['forget', 'erase', 'clear'],\n regex:\n /\\b(?:forget|erase|clear|wipe)\\s+(?:everything|all)\\s+(?:above|before|previous|i\\s+(?:told|said))\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'override-instructions',\n description: '\"Override the system prompt\" / \"new instructions\" injection family.',\n prefilter: ['override', 'new instructions', 'updated instructions', 'replace'],\n regex:\n /\\b(?:override|replace)\\s+(?:the\\s+)?(?:system\\s+)?(?:prompt|instructions?|directives?)\\b|\\bnew\\s+(?:instructions?|prompts?|directives?)\\s*(?::|follow|are)\\b|\\bupdated\\s+(?:instructions?|prompts?|directives?)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'system-prompt-leak',\n description:\n 'Requests asking the model to reveal its system prompt / hidden instructions / configuration.',\n prefilter: ['system prompt', 'system message', 'reveal', 'print', 'show'],\n regex:\n /\\b(?:reveal|print|show|output|repeat|expose|disclose)\\s+(?:your|the)\\s+(?:system\\s+(?:prompt|message|instructions?)|hidden\\s+instructions?|initial\\s+(?:prompt|instructions?)|configuration)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'role-reassignment',\n description: '\"You are now ...\" / role-reassignment imperative family.',\n prefilter: ['you are now', 'you are no longer', 'pretend', 'act as'],\n regex:\n /\\byou\\s+are\\s+(?:now|no\\s+longer)\\s+(?:a\\b|an\\b|the\\b)|\\bpretend\\s+(?:to\\s+be|you\\s+are)\\b|\\bact\\s+as\\s+(?:a|an|the|if\\s+you)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'developer-mode',\n description:\n 'Requests to enter \"developer mode\" / \"DAN\" / unrestricted modes - a long-running jailbreak family.',\n prefilter: ['developer mode', 'admin mode', 'unrestricted'],\n regex:\n /\\b(?:enter|enable|activate)\\s+(?:developer|admin|debug|unrestricted|god|root)\\s+mode\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'jailbreak-marker',\n description: 'Direct \"jailbreak\" markers in tool result content.',\n prefilter: ['jailbreak', 'jailbroken'],\n regex: /\\bjailbroken?\\s+(?:mode|response|model|assistant)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'tool-call-injection',\n description:\n 'Tool-call markers that try to coerce the model into invoking a tool from the result content.',\n prefilter: ['<tool_use', '<function_call', '<invoke'],\n regex: /<(?:tool_use|function_call|invoke|tool_call)\\b[^>]*>/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'role-tag-injection',\n description:\n 'Chat-role tags injected into tool results to spoof a system / assistant message in the conversation.',\n prefilter: ['<|im_start|', '<|system|', '<|assistant|', '<|user|'],\n regex: /<\\|(?:im_start|system|assistant|user|im_end)\\|>/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n];\n\n/**\n * The default-on imperative-pattern catalogue. Stable across patches;\n * additions during the pre-1.0 window are minor-bumps because new\n * patterns may produce additional `tool.inbound.sanitization.hit{...}`\n * counter increments on existing deployments.\n *\n * @stable\n */\nexport const BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[] = PATTERNS;\n\n/**\n * Combined Aho-Corasick-style prefilter set across every pattern.\n * Lower-cased substrings; consumers test the body once with the\n * combined filter before iterating regexes.\n *\n * @stable\n */\nexport const IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string> = Object.freeze([\n ...new Set(BUILT_IN_IMPERATIVE_PATTERNS.flatMap((p) => p.prefilter.map((s) => s.toLowerCase()))),\n]);\n\n/**\n * Compiled scan helper. Returns the list of pattern names that fired\n * AND the number of bytes the strip would remove if applied. Bounded\n * by the budget hint - when exceeded, returns `null` to let the caller\n * apply the best-effort `'detect-failed'` annotation.\n *\n * @stable\n */\nexport interface ScanResult {\n readonly hits: ReadonlyArray<{ readonly pattern: string; readonly matchCount: number }>;\n readonly bytesMatched: number;\n readonly scanDurationUs: number;\n}\n\n/**\n * Run the imperative-pattern scan against `body`. Patterns are\n * iterated in catalogue order; the prefilter shortcut returns early\n * for bodies that do not contain any imperative-family substring.\n *\n * @stable\n */\nexport function scanImperativePatterns(\n body: string,\n patterns: ReadonlyArray<ImperativePattern> = BUILT_IN_IMPERATIVE_PATTERNS,\n budgetMs = 5,\n): ScanResult | null {\n const start = performance.now();\n if (body.length === 0) {\n return { hits: [], bytesMatched: 0, scanDurationUs: 0 };\n }\n const lower = body.toLowerCase();\n let prefilterHit = false;\n for (const sub of IMPERATIVE_PREFILTER_SUBSTRINGS) {\n if (lower.includes(sub)) {\n prefilterHit = true;\n break;\n }\n }\n if (!prefilterHit) {\n return {\n hits: [],\n bytesMatched: 0,\n scanDurationUs: Math.round((performance.now() - start) * 1000),\n };\n }\n const hits: { pattern: string; matchCount: number }[] = [];\n let bytesMatched = 0;\n for (const pattern of patterns) {\n if (performance.now() - start > budgetMs) return null;\n const localPrefilterMatch = pattern.prefilter.some((sub) => lower.includes(sub.toLowerCase()));\n if (!localPrefilterMatch) continue;\n let matchCount = 0;\n pattern.regex.lastIndex = 0;\n let match: RegExpExecArray | null = pattern.regex.exec(body);\n while (match !== null) {\n matchCount++;\n bytesMatched += match[0].length;\n if (pattern.regex.lastIndex === match.index) {\n pattern.regex.lastIndex = match.index + 1;\n }\n match = pattern.regex.exec(body);\n }\n if (matchCount > 0) {\n hits.push({ pattern: pattern.name as string, matchCount });\n }\n }\n return {\n hits,\n bytesMatched,\n scanDurationUs: Math.round((performance.now() - start) * 1000),\n };\n}\n\n/**\n * Apply `pattern.mask` to every match of every pattern in `body`. Used\n * by the `'detect-and-strip*'` policies. The mask is calibrated to NOT\n * match any imperative pattern itself, so post-strip bodies do not\n * trigger another scan hit on round trips.\n *\n * @stable\n */\nexport function stripImperativePatterns(\n body: string,\n patterns: ReadonlyArray<ImperativePattern> = BUILT_IN_IMPERATIVE_PATTERNS,\n): string {\n let out = body;\n for (const pattern of patterns) {\n out = out.replace(pattern.regex, pattern.mask);\n }\n return out;\n}\n"],"mappings":";AAkEA,MAAMA,WAAyC;CAC7C;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAU;GAAa;GAAS;EAC5C,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAU;GAAS;GAAQ;EACvC,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAY;GAAoB;GAAwB;GAAU;EAC9E,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAiB;GAAkB;GAAU;GAAS;GAAO;EACzE,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAe;GAAqB;GAAW;GAAS;EACpE,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAkB;GAAc;GAAe;EAC3D,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW,CAAC,aAAa,aAAa;EACtC,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAa;GAAkB;GAAU;EACrD,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAe;GAAa;GAAgB;GAAU;EAClE,OAAO;EACP,MAAM;EACP;CACF;;;;;;;;;AAUD,MAAaC,+BAA6D;;;;;;;;AAS1E,MAAaC,kCAAyD,OAAO,OAAO,CAClF,GAAG,IAAI,IAAI,6BAA6B,SAAS,MAAM,EAAE,UAAU,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CACjG,CAAC;;;;;;;;AAuBF,SAAgB,uBACd,MACA,WAA6C,8BAC7C,WAAW,GACQ;CACnB,MAAM,QAAQ,YAAY,KAAK;AAC/B,KAAI,KAAK,WAAW,EAClB,QAAO;EAAE,MAAM,EAAE;EAAE,cAAc;EAAG,gBAAgB;EAAG;CAEzD,MAAM,QAAQ,KAAK,aAAa;CAChC,IAAI,eAAe;AACnB,MAAK,MAAM,OAAO,gCAChB,KAAI,MAAM,SAAS,IAAI,EAAE;AACvB,iBAAe;AACf;;AAGJ,KAAI,CAAC,aACH,QAAO;EACL,MAAM,EAAE;EACR,cAAc;EACd,gBAAgB,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,IAAK;EAC/D;CAEH,MAAMC,OAAkD,EAAE;CAC1D,IAAI,eAAe;AACnB,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,YAAY,KAAK,GAAG,QAAQ,SAAU,QAAO;AAEjD,MAAI,CADwB,QAAQ,UAAU,MAAM,QAAQ,MAAM,SAAS,IAAI,aAAa,CAAC,CAAC,CACpE;EAC1B,IAAI,aAAa;AACjB,UAAQ,MAAM,YAAY;EAC1B,IAAIC,QAAgC,QAAQ,MAAM,KAAK,KAAK;AAC5D,SAAO,UAAU,MAAM;AACrB;AACA,mBAAgB,MAAM,GAAG;AACzB,OAAI,QAAQ,MAAM,cAAc,MAAM,MACpC,SAAQ,MAAM,YAAY,MAAM,QAAQ;AAE1C,WAAQ,QAAQ,MAAM,KAAK,KAAK;;AAElC,MAAI,aAAa,EACf,MAAK,KAAK;GAAE,SAAS,QAAQ;GAAgB;GAAY,CAAC;;AAG9D,QAAO;EACL;EACA;EACA,gBAAgB,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,IAAK;EAC/D;;;;;;;;;;AAWH,SAAgB,wBACd,MACA,WAA6C,8BACrC;CACR,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,SACpB,OAAM,IAAI,QAAQ,QAAQ,OAAO,QAAQ,KAAK;AAEhD,QAAO"}
1
+ {"version":3,"file":"imperative-patterns.js","names":["PATTERNS: readonly ImperativePattern[]","BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[]","IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string>","hits: { pattern: string; matchCount: number }[]","match: RegExpExecArray | null"],"sources":["../../src/redaction/imperative-patterns.ts"],"sourcesContent":["/**\n * Imperative-pattern catalogue for inbound prompt-injection defence.\n *\n * Sibling to `BUILT_IN_PATTERNS` (PII / secrets) - the two\n * catalogues are disjoint by construction. The imperative catalogue\n * is consumed by the inbound sanitization layer in `@graphorin/tools`\n * to scan tool / MCP results before they reach the agent's message\n * store; the PII / secrets catalogue is consumed by the outbound\n * exporter validators to scan span attributes before they reach an\n * exporter.\n *\n * The patterns target the canonical English \"ignore previous\n * instructions\" / \"system override\" injection family - the concrete\n * surface that an untrusted-skill or MCP server result might use to\n * smuggle imperative content into the next provider call. The\n * catalogue is intentionally conservative: every entry has a fixed-\n * substring prefilter so the per-byte scan budget stays sub-millisecond\n * on typical 16 KB tool results.\n *\n * @packageDocumentation\n */\n\n/**\n * Stable name of an imperative pattern. The catalogue is curated;\n * user-supplied patterns can use any identifier they want and will be\n * passed through the sanitization layer alongside the built-ins.\n *\n * @stable\n */\nexport type ImperativePatternName =\n | 'ignore-previous-instructions'\n | 'forget-instructions'\n | 'override-instructions'\n | 'system-prompt-leak'\n | 'role-reassignment'\n | 'developer-mode'\n | 'jailbreak-marker'\n | 'tool-call-injection'\n | 'role-tag-injection'\n | 'untrusted-content-delimiter-injection';\n\n/**\n * One entry in the imperative-pattern catalogue. The shape mirrors\n * `BUILT_IN_PATTERNS` so consumers can share scan / replace\n * machinery, but the fields are typed as imperative-only so the two\n * catalogues do not accidentally merge.\n *\n * @stable\n */\nexport interface ImperativePattern {\n readonly name: ImperativePatternName | (string & {});\n readonly description: string;\n /**\n * Cheap substring prefilter applied before the regex; if the body\n * does not contain any of the prefilter substrings the regex is\n * skipped entirely. The prefilter is case-insensitive.\n */\n readonly prefilter: ReadonlyArray<string>;\n /**\n * Full regex applied when the prefilter matches. Always carries the\n * `g` and `i` flags; the catalogue construction validates this.\n */\n readonly regex: RegExp;\n /** Replacement string applied by the `'detect-and-strip*'` policies. */\n readonly mask: string;\n}\n\nconst PATTERNS: readonly ImperativePattern[] = [\n {\n name: 'ignore-previous-instructions',\n description:\n 'The canonical \"ignore previous instructions\" injection - the most common form of prompt injection seen across MCP results.',\n prefilter: ['ignore', 'disregard', 'forget'],\n regex:\n /\\b(?:ignore|disregard|forget)\\s+(?:all\\s+)?(?:the\\s+)?(?:previous|prior|above|earlier|preceding|original)\\s+(?:instructions?|prompts?|messages?|directives?|rules?)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'forget-instructions',\n description: 'Imperative \"forget what I told you\" / memory-wipe family.',\n prefilter: ['forget', 'erase', 'clear'],\n regex:\n /\\b(?:forget|erase|clear|wipe)\\s+(?:everything|all)\\s+(?:above|before|previous|i\\s+(?:told|said))\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'override-instructions',\n description: '\"Override the system prompt\" / \"new instructions\" injection family.',\n prefilter: ['override', 'new instructions', 'updated instructions', 'replace'],\n regex:\n /\\b(?:override|replace)\\s+(?:the\\s+)?(?:system\\s+)?(?:prompt|instructions?|directives?)\\b|\\bnew\\s+(?:instructions?|prompts?|directives?)\\s*(?::|follow|are)\\b|\\bupdated\\s+(?:instructions?|prompts?|directives?)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'system-prompt-leak',\n description:\n 'Requests asking the model to reveal its system prompt / hidden instructions / configuration.',\n prefilter: ['system prompt', 'system message', 'reveal', 'print', 'show'],\n regex:\n /\\b(?:reveal|print|show|output|repeat|expose|disclose)\\s+(?:your|the)\\s+(?:system\\s+(?:prompt|message|instructions?)|hidden\\s+instructions?|initial\\s+(?:prompt|instructions?)|configuration)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'role-reassignment',\n description: '\"You are now ...\" / role-reassignment imperative family.',\n prefilter: ['you are now', 'you are no longer', 'pretend', 'act as'],\n regex:\n /\\byou\\s+are\\s+(?:now|no\\s+longer)\\s+(?:a\\b|an\\b|the\\b)|\\bpretend\\s+(?:to\\s+be|you\\s+are)\\b|\\bact\\s+as\\s+(?:a|an|the|if\\s+you)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'developer-mode',\n description:\n 'Requests to enter \"developer mode\" / \"DAN\" / unrestricted modes - a long-running jailbreak family.',\n prefilter: ['developer mode', 'admin mode', 'unrestricted'],\n regex:\n /\\b(?:enter|enable|activate)\\s+(?:developer|admin|debug|unrestricted|god|root)\\s+mode\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'jailbreak-marker',\n description: 'Direct \"jailbreak\" markers in tool result content.',\n prefilter: ['jailbreak', 'jailbroken'],\n regex: /\\bjailbroken?\\s+(?:mode|response|model|assistant)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'tool-call-injection',\n description:\n 'Tool-call markers that try to coerce the model into invoking a tool from the result content.',\n prefilter: ['<tool_use', '<function_call', '<invoke'],\n regex: /<(?:tool_use|function_call|invoke|tool_call)\\b[^>]*>/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'role-tag-injection',\n description:\n 'Chat-role tags injected into tool results to spoof a system / assistant message in the conversation.',\n prefilter: ['<|im_start|', '<|system|', '<|assistant|', '<|user|'],\n regex: /<\\|(?:im_start|system|assistant|user|im_end)\\|>/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'untrusted-content-delimiter-injection',\n description:\n 'Fabricated `<<<untrusted_content>>>` envelope delimiters inside untrusted content - an attempt to prematurely close, or spoof a nested opening of, the inbound trust envelope. The regex is scoped STRICTLY to the envelope markers (never bare `<<<` / `>>>` runs) so legitimate Python doctest / REPL `>>>` and shell heredoc fragments are untouched.',\n // The word alone is the prefilter: any marker form the regex can\n // match necessarily contains it, including whitespace-padded\n // variants like `<<< untrusted_content` that a tighter\n // `<<<untrusted_content` prefilter would miss.\n prefilter: ['untrusted_content'],\n regex: /<<<\\s*\\/?\\s*untrusted_content/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n];\n\n/**\n * The default-on imperative-pattern catalogue. Stable across patches;\n * additions during the pre-1.0 window are minor-bumps because new\n * patterns may produce additional `tool.inbound.sanitization.hit{...}`\n * counter increments on existing deployments.\n *\n * @stable\n */\nexport const BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[] = PATTERNS;\n\n/**\n * Combined Aho-Corasick-style prefilter set across every pattern.\n * Lower-cased substrings; consumers test the body once with the\n * combined filter before iterating regexes.\n *\n * @stable\n */\nexport const IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string> = Object.freeze([\n ...new Set(BUILT_IN_IMPERATIVE_PATTERNS.flatMap((p) => p.prefilter.map((s) => s.toLowerCase()))),\n]);\n\n/**\n * Compiled scan helper. Returns the list of pattern names that fired\n * AND the number of bytes the strip would remove if applied. Bounded\n * by the budget hint - when exceeded, returns `null` to let the caller\n * apply the best-effort `'detect-failed'` annotation.\n *\n * @stable\n */\nexport interface ScanResult {\n readonly hits: ReadonlyArray<{ readonly pattern: string; readonly matchCount: number }>;\n readonly bytesMatched: number;\n readonly scanDurationUs: number;\n}\n\n/**\n * Run the imperative-pattern scan against `body`. Patterns are\n * iterated in catalogue order; the prefilter shortcut returns early\n * for bodies that do not contain any imperative-family substring.\n *\n * @stable\n */\nexport function scanImperativePatterns(\n body: string,\n patterns: ReadonlyArray<ImperativePattern> = BUILT_IN_IMPERATIVE_PATTERNS,\n budgetMs = 5,\n): ScanResult | null {\n const start = performance.now();\n if (body.length === 0) {\n return { hits: [], bytesMatched: 0, scanDurationUs: 0 };\n }\n const lower = body.toLowerCase();\n let prefilterHit = false;\n for (const sub of IMPERATIVE_PREFILTER_SUBSTRINGS) {\n if (lower.includes(sub)) {\n prefilterHit = true;\n break;\n }\n }\n if (!prefilterHit) {\n return {\n hits: [],\n bytesMatched: 0,\n scanDurationUs: Math.round((performance.now() - start) * 1000),\n };\n }\n const hits: { pattern: string; matchCount: number }[] = [];\n let bytesMatched = 0;\n for (const pattern of patterns) {\n if (performance.now() - start > budgetMs) return null;\n const localPrefilterMatch = pattern.prefilter.some((sub) => lower.includes(sub.toLowerCase()));\n if (!localPrefilterMatch) continue;\n let matchCount = 0;\n pattern.regex.lastIndex = 0;\n let match: RegExpExecArray | null = pattern.regex.exec(body);\n while (match !== null) {\n matchCount++;\n bytesMatched += match[0].length;\n if (pattern.regex.lastIndex === match.index) {\n pattern.regex.lastIndex = match.index + 1;\n }\n match = pattern.regex.exec(body);\n }\n if (matchCount > 0) {\n hits.push({ pattern: pattern.name as string, matchCount });\n }\n }\n return {\n hits,\n bytesMatched,\n scanDurationUs: Math.round((performance.now() - start) * 1000),\n };\n}\n\n/**\n * Apply `pattern.mask` to every match of every pattern in `body`. Used\n * by the `'detect-and-strip*'` policies. The mask is calibrated to NOT\n * match any imperative pattern itself, so post-strip bodies do not\n * trigger another scan hit on round trips.\n *\n * @stable\n */\nexport function stripImperativePatterns(\n body: string,\n patterns: ReadonlyArray<ImperativePattern> = BUILT_IN_IMPERATIVE_PATTERNS,\n): string {\n let out = body;\n for (const pattern of patterns) {\n out = out.replace(pattern.regex, pattern.mask);\n }\n return out;\n}\n"],"mappings":";AAmEA,MAAMA,WAAyC;CAC7C;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAU;GAAa;GAAS;EAC5C,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAU;GAAS;GAAQ;EACvC,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAY;GAAoB;GAAwB;GAAU;EAC9E,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAiB;GAAkB;GAAU;GAAS;GAAO;EACzE,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAe;GAAqB;GAAW;GAAS;EACpE,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAkB;GAAc;GAAe;EAC3D,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW,CAAC,aAAa,aAAa;EACtC,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAa;GAAkB;GAAU;EACrD,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAe;GAAa;GAAgB;GAAU;EAClE,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EAKF,WAAW,CAAC,oBAAoB;EAChC,OAAO;EACP,MAAM;EACP;CACF;;;;;;;;;AAUD,MAAaC,+BAA6D;;;;;;;;AAS1E,MAAaC,kCAAyD,OAAO,OAAO,CAClF,GAAG,IAAI,IAAI,6BAA6B,SAAS,MAAM,EAAE,UAAU,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CACjG,CAAC;;;;;;;;AAuBF,SAAgB,uBACd,MACA,WAA6C,8BAC7C,WAAW,GACQ;CACnB,MAAM,QAAQ,YAAY,KAAK;AAC/B,KAAI,KAAK,WAAW,EAClB,QAAO;EAAE,MAAM,EAAE;EAAE,cAAc;EAAG,gBAAgB;EAAG;CAEzD,MAAM,QAAQ,KAAK,aAAa;CAChC,IAAI,eAAe;AACnB,MAAK,MAAM,OAAO,gCAChB,KAAI,MAAM,SAAS,IAAI,EAAE;AACvB,iBAAe;AACf;;AAGJ,KAAI,CAAC,aACH,QAAO;EACL,MAAM,EAAE;EACR,cAAc;EACd,gBAAgB,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,IAAK;EAC/D;CAEH,MAAMC,OAAkD,EAAE;CAC1D,IAAI,eAAe;AACnB,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,YAAY,KAAK,GAAG,QAAQ,SAAU,QAAO;AAEjD,MAAI,CADwB,QAAQ,UAAU,MAAM,QAAQ,MAAM,SAAS,IAAI,aAAa,CAAC,CAAC,CACpE;EAC1B,IAAI,aAAa;AACjB,UAAQ,MAAM,YAAY;EAC1B,IAAIC,QAAgC,QAAQ,MAAM,KAAK,KAAK;AAC5D,SAAO,UAAU,MAAM;AACrB;AACA,mBAAgB,MAAM,GAAG;AACzB,OAAI,QAAQ,MAAM,cAAc,MAAM,MACpC,SAAQ,MAAM,YAAY,MAAM,QAAQ;AAE1C,WAAQ,QAAQ,MAAM,KAAK,KAAK;;AAElC,MAAI,aAAa,EACf,MAAK,KAAK;GAAE,SAAS,QAAQ;GAAgB;GAAY,CAAC;;AAG9D,QAAO;EACL;EACA;EACA,gBAAgB,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,IAAK;EAC/D;;;;;;;;;;AAWH,SAAgB,wBACd,MACA,WAA6C,8BACrC;CACR,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,SACpB,OAAM,IAAI,QAAQ,QAAQ,OAAO,QAAQ,KAAK;AAEhD,QAAO"}
@@ -5,7 +5,12 @@ import { SpanType } from "@graphorin/core";
5
5
  /** @stable */
6
6
  type SamplingDecisionMaker = 'parent-based' | 'always-on' | 'rate-limit';
7
7
  /**
8
- * Per-span-type rate override.
8
+ * Per-span-type rate override. Applies on the probabilistic root path
9
+ * AND (W-090) to children of a sampled parent under `'parent-based'` -
10
+ * `{ type: 'tool.execute', rate: 0.01 }` thins the per-call spans
11
+ * inside every sampled `agent.run` trace. A child dropped by its rule
12
+ * breaks the tree below it: its own descendants inherit
13
+ * `parentSampled=false`.
9
14
  *
10
15
  * @stable
11
16
  */
@@ -1 +1 @@
1
- {"version":3,"file":"sampling.d.ts","names":[],"sources":["../../src/tracer/sampling.ts"],"sourcesContent":[],"mappings":";;;;;AAoEiB,KAxDL,qBAAA,GA0DiB,cAAA,GAAA,WAAA,GAAA,YAAA;AAc7B;;;;;UAjEiB,YAAA;iBACA;;;;;;;;UASA,eAAA;;;;mBAIE,cAAc;;2BAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCV,OAAA;;qBAEI;;;;;;;;;;;;;iBAcL,aAAA,QAAoB,kBAAuB"}
1
+ {"version":3,"file":"sampling.d.ts","names":[],"sources":["../../src/tracer/sampling.ts"],"sourcesContent":[],"mappings":";;;;;KAeY,qBAAA;;;;;;;;;;;UAYK,YAAA;iBACA;;;;;;;;UASA,eAAA;;;;mBAIE,cAAc;;2BAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCV,OAAA;;qBAEI;;;;;;;;;;;;;iBAcL,aAAA,QAAoB,kBAAuB"}
@@ -22,7 +22,13 @@ function createSampler(opts = {}) {
22
22
  shouldSample(type, parentSampled) {
23
23
  if (decisionMaker === "always-on") return true;
24
24
  if (decisionMaker === "parent-based") {
25
- if (parentSampled === true) return true;
25
+ if (parentSampled === true) {
26
+ const rule = ruleByType.get(type);
27
+ if (rule === void 0) return true;
28
+ if (rule >= 1) return true;
29
+ if (rule <= 0) return false;
30
+ return random() < rule;
31
+ }
26
32
  if (parentSampled === false) return false;
27
33
  }
28
34
  if (decisionMaker === "rate-limit") {
@@ -1 +1 @@
1
- {"version":3,"file":"sampling.js","names":["decisionMaker: SamplingDecisionMaker","windowStart: number | undefined"],"sources":["../../src/tracer/sampling.ts"],"sourcesContent":["/**\n * Head + tail sampling helpers. The tracer pairs the configured\n * sampler with a per-type override registry so chatty span types\n * (`memory.embed`, `tool.execute.partial`, …) can be downsampled\n * without affecting the rest of the trace stream.\n *\n * @packageDocumentation\n */\n\nimport type { SpanType } from '@graphorin/core';\n\n/** @stable */\nexport type SamplingDecisionMaker = 'parent-based' | 'always-on' | 'rate-limit';\n\n/**\n * Per-span-type rate override.\n *\n * @stable\n */\nexport interface SamplingRule {\n readonly type: SpanType | string;\n readonly rate: number;\n}\n\n/**\n * Configuration shape consumed by {@link createSampler}.\n *\n * @stable\n */\nexport interface SamplingOptions {\n /** Default head-sampling rate. Must be in `[0, 1]`. Defaults to `1.0`. */\n readonly rate?: number;\n /** Per-type overrides. Last write wins on duplicate `type`. */\n readonly rules?: ReadonlyArray<SamplingRule>;\n /** Decision maker. Defaults to `'parent-based'`. */\n readonly decisionMaker?: SamplingDecisionMaker;\n /**\n * Cap for the `'rate-limit'` decision maker: at most this many root spans\n * are sampled per rolling 1-second window (RP-19). `undefined` ⇒ no cap\n * (samples everything); `0` ⇒ sample nothing. Ignored by the other\n * decision makers.\n */\n readonly maxPerSecond?: number;\n /**\n * Clock for the `'rate-limit'` window. Defaults to `Date.now`.\n *\n * @internal\n */\n readonly now?: () => number;\n /**\n * Optional override for streaming-event sampling.\n * @see RB-52 - streaming event family `tool.execute.{progress,partial}`.\n */\n readonly streaming?: {\n readonly eventSamplingRate?: number;\n readonly includeChunkContent?: 'none' | 'text-only' | 'all';\n };\n /**\n * Override for the random source. Useful for deterministic tests.\n *\n * @internal\n */\n readonly random?: () => number;\n}\n\n/**\n * @stable\n */\nexport interface Sampler {\n /** Decide whether a span of the given type should be recorded. */\n shouldSample(type: SpanType | string, parentSampled?: boolean): boolean;\n /** Decide whether a span event of the given name should be recorded. */\n shouldRecordEvent(name: string): boolean;\n /** Returns whether chunk *content* should travel through the exporter. */\n shouldIncludeChunkContent(): boolean;\n}\n\n/**\n * Build a {@link Sampler} from the supplied options. The sampler is\n * intentionally inexpensive - every decision boils down to a single\n * `random < threshold` comparison.\n *\n * @stable\n */\nexport function createSampler(opts: SamplingOptions = {}): Sampler {\n const baseRate = clampRate(opts.rate ?? 1.0);\n const decisionMaker: SamplingDecisionMaker = opts.decisionMaker ?? 'parent-based';\n const ruleByType = new Map<string, number>();\n for (const rule of opts.rules ?? []) {\n ruleByType.set(rule.type, clampRate(rule.rate));\n }\n const streamingRate = clampRate(opts.streaming?.eventSamplingRate ?? 1.0);\n const includeChunks = opts.streaming?.includeChunkContent ?? 'none';\n const random = opts.random ?? Math.random;\n const maxPerSecond = opts.maxPerSecond;\n const now = opts.now ?? Date.now;\n let windowStart: number | undefined;\n let windowCount = 0;\n\n return {\n shouldSample(type, parentSampled): boolean {\n if (decisionMaker === 'always-on') return true;\n if (decisionMaker === 'parent-based') {\n // RP-19: true parent-based - follow the parent's real sampling\n // decision. A child of an unsampled parent is NOT recorded (it would\n // otherwise be an orphan); a root span (no parent) falls through to\n // the rate.\n if (parentSampled === true) return true;\n if (parentSampled === false) return false;\n }\n if (decisionMaker === 'rate-limit') {\n // RP-19: a real token-window limiter, distinct from the probabilistic\n // path. Caps sampled spans to `maxPerSecond` per rolling second.\n if (maxPerSecond === undefined) return true;\n if (maxPerSecond <= 0) return false;\n const t = now();\n if (windowStart === undefined || t - windowStart >= 1000) {\n windowStart = t;\n windowCount = 0;\n }\n if (windowCount < maxPerSecond) {\n windowCount += 1;\n return true;\n }\n return false;\n }\n const rate = ruleByType.get(type) ?? baseRate;\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n return random() < rate;\n },\n shouldRecordEvent(name): boolean {\n const rate = ruleByType.get(name) ?? streamingRate;\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n return random() < rate;\n },\n shouldIncludeChunkContent(): boolean {\n return includeChunks !== 'none';\n },\n };\n}\n\nfunction clampRate(rate: number): number {\n if (Number.isNaN(rate)) return 1;\n if (rate < 0) return 0;\n if (rate > 1) return 1;\n return rate;\n}\n"],"mappings":";;;;;;;;AAoFA,SAAgB,cAAc,OAAwB,EAAE,EAAW;CACjE,MAAM,WAAW,UAAU,KAAK,QAAQ,EAAI;CAC5C,MAAMA,gBAAuC,KAAK,iBAAiB;CACnE,MAAM,6BAAa,IAAI,KAAqB;AAC5C,MAAK,MAAM,QAAQ,KAAK,SAAS,EAAE,CACjC,YAAW,IAAI,KAAK,MAAM,UAAU,KAAK,KAAK,CAAC;CAEjD,MAAM,gBAAgB,UAAU,KAAK,WAAW,qBAAqB,EAAI;CACzE,MAAM,gBAAgB,KAAK,WAAW,uBAAuB;CAC7D,MAAM,SAAS,KAAK,UAAU,KAAK;CACnC,MAAM,eAAe,KAAK;CAC1B,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,IAAIC;CACJ,IAAI,cAAc;AAElB,QAAO;EACL,aAAa,MAAM,eAAwB;AACzC,OAAI,kBAAkB,YAAa,QAAO;AAC1C,OAAI,kBAAkB,gBAAgB;AAKpC,QAAI,kBAAkB,KAAM,QAAO;AACnC,QAAI,kBAAkB,MAAO,QAAO;;AAEtC,OAAI,kBAAkB,cAAc;AAGlC,QAAI,iBAAiB,OAAW,QAAO;AACvC,QAAI,gBAAgB,EAAG,QAAO;IAC9B,MAAM,IAAI,KAAK;AACf,QAAI,gBAAgB,UAAa,IAAI,eAAe,KAAM;AACxD,mBAAc;AACd,mBAAc;;AAEhB,QAAI,cAAc,cAAc;AAC9B,oBAAe;AACf,YAAO;;AAET,WAAO;;GAET,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI;AACrC,OAAI,QAAQ,EAAG,QAAO;AACtB,OAAI,QAAQ,EAAG,QAAO;AACtB,UAAO,QAAQ,GAAG;;EAEpB,kBAAkB,MAAe;GAC/B,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI;AACrC,OAAI,QAAQ,EAAG,QAAO;AACtB,OAAI,QAAQ,EAAG,QAAO;AACtB,UAAO,QAAQ,GAAG;;EAEpB,4BAAqC;AACnC,UAAO,kBAAkB;;EAE5B;;AAGH,SAAS,UAAU,MAAsB;AACvC,KAAI,OAAO,MAAM,KAAK,CAAE,QAAO;AAC/B,KAAI,OAAO,EAAG,QAAO;AACrB,KAAI,OAAO,EAAG,QAAO;AACrB,QAAO"}
1
+ {"version":3,"file":"sampling.js","names":["decisionMaker: SamplingDecisionMaker","windowStart: number | undefined"],"sources":["../../src/tracer/sampling.ts"],"sourcesContent":["/**\n * Head + tail sampling helpers. The tracer pairs the configured\n * sampler with a per-type override registry so chatty span types\n * (`memory.embed`, `tool.execute.partial`, …) can be downsampled\n * without affecting the rest of the trace stream. Under the default\n * `'parent-based'` decision maker the per-type rules apply to CHILDREN\n * of a sampled trace too (W-090) - a rule can only downsample inside a\n * sampled trace, never resurrect children of an unsampled parent.\n *\n * @packageDocumentation\n */\n\nimport type { SpanType } from '@graphorin/core';\n\n/** @stable */\nexport type SamplingDecisionMaker = 'parent-based' | 'always-on' | 'rate-limit';\n\n/**\n * Per-span-type rate override. Applies on the probabilistic root path\n * AND (W-090) to children of a sampled parent under `'parent-based'` -\n * `{ type: 'tool.execute', rate: 0.01 }` thins the per-call spans\n * inside every sampled `agent.run` trace. A child dropped by its rule\n * breaks the tree below it: its own descendants inherit\n * `parentSampled=false`.\n *\n * @stable\n */\nexport interface SamplingRule {\n readonly type: SpanType | string;\n readonly rate: number;\n}\n\n/**\n * Configuration shape consumed by {@link createSampler}.\n *\n * @stable\n */\nexport interface SamplingOptions {\n /** Default head-sampling rate. Must be in `[0, 1]`. Defaults to `1.0`. */\n readonly rate?: number;\n /** Per-type overrides. Last write wins on duplicate `type`. */\n readonly rules?: ReadonlyArray<SamplingRule>;\n /** Decision maker. Defaults to `'parent-based'`. */\n readonly decisionMaker?: SamplingDecisionMaker;\n /**\n * Cap for the `'rate-limit'` decision maker: at most this many root spans\n * are sampled per rolling 1-second window (RP-19). `undefined` ⇒ no cap\n * (samples everything); `0` ⇒ sample nothing. Ignored by the other\n * decision makers.\n */\n readonly maxPerSecond?: number;\n /**\n * Clock for the `'rate-limit'` window. Defaults to `Date.now`.\n *\n * @internal\n */\n readonly now?: () => number;\n /**\n * Optional override for streaming-event sampling.\n * @see RB-52 - streaming event family `tool.execute.{progress,partial}`.\n */\n readonly streaming?: {\n readonly eventSamplingRate?: number;\n readonly includeChunkContent?: 'none' | 'text-only' | 'all';\n };\n /**\n * Override for the random source. Useful for deterministic tests.\n *\n * @internal\n */\n readonly random?: () => number;\n}\n\n/**\n * @stable\n */\nexport interface Sampler {\n /** Decide whether a span of the given type should be recorded. */\n shouldSample(type: SpanType | string, parentSampled?: boolean): boolean;\n /** Decide whether a span event of the given name should be recorded. */\n shouldRecordEvent(name: string): boolean;\n /** Returns whether chunk *content* should travel through the exporter. */\n shouldIncludeChunkContent(): boolean;\n}\n\n/**\n * Build a {@link Sampler} from the supplied options. The sampler is\n * intentionally inexpensive - every decision boils down to a single\n * `random < threshold` comparison.\n *\n * @stable\n */\nexport function createSampler(opts: SamplingOptions = {}): Sampler {\n const baseRate = clampRate(opts.rate ?? 1.0);\n const decisionMaker: SamplingDecisionMaker = opts.decisionMaker ?? 'parent-based';\n const ruleByType = new Map<string, number>();\n for (const rule of opts.rules ?? []) {\n ruleByType.set(rule.type, clampRate(rule.rate));\n }\n const streamingRate = clampRate(opts.streaming?.eventSamplingRate ?? 1.0);\n const includeChunks = opts.streaming?.includeChunkContent ?? 'none';\n const random = opts.random ?? Math.random;\n const maxPerSecond = opts.maxPerSecond;\n const now = opts.now ?? Date.now;\n let windowStart: number | undefined;\n let windowCount = 0;\n\n return {\n shouldSample(type, parentSampled): boolean {\n if (decisionMaker === 'always-on') return true;\n if (decisionMaker === 'parent-based') {\n // RP-19: true parent-based - follow the parent's real sampling\n // decision. A child of an unsampled parent is NOT recorded (it would\n // otherwise be an orphan); a root span (no parent) falls through to\n // the rate.\n //\n // W-090: a per-type rule ACTS on children of a sampled parent -\n // that is where the volume lives (`tool.execute` inside a sampled\n // `agent.run` trace), and the docstring always promised it. A rule\n // never resurrects children of an UNSAMPLED parent (orphans); a\n // child dropped by its rule makes its own descendants inherit\n // parentSampled=false.\n if (parentSampled === true) {\n const rule = ruleByType.get(type);\n if (rule === undefined) return true;\n if (rule >= 1) return true;\n if (rule <= 0) return false;\n return random() < rule;\n }\n if (parentSampled === false) return false;\n }\n if (decisionMaker === 'rate-limit') {\n // RP-19: a real token-window limiter, distinct from the probabilistic\n // path. Caps sampled spans to `maxPerSecond` per rolling second.\n if (maxPerSecond === undefined) return true;\n if (maxPerSecond <= 0) return false;\n const t = now();\n if (windowStart === undefined || t - windowStart >= 1000) {\n windowStart = t;\n windowCount = 0;\n }\n if (windowCount < maxPerSecond) {\n windowCount += 1;\n return true;\n }\n return false;\n }\n const rate = ruleByType.get(type) ?? baseRate;\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n return random() < rate;\n },\n shouldRecordEvent(name): boolean {\n const rate = ruleByType.get(name) ?? streamingRate;\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n return random() < rate;\n },\n shouldIncludeChunkContent(): boolean {\n return includeChunks !== 'none';\n },\n };\n}\n\nfunction clampRate(rate: number): number {\n if (Number.isNaN(rate)) return 1;\n if (rate < 0) return 0;\n if (rate > 1) return 1;\n return rate;\n}\n"],"mappings":";;;;;;;;AA4FA,SAAgB,cAAc,OAAwB,EAAE,EAAW;CACjE,MAAM,WAAW,UAAU,KAAK,QAAQ,EAAI;CAC5C,MAAMA,gBAAuC,KAAK,iBAAiB;CACnE,MAAM,6BAAa,IAAI,KAAqB;AAC5C,MAAK,MAAM,QAAQ,KAAK,SAAS,EAAE,CACjC,YAAW,IAAI,KAAK,MAAM,UAAU,KAAK,KAAK,CAAC;CAEjD,MAAM,gBAAgB,UAAU,KAAK,WAAW,qBAAqB,EAAI;CACzE,MAAM,gBAAgB,KAAK,WAAW,uBAAuB;CAC7D,MAAM,SAAS,KAAK,UAAU,KAAK;CACnC,MAAM,eAAe,KAAK;CAC1B,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,IAAIC;CACJ,IAAI,cAAc;AAElB,QAAO;EACL,aAAa,MAAM,eAAwB;AACzC,OAAI,kBAAkB,YAAa,QAAO;AAC1C,OAAI,kBAAkB,gBAAgB;AAYpC,QAAI,kBAAkB,MAAM;KAC1B,MAAM,OAAO,WAAW,IAAI,KAAK;AACjC,SAAI,SAAS,OAAW,QAAO;AAC/B,SAAI,QAAQ,EAAG,QAAO;AACtB,SAAI,QAAQ,EAAG,QAAO;AACtB,YAAO,QAAQ,GAAG;;AAEpB,QAAI,kBAAkB,MAAO,QAAO;;AAEtC,OAAI,kBAAkB,cAAc;AAGlC,QAAI,iBAAiB,OAAW,QAAO;AACvC,QAAI,gBAAgB,EAAG,QAAO;IAC9B,MAAM,IAAI,KAAK;AACf,QAAI,gBAAgB,UAAa,IAAI,eAAe,KAAM;AACxD,mBAAc;AACd,mBAAc;;AAEhB,QAAI,cAAc,cAAc;AAC9B,oBAAe;AACf,YAAO;;AAET,WAAO;;GAET,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI;AACrC,OAAI,QAAQ,EAAG,QAAO;AACtB,OAAI,QAAQ,EAAG,QAAO;AACtB,UAAO,QAAQ,GAAG;;EAEpB,kBAAkB,MAAe;GAC/B,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI;AACrC,OAAI,QAAQ,EAAG,QAAO;AACtB,OAAI,QAAQ,EAAG,QAAO;AACtB,UAAO,QAAQ,GAAG;;EAEpB,4BAAqC;AACnC,UAAO,kBAAkB;;EAE5B;;AAGH,SAAS,UAAU,MAAsB;AACvC,KAAI,OAAO,MAAM,KAAK,CAAE,QAAO;AAC/B,KAAI,OAAO,EAAG,QAAO;AACrB,KAAI,OAAO,EAAG,QAAO;AACrB,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"span.d.ts","names":[],"sources":["../../src/tracer/span.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;UA0BiB,mBAAA;yBACQ;;;;;;;;;UAUR,wBAAwB,WAAW,kBAAkB,OAAO;oCACzC,2BAA2B"}
1
+ {"version":3,"file":"span.d.ts","names":[],"sources":["../../src/tracer/span.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;UA2BiB,mBAAA;yBACQ;;;;;;;;;UAUR,wBAAwB,WAAW,kBAAkB,OAAO;oCACzC,2BAA2B"}
@@ -24,13 +24,17 @@ function createSpan(input) {
24
24
  attributes[name] = value;
25
25
  if (opts?.sensitivity !== void 0) sensitivities[name] = opts.sensitivity;
26
26
  },
27
- addEvent(name, attrs) {
27
+ addEvent(name, attrs, opts) {
28
28
  if (ended) return;
29
29
  if (!input.recordEvent(name)) return;
30
+ const eventSensitivities = {};
31
+ if (opts?.sensitivity !== void 0) for (const key of Object.keys(attrs ?? {})) eventSensitivities[key] = opts.sensitivity;
32
+ for (const [key, tier] of Object.entries(opts?.sensitivityByAttribute ?? {})) eventSensitivities[key] = tier;
30
33
  events.push({
31
34
  name,
32
35
  timeUnixNano: Math.floor(input.now() * 1e6),
33
- attributes: Object.freeze({ ...attrs ?? {} })
36
+ attributes: Object.freeze({ ...attrs ?? {} }),
37
+ ...Object.keys(eventSensitivities).length > 0 ? { sensitivityByAttribute: Object.freeze(eventSensitivities) } : {}
34
38
  });
35
39
  },
36
40
  recordException(err) {
@@ -44,7 +48,12 @@ function createSpan(input) {
44
48
  events.push({
45
49
  name: "exception",
46
50
  timeUnixNano: Math.floor(input.now() * 1e6),
47
- attributes: attrs
51
+ attributes: attrs,
52
+ sensitivityByAttribute: Object.freeze({
53
+ "exception.type": "public",
54
+ "exception.message": "internal",
55
+ "exception.stacktrace": "internal"
56
+ })
48
57
  });
49
58
  },
50
59
  setStatus(s, message) {
@@ -1 +1 @@
1
- {"version":3,"file":"span.js","names":["attributes: Record<string, SpanAttributeValue>","sensitivities: Record<string, Sensitivity>","events: SpanRecord['events'][number][]","status: SpanStatus","statusMessage: string | undefined","attrs: SpanAttributes","record: SpanRecord<T>"],"sources":["../../src/tracer/span.ts"],"sourcesContent":["/**\n * Concrete `AISpan<T>` implementation. The span is intentionally\n * decoupled from `@opentelemetry/api`: the tracer materialises a\n * {@link SpanRecord} on `end()` and lets exporters do the rest.\n *\n * @packageDocumentation\n */\n\nimport type {\n AISpan,\n Sensitivity,\n SpanAttributes,\n SpanAttributeValue,\n SpanStatus,\n SpanType,\n} from '@graphorin/core';\n\nimport type { SpanRecord } from '../exporters/types.js';\n\n/**\n * Optional metadata accepted by `setAttribute(...)` to declare the\n * sensitivity of a single attribute. The validator uses the declared\n * tier when deciding whether to drop the attribute.\n *\n * @stable\n */\nexport interface SetAttributeOptions {\n readonly sensitivity?: Sensitivity;\n}\n\n/**\n * The internal span carries the convenience `setAttribute(...)` method\n * exposed by the tracer surface (per-attribute sensitivity tagging) on\n * top of the standard {@link AISpan} contract.\n *\n * @stable\n */\nexport interface GraphorinSpan<T extends SpanType = SpanType> extends AISpan<T> {\n setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void;\n}\n\n/**\n * @internal - sink invoked when a span ends.\n */\nexport type SpanSink = (record: SpanRecord) => void;\n\n/**\n * @internal - parameters passed by the tracer to {@link createSpan}.\n */\nexport interface SpanCreateInput<T extends SpanType = SpanType> {\n readonly type: T;\n readonly name: string;\n readonly traceId: string;\n readonly id: string;\n readonly parentId?: string;\n readonly attrs?: SpanAttributes;\n readonly attrSensitivities?: Readonly<Record<string, Sensitivity>>;\n readonly sink: SpanSink;\n readonly now: () => number;\n readonly recordEvent: (name: string) => boolean;\n}\n\n/**\n * @internal\n */\nexport function createSpan<T extends SpanType>(input: SpanCreateInput<T>): GraphorinSpan<T> {\n const startUnixNano = Math.floor(input.now() * 1_000_000);\n const attributes: Record<string, SpanAttributeValue> = { ...(input.attrs ?? {}) };\n const sensitivities: Record<string, Sensitivity> = { ...(input.attrSensitivities ?? {}) };\n const events: SpanRecord['events'][number][] = [];\n let status: SpanStatus = 'ok';\n let statusMessage: string | undefined;\n let ended = false;\n\n const span: GraphorinSpan<T> = {\n type: input.type,\n id: input.id,\n traceId: input.traceId,\n ...(input.parentId === undefined ? {} : { parentId: input.parentId }),\n setAttributes(attrs: SpanAttributes): void {\n if (ended) return;\n for (const [k, v] of Object.entries(attrs)) {\n attributes[k] = v;\n }\n },\n setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void {\n if (ended) return;\n attributes[name] = value;\n if (opts?.sensitivity !== undefined) {\n sensitivities[name] = opts.sensitivity;\n }\n },\n addEvent(name: string, attrs?: SpanAttributes): void {\n if (ended) return;\n if (!input.recordEvent(name)) return;\n events.push({\n name,\n timeUnixNano: Math.floor(input.now() * 1_000_000),\n attributes: Object.freeze({ ...(attrs ?? {}) }) as SpanAttributes,\n });\n },\n recordException(err: unknown): void {\n if (ended) return;\n const e = err as { name?: unknown; message?: unknown; stack?: unknown };\n const attrs: SpanAttributes = {\n 'exception.type': typeof e?.name === 'string' && e.name.length > 0 ? e.name : 'Error',\n 'exception.message': typeof e?.message === 'string' ? e.message : String(err),\n ...(typeof e?.stack === 'string' ? { 'exception.stacktrace': e.stack } : {}),\n };\n events.push({\n name: 'exception',\n timeUnixNano: Math.floor(input.now() * 1_000_000),\n attributes: attrs,\n });\n },\n setStatus(s: SpanStatus, message?: string): void {\n if (ended) return;\n status = s;\n if (message !== undefined) statusMessage = message;\n },\n end(): void {\n if (ended) return;\n ended = true;\n const endUnixNano = Math.floor(input.now() * 1_000_000);\n const record: SpanRecord<T> = {\n type: input.type,\n id: input.id,\n traceId: input.traceId,\n ...(input.parentId === undefined ? {} : { parentId: input.parentId }),\n name: input.name,\n startUnixNano,\n endUnixNano,\n status,\n ...(statusMessage === undefined ? {} : { statusMessage }),\n attributes: Object.freeze({ ...attributes }) as SpanAttributes,\n events: Object.freeze([...events]) as SpanRecord['events'],\n ...(Object.keys(sensitivities).length === 0\n ? {}\n : {\n sensitivityByAttribute: Object.freeze({ ...sensitivities }) as Readonly<\n Record<string, SpanAttributeValue>\n >,\n }),\n };\n input.sink(record);\n },\n };\n\n return span;\n}\n"],"mappings":";;;;AAiEA,SAAgB,WAA+B,OAA6C;CAC1F,MAAM,gBAAgB,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;CACzD,MAAMA,aAAiD,EAAE,GAAI,MAAM,SAAS,EAAE,EAAG;CACjF,MAAMC,gBAA6C,EAAE,GAAI,MAAM,qBAAqB,EAAE,EAAG;CACzF,MAAMC,SAAyC,EAAE;CACjD,IAAIC,SAAqB;CACzB,IAAIC;CACJ,IAAI,QAAQ;AA4EZ,QA1E+B;EAC7B,MAAM,MAAM;EACZ,IAAI,MAAM;EACV,SAAS,MAAM;EACf,GAAI,MAAM,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,MAAM,UAAU;EACpE,cAAc,OAA6B;AACzC,OAAI,MAAO;AACX,QAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CACxC,YAAW,KAAK;;EAGpB,aAAa,MAAc,OAA2B,MAAkC;AACtF,OAAI,MAAO;AACX,cAAW,QAAQ;AACnB,OAAI,MAAM,gBAAgB,OACxB,eAAc,QAAQ,KAAK;;EAG/B,SAAS,MAAc,OAA8B;AACnD,OAAI,MAAO;AACX,OAAI,CAAC,MAAM,YAAY,KAAK,CAAE;AAC9B,UAAO,KAAK;IACV;IACA,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;IACjD,YAAY,OAAO,OAAO,EAAE,GAAI,SAAS,EAAE,EAAG,CAAC;IAChD,CAAC;;EAEJ,gBAAgB,KAAoB;AAClC,OAAI,MAAO;GACX,MAAM,IAAI;GACV,MAAMC,QAAwB;IAC5B,kBAAkB,OAAO,GAAG,SAAS,YAAY,EAAE,KAAK,SAAS,IAAI,EAAE,OAAO;IAC9E,qBAAqB,OAAO,GAAG,YAAY,WAAW,EAAE,UAAU,OAAO,IAAI;IAC7E,GAAI,OAAO,GAAG,UAAU,WAAW,EAAE,wBAAwB,EAAE,OAAO,GAAG,EAAE;IAC5E;AACD,UAAO,KAAK;IACV,MAAM;IACN,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;IACjD,YAAY;IACb,CAAC;;EAEJ,UAAU,GAAe,SAAwB;AAC/C,OAAI,MAAO;AACX,YAAS;AACT,OAAI,YAAY,OAAW,iBAAgB;;EAE7C,MAAY;AACV,OAAI,MAAO;AACX,WAAQ;GACR,MAAM,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;GACvD,MAAMC,SAAwB;IAC5B,MAAM,MAAM;IACZ,IAAI,MAAM;IACV,SAAS,MAAM;IACf,GAAI,MAAM,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,MAAM,UAAU;IACpE,MAAM,MAAM;IACZ;IACA;IACA;IACA,GAAI,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe;IACxD,YAAY,OAAO,OAAO,EAAE,GAAG,YAAY,CAAC;IAC5C,QAAQ,OAAO,OAAO,CAAC,GAAG,OAAO,CAAC;IAClC,GAAI,OAAO,KAAK,cAAc,CAAC,WAAW,IACtC,EAAE,GACF,EACE,wBAAwB,OAAO,OAAO,EAAE,GAAG,eAAe,CAAC,EAG5D;IACN;AACD,SAAM,KAAK,OAAO;;EAErB"}
1
+ {"version":3,"file":"span.js","names":["attributes: Record<string, SpanAttributeValue>","sensitivities: Record<string, Sensitivity>","events: SpanRecord['events'][number][]","status: SpanStatus","statusMessage: string | undefined","eventSensitivities: Record<string, 'public' | 'internal' | 'secret'>","attrs: SpanAttributes","record: SpanRecord<T>"],"sources":["../../src/tracer/span.ts"],"sourcesContent":["/**\n * Concrete `AISpan<T>` implementation. The span is intentionally\n * decoupled from `@opentelemetry/api`: the tracer materialises a\n * {@link SpanRecord} on `end()` and lets exporters do the rest.\n *\n * @packageDocumentation\n */\n\nimport type {\n AddEventOptions,\n AISpan,\n Sensitivity,\n SpanAttributes,\n SpanAttributeValue,\n SpanStatus,\n SpanType,\n} from '@graphorin/core';\n\nimport type { SpanRecord } from '../exporters/types.js';\n\n/**\n * Optional metadata accepted by `setAttribute(...)` to declare the\n * sensitivity of a single attribute. The validator uses the declared\n * tier when deciding whether to drop the attribute.\n *\n * @stable\n */\nexport interface SetAttributeOptions {\n readonly sensitivity?: Sensitivity;\n}\n\n/**\n * The internal span carries the convenience `setAttribute(...)` method\n * exposed by the tracer surface (per-attribute sensitivity tagging) on\n * top of the standard {@link AISpan} contract.\n *\n * @stable\n */\nexport interface GraphorinSpan<T extends SpanType = SpanType> extends AISpan<T> {\n setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void;\n}\n\n/**\n * @internal - sink invoked when a span ends.\n */\nexport type SpanSink = (record: SpanRecord) => void;\n\n/**\n * @internal - parameters passed by the tracer to {@link createSpan}.\n */\nexport interface SpanCreateInput<T extends SpanType = SpanType> {\n readonly type: T;\n readonly name: string;\n readonly traceId: string;\n readonly id: string;\n readonly parentId?: string;\n readonly attrs?: SpanAttributes;\n readonly attrSensitivities?: Readonly<Record<string, Sensitivity>>;\n readonly sink: SpanSink;\n readonly now: () => number;\n readonly recordEvent: (name: string) => boolean;\n}\n\n/**\n * @internal\n */\nexport function createSpan<T extends SpanType>(input: SpanCreateInput<T>): GraphorinSpan<T> {\n const startUnixNano = Math.floor(input.now() * 1_000_000);\n const attributes: Record<string, SpanAttributeValue> = { ...(input.attrs ?? {}) };\n const sensitivities: Record<string, Sensitivity> = { ...(input.attrSensitivities ?? {}) };\n const events: SpanRecord['events'][number][] = [];\n let status: SpanStatus = 'ok';\n let statusMessage: string | undefined;\n let ended = false;\n\n const span: GraphorinSpan<T> = {\n type: input.type,\n id: input.id,\n traceId: input.traceId,\n ...(input.parentId === undefined ? {} : { parentId: input.parentId }),\n setAttributes(attrs: SpanAttributes): void {\n if (ended) return;\n for (const [k, v] of Object.entries(attrs)) {\n attributes[k] = v;\n }\n },\n setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void {\n if (ended) return;\n attributes[name] = value;\n if (opts?.sensitivity !== undefined) {\n sensitivities[name] = opts.sensitivity;\n }\n },\n addEvent(name: string, attrs?: SpanAttributes, opts?: AddEventOptions): void {\n if (ended) return;\n if (!input.recordEvent(name)) return;\n // W-094: resolve the event default + per-attribute overrides into\n // one map the validation exporter consumes. Untagged attributes\n // stay untagged - default-deny below the floor is preserved.\n const eventSensitivities: Record<string, 'public' | 'internal' | 'secret'> = {};\n if (opts?.sensitivity !== undefined) {\n for (const key of Object.keys(attrs ?? {})) {\n eventSensitivities[key] = opts.sensitivity;\n }\n }\n for (const [key, tier] of Object.entries(opts?.sensitivityByAttribute ?? {})) {\n eventSensitivities[key] = tier;\n }\n events.push({\n name,\n timeUnixNano: Math.floor(input.now() * 1_000_000),\n attributes: Object.freeze({ ...(attrs ?? {}) }) as SpanAttributes,\n ...(Object.keys(eventSensitivities).length > 0\n ? { sensitivityByAttribute: Object.freeze(eventSensitivities) }\n : {}),\n });\n },\n recordException(err: unknown): void {\n if (ended) return;\n const e = err as { name?: unknown; message?: unknown; stack?: unknown };\n const attrs: SpanAttributes = {\n 'exception.type': typeof e?.name === 'string' && e.name.length > 0 ? e.name : 'Error',\n 'exception.message': typeof e?.message === 'string' ? e.message : String(err),\n ...(typeof e?.stack === 'string' ? { 'exception.stacktrace': e.stack } : {}),\n };\n events.push({\n name: 'exception',\n timeUnixNano: Math.floor(input.now() * 1_000_000),\n attributes: attrs,\n // W-094: the CLASS NAME of an error is safe and load-bearing for\n // out-of-the-box error dashboards - tag it 'public'. Message +\n // stacktrace stay 'internal' (paritous with the un-sanitized\n // statusMessage; the PII patterns still apply at that floor).\n sensitivityByAttribute: Object.freeze({\n 'exception.type': 'public',\n 'exception.message': 'internal',\n 'exception.stacktrace': 'internal',\n }),\n });\n },\n setStatus(s: SpanStatus, message?: string): void {\n if (ended) return;\n status = s;\n if (message !== undefined) statusMessage = message;\n },\n end(): void {\n if (ended) return;\n ended = true;\n const endUnixNano = Math.floor(input.now() * 1_000_000);\n const record: SpanRecord<T> = {\n type: input.type,\n id: input.id,\n traceId: input.traceId,\n ...(input.parentId === undefined ? {} : { parentId: input.parentId }),\n name: input.name,\n startUnixNano,\n endUnixNano,\n status,\n ...(statusMessage === undefined ? {} : { statusMessage }),\n attributes: Object.freeze({ ...attributes }) as SpanAttributes,\n events: Object.freeze([...events]) as SpanRecord['events'],\n ...(Object.keys(sensitivities).length === 0\n ? {}\n : {\n sensitivityByAttribute: Object.freeze({ ...sensitivities }) as Readonly<\n Record<string, SpanAttributeValue>\n >,\n }),\n };\n input.sink(record);\n },\n };\n\n return span;\n}\n"],"mappings":";;;;AAkEA,SAAgB,WAA+B,OAA6C;CAC1F,MAAM,gBAAgB,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;CACzD,MAAMA,aAAiD,EAAE,GAAI,MAAM,SAAS,EAAE,EAAG;CACjF,MAAMC,gBAA6C,EAAE,GAAI,MAAM,qBAAqB,EAAE,EAAG;CACzF,MAAMC,SAAyC,EAAE;CACjD,IAAIC,SAAqB;CACzB,IAAIC;CACJ,IAAI,QAAQ;AAoGZ,QAlG+B;EAC7B,MAAM,MAAM;EACZ,IAAI,MAAM;EACV,SAAS,MAAM;EACf,GAAI,MAAM,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,MAAM,UAAU;EACpE,cAAc,OAA6B;AACzC,OAAI,MAAO;AACX,QAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CACxC,YAAW,KAAK;;EAGpB,aAAa,MAAc,OAA2B,MAAkC;AACtF,OAAI,MAAO;AACX,cAAW,QAAQ;AACnB,OAAI,MAAM,gBAAgB,OACxB,eAAc,QAAQ,KAAK;;EAG/B,SAAS,MAAc,OAAwB,MAA8B;AAC3E,OAAI,MAAO;AACX,OAAI,CAAC,MAAM,YAAY,KAAK,CAAE;GAI9B,MAAMC,qBAAuE,EAAE;AAC/E,OAAI,MAAM,gBAAgB,OACxB,MAAK,MAAM,OAAO,OAAO,KAAK,SAAS,EAAE,CAAC,CACxC,oBAAmB,OAAO,KAAK;AAGnC,QAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,MAAM,0BAA0B,EAAE,CAAC,CAC1E,oBAAmB,OAAO;AAE5B,UAAO,KAAK;IACV;IACA,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;IACjD,YAAY,OAAO,OAAO,EAAE,GAAI,SAAS,EAAE,EAAG,CAAC;IAC/C,GAAI,OAAO,KAAK,mBAAmB,CAAC,SAAS,IACzC,EAAE,wBAAwB,OAAO,OAAO,mBAAmB,EAAE,GAC7D,EAAE;IACP,CAAC;;EAEJ,gBAAgB,KAAoB;AAClC,OAAI,MAAO;GACX,MAAM,IAAI;GACV,MAAMC,QAAwB;IAC5B,kBAAkB,OAAO,GAAG,SAAS,YAAY,EAAE,KAAK,SAAS,IAAI,EAAE,OAAO;IAC9E,qBAAqB,OAAO,GAAG,YAAY,WAAW,EAAE,UAAU,OAAO,IAAI;IAC7E,GAAI,OAAO,GAAG,UAAU,WAAW,EAAE,wBAAwB,EAAE,OAAO,GAAG,EAAE;IAC5E;AACD,UAAO,KAAK;IACV,MAAM;IACN,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;IACjD,YAAY;IAKZ,wBAAwB,OAAO,OAAO;KACpC,kBAAkB;KAClB,qBAAqB;KACrB,wBAAwB;KACzB,CAAC;IACH,CAAC;;EAEJ,UAAU,GAAe,SAAwB;AAC/C,OAAI,MAAO;AACX,YAAS;AACT,OAAI,YAAY,OAAW,iBAAgB;;EAE7C,MAAY;AACV,OAAI,MAAO;AACX,WAAQ;GACR,MAAM,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;GACvD,MAAMC,SAAwB;IAC5B,MAAM,MAAM;IACZ,IAAI,MAAM;IACV,SAAS,MAAM;IACf,GAAI,MAAM,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,MAAM,UAAU;IACpE,MAAM,MAAM;IACZ;IACA;IACA;IACA,GAAI,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe;IACxD,YAAY,OAAO,OAAO,EAAE,GAAG,YAAY,CAAC;IAC5C,QAAQ,OAAO,OAAO,CAAC,GAAG,OAAO,CAAC;IAClC,GAAI,OAAO,KAAK,cAAc,CAAC,WAAW,IACtC,EAAE,GACF,EACE,wBAAwB,OAAO,OAAO,EAAE,GAAG,eAAe,CAAC,EAG5D;IACN;AACD,SAAM,KAAK,OAAO;;EAErB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphorin/observability",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Observability primitives for the Graphorin framework: typed AISpan tracer over OpenTelemetry, mandatory withValidation wrapper for every exporter, sensitivity-aware RedactionValidator with built-in PII / secret patterns, GenAI Semantic Conventions conformance helpers, OpenInference span-kind emission, structured logger with span correlation, append-only JSONL exporter for replay, sanitized replay primitives, hierarchical CostTracker, and a minimal inline eval runner alongside a zero-default telemetry stub.",
5
5
  "license": "MIT",
6
6
  "author": "Oleksiy Stepurenko",
@@ -32,8 +32,9 @@
32
32
  "logger"
33
33
  ],
34
34
  "type": "module",
35
+ "sideEffects": false,
35
36
  "engines": {
36
- "node": ">=22.0.0"
37
+ "node": ">=22.12.0"
37
38
  },
38
39
  "main": "./dist/index.js",
39
40
  "module": "./dist/index.js",
@@ -41,91 +42,73 @@
41
42
  "exports": {
42
43
  ".": {
43
44
  "types": "./dist/index.d.ts",
44
- "import": "./dist/index.js"
45
+ "default": "./dist/index.js"
45
46
  },
46
47
  "./tracer": {
47
48
  "types": "./dist/tracer/index.d.ts",
48
- "import": "./dist/tracer/index.js"
49
+ "default": "./dist/tracer/index.js"
49
50
  },
50
51
  "./redaction": {
51
52
  "types": "./dist/redaction/index.d.ts",
52
- "import": "./dist/redaction/index.js"
53
+ "default": "./dist/redaction/index.js"
53
54
  },
54
55
  "./redaction/patterns": {
55
56
  "types": "./dist/redaction/patterns.d.ts",
56
- "import": "./dist/redaction/patterns.js"
57
+ "default": "./dist/redaction/patterns.js"
57
58
  },
58
59
  "./redaction/imperative-patterns": {
59
60
  "types": "./dist/redaction/imperative-patterns.d.ts",
60
- "import": "./dist/redaction/imperative-patterns.js"
61
+ "default": "./dist/redaction/imperative-patterns.js"
61
62
  },
62
63
  "./exporters": {
63
64
  "types": "./dist/exporters/index.d.ts",
64
- "import": "./dist/exporters/index.js"
65
+ "default": "./dist/exporters/index.js"
65
66
  },
66
67
  "./gen-ai": {
67
68
  "types": "./dist/gen-ai/index.d.ts",
68
- "import": "./dist/gen-ai/index.js"
69
+ "default": "./dist/gen-ai/index.js"
69
70
  },
70
71
  "./openinference": {
71
72
  "types": "./dist/openinference/index.d.ts",
72
- "import": "./dist/openinference/index.js"
73
+ "default": "./dist/openinference/index.js"
73
74
  },
74
75
  "./cost": {
75
76
  "types": "./dist/cost/index.d.ts",
76
- "import": "./dist/cost/index.js"
77
+ "default": "./dist/cost/index.js"
77
78
  },
78
79
  "./replay": {
79
80
  "types": "./dist/replay/index.d.ts",
80
- "import": "./dist/replay/index.js"
81
+ "default": "./dist/replay/index.js"
81
82
  },
82
83
  "./eval": {
83
84
  "types": "./dist/eval/index.d.ts",
84
- "import": "./dist/eval/index.js"
85
+ "default": "./dist/eval/index.js"
85
86
  },
86
87
  "./logger": {
87
88
  "types": "./dist/logger/index.d.ts",
88
- "import": "./dist/logger/index.js"
89
+ "default": "./dist/logger/index.js"
89
90
  },
90
91
  "./telemetry": {
91
92
  "types": "./dist/telemetry/index.d.ts",
92
- "import": "./dist/telemetry/index.js"
93
+ "default": "./dist/telemetry/index.js"
93
94
  },
94
95
  "./package.json": "./package.json"
95
96
  },
96
97
  "files": [
97
98
  "dist",
99
+ "src",
98
100
  "README.md",
99
101
  "CHANGELOG.md",
100
102
  "LICENSE"
101
103
  ],
102
104
  "dependencies": {
103
- "@graphorin/core": "0.6.1"
104
- },
105
- "peerDependencies": {
106
- "@opentelemetry/api": "^1.9.0",
107
- "@opentelemetry/sdk-node": "^0.217.0",
108
- "@opentelemetry/exporter-trace-otlp-http": "^0.215.0"
109
- },
110
- "peerDependenciesMeta": {
111
- "@opentelemetry/api": {
112
- "optional": false
113
- },
114
- "@opentelemetry/sdk-node": {
115
- "optional": true
116
- },
117
- "@opentelemetry/exporter-trace-otlp-http": {
118
- "optional": true
119
- }
105
+ "@graphorin/core": "0.7.0"
120
106
  },
121
107
  "publishConfig": {
122
108
  "access": "public",
123
109
  "provenance": true
124
110
  },
125
111
  "devDependencies": {
126
- "@opentelemetry/api": "^1.9.0",
127
- "@opentelemetry/exporter-trace-otlp-http": "^0.215.0",
128
- "@opentelemetry/sdk-node": "^0.217.0",
129
112
  "fast-check": "^3.23.0"
130
113
  },
131
114
  "scripts": {