@armoriq/sdk-dev 0.6.8 → 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 (99) hide show
  1. package/README.md +168 -1
  2. package/dist/_version.d.ts +1 -1
  3. package/dist/_version.js +1 -1
  4. package/dist/cli/commands/auth.d.ts +12 -0
  5. package/dist/cli/commands/auth.d.ts.map +1 -1
  6. package/dist/cli/commands/auth.js +460 -42
  7. package/dist/cli/commands/auth.js.map +1 -1
  8. package/dist/cli/index.js +0 -0
  9. package/dist/client.d.ts +8 -15
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +20 -18
  12. package/dist/client.js.map +1 -1
  13. package/dist/config.d.ts +0 -17
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +1 -19
  16. package/dist/config.js.map +1 -1
  17. package/dist/index.d.ts +3 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +8 -14
  20. package/dist/index.js.map +1 -1
  21. package/dist/integrations/google_adk.d.ts +168 -6
  22. package/dist/integrations/google_adk.d.ts.map +1 -1
  23. package/dist/integrations/google_adk.js +797 -103
  24. package/dist/integrations/google_adk.js.map +1 -1
  25. package/dist/integrations/langchain.d.ts +51 -1
  26. package/dist/integrations/langchain.d.ts.map +1 -1
  27. package/dist/integrations/langchain.js +563 -30
  28. package/dist/integrations/langchain.js.map +1 -1
  29. package/dist/integrations/strands.d.ts +48 -0
  30. package/dist/integrations/strands.d.ts.map +1 -1
  31. package/dist/integrations/strands.js +441 -27
  32. package/dist/integrations/strands.js.map +1 -1
  33. package/dist/models.d.ts +2 -2
  34. package/dist/models.d.ts.map +1 -1
  35. package/dist/observability/content-capture.d.ts +75 -0
  36. package/dist/observability/content-capture.d.ts.map +1 -0
  37. package/dist/observability/content-capture.js +339 -0
  38. package/dist/observability/content-capture.js.map +1 -0
  39. package/dist/observability/index.d.ts +6 -7
  40. package/dist/observability/index.d.ts.map +1 -1
  41. package/dist/observability/index.js +18 -27
  42. package/dist/observability/index.js.map +1 -1
  43. package/dist/observability/otel-config.d.ts +47 -0
  44. package/dist/observability/otel-config.d.ts.map +1 -0
  45. package/dist/observability/otel-config.js +268 -0
  46. package/dist/observability/otel-config.js.map +1 -0
  47. package/dist/observability/otel-export-ceiling.d.ts +96 -0
  48. package/dist/observability/otel-export-ceiling.d.ts.map +1 -0
  49. package/dist/observability/otel-export-ceiling.js +264 -0
  50. package/dist/observability/otel-export-ceiling.js.map +1 -0
  51. package/dist/observability/otel-runtime.d.ts +103 -0
  52. package/dist/observability/otel-runtime.d.ts.map +1 -0
  53. package/dist/observability/otel-runtime.js +668 -0
  54. package/dist/observability/otel-runtime.js.map +1 -0
  55. package/dist/observability/otel-session.d.ts +168 -0
  56. package/dist/observability/otel-session.d.ts.map +1 -0
  57. package/dist/observability/otel-session.js +621 -0
  58. package/dist/observability/otel-session.js.map +1 -0
  59. package/dist/observability/otel-shutdown.d.ts +17 -0
  60. package/dist/observability/otel-shutdown.d.ts.map +1 -0
  61. package/dist/observability/otel-shutdown.js +54 -0
  62. package/dist/observability/otel-shutdown.js.map +1 -0
  63. package/dist/observability/policy-lease.d.ts +22 -0
  64. package/dist/observability/policy-lease.d.ts.map +1 -0
  65. package/dist/observability/policy-lease.js +102 -0
  66. package/dist/observability/policy-lease.js.map +1 -0
  67. package/dist/plan_builder.d.ts +5 -4
  68. package/dist/plan_builder.d.ts.map +1 -1
  69. package/dist/plan_builder.js +14 -15
  70. package/dist/plan_builder.js.map +1 -1
  71. package/dist/session.d.ts +61 -93
  72. package/dist/session.d.ts.map +1 -1
  73. package/dist/session.js +388 -812
  74. package/dist/session.js.map +1 -1
  75. package/dist/token_usage.d.ts +11 -18
  76. package/dist/token_usage.d.ts.map +1 -1
  77. package/dist/token_usage.js +29 -94
  78. package/dist/token_usage.js.map +1 -1
  79. package/dist/tool_name.d.ts +18 -0
  80. package/dist/tool_name.d.ts.map +1 -0
  81. package/dist/tool_name.js +29 -0
  82. package/dist/tool_name.js.map +1 -0
  83. package/dist/tool_push.d.ts +28 -0
  84. package/dist/tool_push.d.ts.map +1 -0
  85. package/dist/tool_push.js +151 -0
  86. package/dist/tool_push.js.map +1 -0
  87. package/dist/tool_registry.d.ts +100 -0
  88. package/dist/tool_registry.d.ts.map +1 -0
  89. package/dist/tool_registry.js +440 -0
  90. package/dist/tool_registry.js.map +1 -0
  91. package/dist/tool_schema.d.ts +22 -0
  92. package/dist/tool_schema.d.ts.map +1 -0
  93. package/dist/tool_schema.js +163 -0
  94. package/dist/tool_schema.js.map +1 -0
  95. package/package.json +13 -7
  96. package/dist/integrations/microsoft_copilot.d.ts +0 -84
  97. package/dist/integrations/microsoft_copilot.d.ts.map +0 -1
  98. package/dist/integrations/microsoft_copilot.js +0 -126
  99. package/dist/integrations/microsoft_copilot.js.map +0 -1
@@ -0,0 +1,621 @@
1
+ "use strict";
2
+ /** Request-scoped native OTel span bridge for SDK and framework adapters. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.OtelSession = void 0;
5
+ exports.modelSystem = modelSystem;
6
+ const crypto_1 = require("crypto");
7
+ const api_1 = require("@opentelemetry/api");
8
+ const content_capture_1 = require("./content-capture");
9
+ const TASK_OUTCOME = Object.freeze({
10
+ ok: 'completed',
11
+ error: 'failed',
12
+ denied: 'denied',
13
+ cancelled: 'abandoned',
14
+ timeout: 'abandoned',
15
+ disconnected: 'abandoned',
16
+ bootstrap: 'unknown',
17
+ process_exit: 'abandoned',
18
+ });
19
+ const MAX_PLAN_STEP_ORDINAL = 999_999_999;
20
+ /**
21
+ * The SDK's own enforcement vocabulary (`'allow' | 'block' | 'hold' | 'error'`,
22
+ * used throughout session.ts/client.ts/the framework integrations) is not the
23
+ * observability wire's vocabulary. The Python SDK's single mapping point,
24
+ * `_emit_enforce_policy_call`, maps its own allow/block/hold decision onto the
25
+ * wire's `allow`/`deny`/`hold` — this is the TypeScript equivalent of that one
26
+ * mapping point. `'error'` has no Python analog here: an enforcement error is
27
+ * not a policy verdict at all (Python represents it with `decision=None`, a
28
+ * distinct code path from this four-way SDK enum), so it passes through
29
+ * unmapped rather than being folded into the deny/hold/allow vocabulary.
30
+ */
31
+ function toWirePolicyDecision(decision) {
32
+ return decision === 'block' ? 'deny' : decision;
33
+ }
34
+ /** Matches the shared Python/TypeScript fixture: `plan-step-N`, with no leading zeroes. */
35
+ function canonicalPlanStepOrdinal(value) {
36
+ if (typeof value !== 'number'
37
+ || !Number.isSafeInteger(value)
38
+ || value < 0
39
+ || value > MAX_PLAN_STEP_ORDINAL) {
40
+ return undefined;
41
+ }
42
+ return value;
43
+ }
44
+ function errorType(error) {
45
+ if (!error)
46
+ return undefined;
47
+ if (error instanceof Error)
48
+ return error.name.slice(0, 128);
49
+ return 'Error';
50
+ }
51
+ /** Never export a stack: messages still pass through the shared projector. */
52
+ function safeErrorSummary(error) {
53
+ if (!error)
54
+ return undefined;
55
+ if (error instanceof Error)
56
+ return { type: error.name.slice(0, 128), message: error.message.slice(0, 2_048) };
57
+ if (typeof error === 'string')
58
+ return { type: 'Error', message: error.slice(0, 2_048) };
59
+ return { type: 'Error', message: String(error).slice(0, 2_048) };
60
+ }
61
+ function asStatus(status) {
62
+ return status === 'ok' ? api_1.SpanStatusCode.OK : api_1.SpanStatusCode.ERROR;
63
+ }
64
+ /**
65
+ * Map a well-known model identifier to a `gen_ai.system` provider label.
66
+ * Matches the Python SDK's `armoriq_sdk/observability/otel_semantics.py`
67
+ * `model_system()`. Descriptive metadata only — an unrecognized model
68
+ * returns undefined rather than a guessed provider.
69
+ */
70
+ function modelSystem(model) {
71
+ if (typeof model !== 'string')
72
+ return undefined;
73
+ const normalized = model.toLowerCase();
74
+ if (normalized.startsWith('gpt-') || normalized.startsWith('o1') || normalized.startsWith('o3') || normalized.startsWith('o4'))
75
+ return 'openai';
76
+ if (normalized.startsWith('claude-'))
77
+ return 'anthropic';
78
+ if (normalized.startsWith('gemini-') || normalized.startsWith('google/'))
79
+ return 'google';
80
+ return undefined;
81
+ }
82
+ /**
83
+ * This bridge owns only request span state. Providers and exporters stay in
84
+ * ArmorIQTelemetryRuntime, so concurrent sessions cannot affect each other or
85
+ * the host application's global OTel setup.
86
+ */
87
+ class OtelSession {
88
+ runtime;
89
+ options;
90
+ root = null;
91
+ plan = null;
92
+ planId = null;
93
+ snapshot = null;
94
+ closed = false;
95
+ closing = false;
96
+ closePromise = null;
97
+ pendingToolContent = new Map();
98
+ constructor(runtime, options) {
99
+ this.runtime = runtime;
100
+ this.options = { ...options, traceName: options.traceName ?? 'armoriq.agent.run' };
101
+ }
102
+ get enabled() {
103
+ return !this.closed && !this.closing && this.runtime.config.enabled;
104
+ }
105
+ async attributes(values) {
106
+ const snapshot = await this.currentSnapshot();
107
+ if (!snapshot)
108
+ return {};
109
+ return this.runtime.safeAttributes(snapshot, values, this.attributeContext());
110
+ }
111
+ /** Explicit readiness path for setup/tests; normal request hooks never await it. */
112
+ async refreshPolicy() {
113
+ await this.runtime.policySnapshot({ awaitFresh: true });
114
+ }
115
+ async beginRoot(content = {}) {
116
+ const traceName = this.runtime.externalTraceName(this.options.traceName);
117
+ if (this.closed || this.closing || this.root)
118
+ return { span: this.root, name: traceName };
119
+ // A borrowed provider belongs to the application, so retain its active
120
+ // request parent. ArmorIQ-owned exporters stay rooted at ROOT_CONTEXT to
121
+ // prevent unrelated application trace context from crossing that boundary.
122
+ const parentContext = this.runtime.config.exporter === 'provider' ? api_1.context.active() : api_1.ROOT_CONTEXT;
123
+ const snapshot = await this.currentSnapshot();
124
+ const tracer = snapshot ? this.runtime.tracer(snapshot) : null;
125
+ if (!snapshot || !tracer)
126
+ return { span: null, name: traceName };
127
+ try {
128
+ const attrs = this.runtime.safeAttributes(snapshot, {
129
+ 'armoriq.session_id': this.options.sessionId,
130
+ 'gen_ai.operation.name': 'invoke_agent',
131
+ 'armoriq.timing.provenance': 'measured',
132
+ ...(this.options.agentId ? { 'gen_ai.agent.name': this.options.agentId } : {}),
133
+ ...(this.options.agentId ? { 'armoriq.agent_id': this.options.agentId } : {}),
134
+ // A missing framework hook should remain visible as an SDK limitation,
135
+ // not look like a successful capture of an empty prompt.
136
+ 'gen_ai.input.messages': content.input,
137
+ }, this.attributeContext());
138
+ this.root = tracer.startSpan(traceName, { attributes: attrs }, parentContext);
139
+ return { span: this.root, name: traceName };
140
+ }
141
+ catch {
142
+ return { span: null, name: traceName };
143
+ }
144
+ }
145
+ async startPlan(input) {
146
+ await this.endSpan(this.plan, 'ok');
147
+ this.plan = null;
148
+ this.planId = null;
149
+ const root = (await this.beginRoot()).span;
150
+ const snapshot = await this.currentSnapshot();
151
+ const tracer = snapshot ? this.runtime.tracer(snapshot) : null;
152
+ if (!root || !tracer || !snapshot)
153
+ return { span: null, name: 'armoriq.intent.plan' };
154
+ try {
155
+ const attrs = this.runtime.safeAttributes(snapshot, {
156
+ 'gen_ai.operation.name': 'plan',
157
+ 'armoriq.timing.provenance': 'measured',
158
+ ...(this.options.agentId ? { 'gen_ai.agent.name': this.options.agentId } : {}),
159
+ 'armoriq.intent_plan_item_count': input.itemCount,
160
+ ...(input.planId ? { 'armoriq.intent_plan_id': input.planId } : {}),
161
+ }, this.attributeContext());
162
+ // Pass the parent context directly. `context.with()` can be a no-op when
163
+ // the application has not installed a context manager, which would turn
164
+ // every child into a separate trace.
165
+ this.plan = tracer.startSpan('armoriq.intent.plan', { attributes: attrs }, api_1.trace.setSpan(api_1.ROOT_CONTEXT, root));
166
+ this.planId = input.planId ?? `otel-plan-${this.plan.spanContext().spanId}`;
167
+ if (!input.planId) {
168
+ this.plan.setAttribute('armoriq.external_intent_plan_id', this.planId);
169
+ }
170
+ return { span: this.plan, name: 'armoriq.intent.plan' };
171
+ }
172
+ catch {
173
+ return { span: null, name: 'armoriq.intent.plan' };
174
+ }
175
+ }
176
+ async beginPolicy(input) {
177
+ return this.beginChild('armoriq.policy.evaluate', {
178
+ ...this.planItemAttributes(input),
179
+ 'armoriq.policy.tool_name': input.toolName,
180
+ ...this.planItemOrdinalAttributes(input.itemOrdinal),
181
+ ...(input.toolCallId ? { 'gen_ai.tool.call.id': input.toolCallId } : {}),
182
+ ...(Object.prototype.hasOwnProperty.call(input, 'arguments') ? { 'gen_ai.tool.call.arguments': input.arguments } : {}),
183
+ });
184
+ }
185
+ async endPolicy(handle, result) {
186
+ const decision = result.decision;
187
+ await this.endSpan(handle.span, result.terminalStatus ?? (decision === 'allow' ? 'ok' : decision === 'block' ? 'denied' : 'error'), {
188
+ ...(decision ? { 'armoriq.policy.decision': toWirePolicyDecision(decision) } : {}),
189
+ ...(result.policyName ? { 'armoriq.policy.name': result.policyName } : {}),
190
+ ...(result.policyId ? { 'armoriq.policy.id': result.policyId } : {}),
191
+ ...(result.policyVersion ? { 'armoriq.policy.version': result.policyVersion } : {}),
192
+ ...(result.matchedRuleId ?? result.ruleId ? { 'armoriq.policy.rule_id': result.matchedRuleId ?? result.ruleId } : {}),
193
+ ...(result.policySource ? { 'armoriq.policy.source': result.policySource } : {}),
194
+ ...(result.policyReasonCode ? { 'armoriq.policy.reason_code': result.policyReasonCode } : {}),
195
+ ...(result.defaultAction ? { 'armoriq.policy.default_action': result.defaultAction } : {}),
196
+ 'armoriq.intent_plan_item_status': decision === 'allow'
197
+ ? 'allowed'
198
+ : decision === 'hold'
199
+ ? 'waiting'
200
+ : decision === 'block'
201
+ ? 'blocked'
202
+ : 'failed',
203
+ ...(errorType(result.error) ? { 'error.type': errorType(result.error) } : {}),
204
+ ...(safeErrorSummary(result.error) ? { 'armoriq.observability.error.summary': safeErrorSummary(result.error) } : {}),
205
+ });
206
+ }
207
+ /** Record a decision that completed before telemetry started. */
208
+ async recordPolicy(input, result) {
209
+ const handle = await this.beginChild('armoriq.policy.evaluate', {
210
+ ...this.planItemAttributes(input),
211
+ 'armoriq.policy.tool_name': input.toolName,
212
+ ...this.planItemOrdinalAttributes(input.itemOrdinal),
213
+ ...(input.toolCallId ? { 'gen_ai.tool.call.id': input.toolCallId } : {}),
214
+ ...(Object.prototype.hasOwnProperty.call(input, 'arguments') ? { 'gen_ai.tool.call.arguments': input.arguments } : {}),
215
+ }, 'post_hoc');
216
+ await this.endPolicy(handle, result);
217
+ }
218
+ async beginTool(input) {
219
+ return this.beginToolWithTiming(input, 'measured');
220
+ }
221
+ /** Record a tool that completed before this SDK observed its lifecycle. */
222
+ async recordTool(input, result) {
223
+ const timing = safeNonNegative(result.durationMs) === undefined ? 'post_hoc' : 'reported';
224
+ const handle = await this.beginToolWithTiming(input, timing);
225
+ await this.endTool(handle, result);
226
+ }
227
+ async beginToolWithTiming(input, timing) {
228
+ const operation = await this.beginOperationWithTiming({
229
+ category: input.operation?.category ?? 'tool',
230
+ name: input.operation?.name ?? 'armoriq.tool',
231
+ toolType: input.operation?.toolType ?? 'function',
232
+ ...input.operation,
233
+ toolName: input.toolName,
234
+ callId: input.toolCallId,
235
+ planItemOrdinal: input.itemOrdinal,
236
+ }, timing);
237
+ if (operation.span && Object.prototype.hasOwnProperty.call(input, 'arguments')) {
238
+ this.queueToolContent(operation.span, { toolArguments: input.arguments });
239
+ }
240
+ return operation;
241
+ }
242
+ /** SDK audit transport metadata; distinct from a framework tool execution. */
243
+ async beginAuditTool(input) {
244
+ return this.beginOperation({
245
+ category: 'tool',
246
+ name: 'tool.execute',
247
+ toolType: 'function',
248
+ toolName: input.toolName,
249
+ callId: input.toolCallId,
250
+ planItemOrdinal: input.itemOrdinal,
251
+ });
252
+ }
253
+ async beginOperation(input) {
254
+ return this.beginOperationWithTiming(input, 'measured');
255
+ }
256
+ async beginOperationWithTiming(input, timing) {
257
+ if (!isOperationCategory(input.category))
258
+ return { span: null, name: 'armoriq.operation' };
259
+ const toolName = safeIdentifier(input.toolName);
260
+ const planId = safeIdentifier(input.planId ?? this.planId);
261
+ const planItemOrdinal = canonicalPlanStepOrdinal(input.planItemOrdinal);
262
+ const values = {
263
+ 'armoriq.operation.category': input.category,
264
+ };
265
+ const toolType = isToolType(input.toolType) ? input.toolType : defaultToolType(input.category);
266
+ if (toolType)
267
+ values['armoriq.tool.type'] = toolType;
268
+ if (toolName) {
269
+ values['armoriq.tool.name'] = toolName;
270
+ values['gen_ai.tool.name'] = toolName;
271
+ values['gen_ai.operation.name'] = 'execute_tool';
272
+ const callId = safeIdentifier(input.callId);
273
+ values['armoriq.tool.call_id'] = callId
274
+ ?? this.stableOperationCallId(input.category, toolName, planId, planItemOrdinal);
275
+ if (callId)
276
+ values['gen_ai.tool.call.id'] = callId;
277
+ }
278
+ const mcpServer = safeIdentifier(input.mcpServer);
279
+ if (mcpServer)
280
+ values['armoriq.mcp.server'] = mcpServer;
281
+ if (planId)
282
+ values['armoriq.intent_plan_id'] = planId;
283
+ const planItemId = safeIdentifier(input.planItemId)
284
+ ?? (planItemOrdinal === undefined ? undefined : `plan-step-${planItemOrdinal}`);
285
+ if (planItemId)
286
+ values['armoriq.intent_plan_item_id'] = planItemId;
287
+ if (planItemOrdinal !== undefined)
288
+ values['armoriq.intent_plan_item_ordinal'] = planItemOrdinal;
289
+ const executable = safeExecutable(input.executableName);
290
+ if (executable)
291
+ values['process.executable.name'] = executable;
292
+ Object.assign(values, terminalCommandAttributes(input.category, input));
293
+ const delegationId = safeIdentifier(input.delegationId);
294
+ if (delegationId)
295
+ values['armoriq.delegation.id'] = delegationId;
296
+ if (input.category === 'delegation' && isOperationOutcome(input.delegationOutcome))
297
+ values['armoriq.delegation.outcome'] = input.delegationOutcome;
298
+ if (input.category === 'approval' && isOperationOutcome(input.approvalOutcome))
299
+ values['armoriq.approval.outcome'] = input.approvalOutcome;
300
+ setSafeNumber(values, 'armoriq.approval.wait_ms', input.approvalWaitMs);
301
+ return this.beginChild(safeOperationName(input.name, input.category), values, timing);
302
+ }
303
+ async recordOperation(input) {
304
+ const handle = await this.beginOperationWithTiming(input, 'post_hoc');
305
+ await this.endOperation(handle, input);
306
+ }
307
+ async endOperation(handle, input) {
308
+ const values = {
309
+ 'armoriq.operation.category': input.category,
310
+ ...terminalCommandAttributes(input.category, input),
311
+ };
312
+ const delegationId = safeIdentifier(input.delegationId);
313
+ if (delegationId)
314
+ values['armoriq.delegation.id'] = delegationId;
315
+ if (input.category === 'delegation' && isOperationOutcome(input.delegationOutcome))
316
+ values['armoriq.delegation.outcome'] = input.delegationOutcome;
317
+ if (input.category === 'approval' && isOperationOutcome(input.approvalOutcome))
318
+ values['armoriq.approval.outcome'] = input.approvalOutcome;
319
+ setSafeNumber(values, 'armoriq.approval.wait_ms', input.approvalWaitMs);
320
+ await this.endSpan(handle.span, operationStatus(input), values);
321
+ }
322
+ async endTool(handle, result) {
323
+ const durationMs = safeNonNegative(result.durationMs);
324
+ const terminalFacts = terminalCommandAttributes(commandCategoryForOperationName(handle.name), result);
325
+ if (handle.span && Object.prototype.hasOwnProperty.call(result, 'result')) {
326
+ this.queueToolContent(handle.span, { toolResult: result.result });
327
+ }
328
+ const pendingContent = this.takeToolContent(handle.span);
329
+ await this.endSpan(handle.span, result.outcome === 'success' ? 'ok' : result.outcome, {
330
+ ...(durationMs === undefined ? {} : { 'armoriq.duration_ms': durationMs }),
331
+ ...terminalFacts,
332
+ 'armoriq.tool.outcome': result.outcome,
333
+ 'armoriq.intent_plan_item_status': result.outcome === 'success'
334
+ ? 'succeeded'
335
+ : result.outcome === 'cancelled' || result.outcome === 'disconnected'
336
+ ? 'skipped'
337
+ : 'failed',
338
+ ...pendingContent,
339
+ ...(errorType(result.error) ? { 'error.type': errorType(result.error) } : {}),
340
+ ...(safeErrorSummary(result.error) ? { 'armoriq.observability.error.summary': safeErrorSummary(result.error) } : {}),
341
+ }, true);
342
+ }
343
+ async beginModel(model, content = {}) {
344
+ // Matches the Python SDK's span name (armoriq_sdk/observability/otel_semantics.py's
345
+ // SPAN_MODEL) and this span's own 'gen_ai.operation.name' attribute below —
346
+ // 'invoke' was never a defined OTel GenAI operation name.
347
+ const system = modelSystem(model);
348
+ return this.beginChild('gen_ai.chat', {
349
+ 'gen_ai.operation.name': 'chat',
350
+ 'gen_ai.request.model': model,
351
+ ...(system ? { 'gen_ai.system': system } : {}),
352
+ 'gen_ai.input.messages': content.input,
353
+ });
354
+ }
355
+ async endModel(handle, values, error, terminalStatus, content = {}) {
356
+ const hasCost = Object.prototype.hasOwnProperty.call(values, 'gen_ai.usage.cost_usd');
357
+ const hasCostProvenance = Object.prototype.hasOwnProperty.call(values, 'armoriq.cost.provenance');
358
+ await this.endSpan(handle.span, terminalStatus ?? (error ? 'error' : 'ok'), {
359
+ ...values,
360
+ ...(hasCost && !hasCostProvenance ? { 'armoriq.cost.provenance': 'provider_reported' } : {}),
361
+ ...(Object.prototype.hasOwnProperty.call(content, 'output') ? { 'gen_ai.output.messages': content.output } : {}),
362
+ ...(errorType(error) ? { 'error.type': errorType(error) } : {}),
363
+ ...(safeErrorSummary(error) ? { 'armoriq.observability.error.summary': safeErrorSummary(error) } : {}),
364
+ });
365
+ }
366
+ /** End only the current plan and drain it; the request root remains usable. */
367
+ async flush(status = 'ok') {
368
+ if (this.closed || this.closing)
369
+ return;
370
+ await this.endSpan(this.plan, status);
371
+ this.plan = null;
372
+ this.planId = null;
373
+ await this.runtime.forceFlush();
374
+ }
375
+ async close(status = 'ok', taskOutcome, content = {}) {
376
+ if (this.closePromise)
377
+ return this.closePromise;
378
+ this.closePromise = (async () => {
379
+ if (this.closed)
380
+ return;
381
+ this.closing = true;
382
+ await this.endSpan(this.plan, status);
383
+ this.plan = null;
384
+ this.planId = null;
385
+ await this.endSpan(this.root, status, {
386
+ 'gen_ai.task.outcome': taskOutcome ?? TASK_OUTCOME[status],
387
+ // Always include the field. The projector renders a structured
388
+ // adapter-unavailable state instead of an invented empty response.
389
+ 'gen_ai.output.messages': content.output,
390
+ });
391
+ this.root = null;
392
+ await this.runtime.close();
393
+ this.closed = true;
394
+ })().finally(() => {
395
+ this.pendingToolContent.clear();
396
+ }).catch(() => undefined);
397
+ return this.closePromise;
398
+ }
399
+ async beginChild(name, values, timingProvenance = 'measured') {
400
+ const root = (await this.beginRoot()).span;
401
+ const snapshot = await this.currentSnapshot();
402
+ const tracer = snapshot ? this.runtime.tracer(snapshot) : null;
403
+ if (!root || !tracer || !snapshot || this.closed || this.closing)
404
+ return { span: null, name };
405
+ try {
406
+ const parent = this.plan ?? root;
407
+ const attrs = this.runtime.safeAttributes(snapshot, {
408
+ 'armoriq.timing.provenance': timingProvenance,
409
+ ...values,
410
+ }, this.attributeContext());
411
+ const span = tracer.startSpan(name, { attributes: attrs }, api_1.trace.setSpan(api_1.ROOT_CONTEXT, parent));
412
+ return { span, name };
413
+ }
414
+ catch {
415
+ return { span: null, name };
416
+ }
417
+ }
418
+ planItemAttributes(input) {
419
+ const itemOrdinal = canonicalPlanStepOrdinal(input.itemOrdinal);
420
+ if (itemOrdinal === undefined || !this.planId)
421
+ return {};
422
+ return {
423
+ 'armoriq.intent_plan_id': this.planId,
424
+ 'armoriq.intent_plan_item_id': `plan-step-${itemOrdinal}`,
425
+ 'armoriq.intent_plan_item_ordinal': itemOrdinal,
426
+ };
427
+ }
428
+ stableOperationCallId(category, toolName, planId, itemOrdinal) {
429
+ const seed = [this.options.sessionId, planId ?? '', itemOrdinal ?? '', category, toolName].join('\u0000');
430
+ return `op-${(0, crypto_1.createHash)('sha256').update(seed).digest('hex').slice(0, 32)}`;
431
+ }
432
+ planItemOrdinalAttributes(itemOrdinal) {
433
+ const canonicalOrdinal = canonicalPlanStepOrdinal(itemOrdinal);
434
+ return canonicalOrdinal === undefined ? {} : { 'armoriq.intent_plan_item_ordinal': canonicalOrdinal };
435
+ }
436
+ async endSpan(span, status, values = {}, finalTool = false) {
437
+ if (!span)
438
+ return;
439
+ try {
440
+ const attrs = finalTool
441
+ ? await this.finalToolAttributes(values)
442
+ : await this.attributes(values);
443
+ span.setAttributes(attrs);
444
+ span.setStatus({ code: asStatus(status), message: status === 'ok' ? undefined : status });
445
+ span.end();
446
+ }
447
+ catch {
448
+ // Native telemetry stays fail-open.
449
+ }
450
+ }
451
+ async currentSnapshot() {
452
+ if (this.closed)
453
+ return null;
454
+ try {
455
+ this.snapshot = await this.runtime.policySnapshot();
456
+ return this.snapshot.captureMode === 'off' ? null : this.snapshot;
457
+ }
458
+ catch {
459
+ return null;
460
+ }
461
+ }
462
+ queueToolContent(span, content) {
463
+ try {
464
+ const buffered = (0, content_capture_1.bufferTelemetryContent)(content);
465
+ const previous = this.pendingToolContent.get(span);
466
+ if (!previous && this.pendingToolContent.size >= MAX_PENDING_TOOL_SPANS) {
467
+ const oldest = this.pendingToolContent.keys().next().value;
468
+ if (oldest)
469
+ this.pendingToolContent.delete(oldest);
470
+ }
471
+ this.pendingToolContent.set(span, Object.freeze({
472
+ content: Object.freeze({ ...(previous?.content ?? {}), ...buffered.content }),
473
+ redacted: Boolean(previous?.redacted || buffered.redacted),
474
+ truncated: Boolean(previous?.truncated || buffered.truncated),
475
+ }));
476
+ }
477
+ catch {
478
+ // Never retain the original value when defensive sanitization fails.
479
+ }
480
+ }
481
+ takeToolContent(span) {
482
+ if (!span)
483
+ return {};
484
+ const pending = this.pendingToolContent.get(span);
485
+ this.pendingToolContent.delete(span);
486
+ if (!pending)
487
+ return {};
488
+ const values = {};
489
+ if (Object.prototype.hasOwnProperty.call(pending.content, 'toolArguments')) {
490
+ values['gen_ai.tool.call.arguments'] = pending.content.toolArguments;
491
+ }
492
+ if (Object.prototype.hasOwnProperty.call(pending.content, 'toolResult')) {
493
+ values['gen_ai.tool.call.result'] = pending.content.toolResult;
494
+ }
495
+ if (pending.redacted)
496
+ values['armoriq.observability.redaction.applied'] = true;
497
+ if (pending.truncated)
498
+ values['armoriq.observability.content.truncated'] = true;
499
+ return values;
500
+ }
501
+ async finalToolAttributes(values) {
502
+ try {
503
+ this.snapshot = await this.runtime.policySnapshot({ awaitFresh: true });
504
+ return this.runtime.finalToolAttributes(this.snapshot, values, this.attributeContext());
505
+ }
506
+ catch {
507
+ return {};
508
+ }
509
+ }
510
+ attributeContext() {
511
+ return { traceName: this.runtime.externalTraceName(this.options.traceName), sessionId: this.options.sessionId, userId: this.options.userId };
512
+ }
513
+ }
514
+ exports.OtelSession = OtelSession;
515
+ const SAFE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,127}$/;
516
+ const MAX_PENDING_TOOL_SPANS = 256;
517
+ const SAFE_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
518
+ const SAFE_EXECUTABLE = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
519
+ const OPERATION_CATEGORIES = new Set(['tool', 'mcp', 'bash', 'cli', 'command', 'approval', 'delegation']);
520
+ const OPERATION_OUTCOMES = new Set(['pending', 'approved', 'denied', 'expired', 'cancelled', 'failed']);
521
+ const OPERATION_NAMES = new Set([
522
+ 'tool.execute', 'armoriq.tool', 'mcp.execute', 'bash.execute', 'cli.execute', 'command.execute',
523
+ 'approval.wait', 'delegation.wait', 'delegation.complete',
524
+ ]);
525
+ function safeIdentifier(value) {
526
+ return typeof value === 'string' && (SAFE_IDENTIFIER.test(value) || SAFE_UUID.test(value)) ? value : undefined;
527
+ }
528
+ function safeExecutable(value) {
529
+ return typeof value === 'string' && SAFE_EXECUTABLE.test(value) ? value : undefined;
530
+ }
531
+ function isOperationCategory(value) {
532
+ return typeof value === 'string' && OPERATION_CATEGORIES.has(value);
533
+ }
534
+ function isOperationOutcome(value) {
535
+ return typeof value === 'string' && OPERATION_OUTCOMES.has(value);
536
+ }
537
+ function isToolType(value) {
538
+ return value === 'function' || value === 'mcp' || value === 'bash' || value === 'cli' || value === 'command';
539
+ }
540
+ function safeNonNegative(value) {
541
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? Math.floor(value) : undefined;
542
+ }
543
+ function setSafeNumber(values, key, value) {
544
+ const number = safeNonNegative(value);
545
+ if (number !== undefined)
546
+ values[key] = number;
547
+ }
548
+ function setSafeCounter(values, key, value) {
549
+ if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0)
550
+ values[key] = value;
551
+ }
552
+ function setSafeBoolean(values, key, value) {
553
+ if (typeof value === 'boolean')
554
+ values[key] = value;
555
+ }
556
+ function terminalCommandAttributes(category, facts) {
557
+ const values = {};
558
+ if (category !== 'bash' && category !== 'cli' && category !== 'command')
559
+ return values;
560
+ setSafeCounter(values, 'armoriq.command.exit_code', facts.exitCode);
561
+ setSafeCounter(values, 'armoriq.execution.retry_count', facts.retryCount);
562
+ setSafeCounter(values, 'armoriq.output.stdout_bytes', facts.stdoutBytes);
563
+ setSafeCounter(values, 'armoriq.output.stderr_bytes', facts.stderrBytes);
564
+ setSafeBoolean(values, 'armoriq.execution.timeout', facts.timeout);
565
+ setSafeBoolean(values, 'armoriq.execution.killed', facts.killed);
566
+ setSafeBoolean(values, 'armoriq.output.truncated', facts.truncated);
567
+ return values;
568
+ }
569
+ function commandCategoryForOperationName(value) {
570
+ if (value === 'bash.execute')
571
+ return 'bash';
572
+ if (value === 'cli.execute')
573
+ return 'cli';
574
+ return value === 'command.execute' ? 'command' : undefined;
575
+ }
576
+ function defaultToolType(category) {
577
+ if (category === 'tool')
578
+ return 'function';
579
+ return category === 'mcp' || category === 'bash' || category === 'cli' || category === 'command' ? category : undefined;
580
+ }
581
+ function defaultOperationName(category) {
582
+ return category === 'approval' || category === 'delegation' ? `${category}.wait` : `${category}.execute`;
583
+ }
584
+ function safeOperationName(value, category) {
585
+ return typeof value === 'string' && OPERATION_NAMES.has(value)
586
+ ? value
587
+ : defaultOperationName(category);
588
+ }
589
+ function operationStatus(input) {
590
+ const outcome = input.category === 'approval'
591
+ ? input.approvalOutcome
592
+ : input.category === 'delegation'
593
+ ? input.delegationOutcome
594
+ : undefined;
595
+ if (outcome === 'failed')
596
+ return 'error';
597
+ if (outcome === 'denied')
598
+ return 'denied';
599
+ if (outcome === 'cancelled')
600
+ return 'cancelled';
601
+ if (outcome === 'expired')
602
+ return 'timeout';
603
+ if (input.category === 'bash' || input.category === 'cli' || input.category === 'command') {
604
+ if (input.timeout === true)
605
+ return 'timeout';
606
+ if (input.killed === true)
607
+ return 'cancelled';
608
+ if (typeof input.exitCode === 'number' && Number.isSafeInteger(input.exitCode) && input.exitCode > 0)
609
+ return 'error';
610
+ }
611
+ if (input.operationOutcome === 'failed')
612
+ return 'error';
613
+ if (input.operationOutcome === 'cancelled')
614
+ return 'cancelled';
615
+ if (input.operationOutcome === 'timeout')
616
+ return 'timeout';
617
+ if (input.operationOutcome === 'disconnected')
618
+ return 'disconnected';
619
+ return 'ok';
620
+ }
621
+ //# sourceMappingURL=otel-session.js.map