@armoriq/sdk-dev 0.6.10 → 0.8.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.
- package/README.md +168 -1
- package/dist/_version.d.ts +1 -1
- package/dist/_version.d.ts.map +1 -1
- package/dist/_version.js +1 -1
- package/dist/_version.js.map +1 -1
- package/dist/cli/commands/auth.d.ts +12 -0
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +522 -49
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/client.d.ts +8 -15
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +20 -18
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +0 -17
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -19
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -14
- package/dist/index.js.map +1 -1
- package/dist/integrations/google_adk.d.ts +155 -7
- package/dist/integrations/google_adk.d.ts.map +1 -1
- package/dist/integrations/google_adk.js +727 -46
- package/dist/integrations/google_adk.js.map +1 -1
- package/dist/integrations/langchain.d.ts +48 -2
- package/dist/integrations/langchain.d.ts.map +1 -1
- package/dist/integrations/langchain.js +528 -33
- package/dist/integrations/langchain.js.map +1 -1
- package/dist/integrations/strands.d.ts +65 -1
- package/dist/integrations/strands.d.ts.map +1 -1
- package/dist/integrations/strands.js +456 -36
- package/dist/integrations/strands.js.map +1 -1
- package/dist/models.d.ts +2 -2
- package/dist/models.d.ts.map +1 -1
- package/dist/observability/content-capture.d.ts +103 -0
- package/dist/observability/content-capture.d.ts.map +1 -0
- package/dist/observability/content-capture.js +423 -0
- package/dist/observability/content-capture.js.map +1 -0
- package/dist/observability/index.d.ts +6 -7
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +18 -27
- package/dist/observability/index.js.map +1 -1
- package/dist/observability/otel-config.d.ts +47 -0
- package/dist/observability/otel-config.d.ts.map +1 -0
- package/dist/observability/otel-config.js +268 -0
- package/dist/observability/otel-config.js.map +1 -0
- package/dist/observability/otel-export-ceiling.d.ts +96 -0
- package/dist/observability/otel-export-ceiling.d.ts.map +1 -0
- package/dist/observability/otel-export-ceiling.js +271 -0
- package/dist/observability/otel-export-ceiling.js.map +1 -0
- package/dist/observability/otel-runtime.d.ts +103 -0
- package/dist/observability/otel-runtime.d.ts.map +1 -0
- package/dist/observability/otel-runtime.js +680 -0
- package/dist/observability/otel-runtime.js.map +1 -0
- package/dist/observability/otel-session.d.ts +168 -0
- package/dist/observability/otel-session.d.ts.map +1 -0
- package/dist/observability/otel-session.js +630 -0
- package/dist/observability/otel-session.js.map +1 -0
- package/dist/observability/otel-shutdown.d.ts +17 -0
- package/dist/observability/otel-shutdown.d.ts.map +1 -0
- package/dist/observability/otel-shutdown.js +54 -0
- package/dist/observability/otel-shutdown.js.map +1 -0
- package/dist/observability/policy-lease.d.ts +22 -0
- package/dist/observability/policy-lease.d.ts.map +1 -0
- package/dist/observability/policy-lease.js +102 -0
- package/dist/observability/policy-lease.js.map +1 -0
- package/dist/plan_builder.d.ts +5 -4
- package/dist/plan_builder.d.ts.map +1 -1
- package/dist/plan_builder.js +14 -15
- package/dist/plan_builder.js.map +1 -1
- package/dist/session.d.ts +61 -93
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +388 -804
- package/dist/session.js.map +1 -1
- package/dist/token_usage.d.ts +11 -18
- package/dist/token_usage.d.ts.map +1 -1
- package/dist/token_usage.js +29 -94
- package/dist/token_usage.js.map +1 -1
- package/dist/tool_name.d.ts +18 -0
- package/dist/tool_name.d.ts.map +1 -0
- package/dist/tool_name.js +29 -0
- package/dist/tool_name.js.map +1 -0
- package/dist/tool_push.d.ts +28 -0
- package/dist/tool_push.d.ts.map +1 -0
- package/dist/tool_push.js +151 -0
- package/dist/tool_push.js.map +1 -0
- package/dist/tool_registry.d.ts +100 -0
- package/dist/tool_registry.d.ts.map +1 -0
- package/dist/tool_registry.js +440 -0
- package/dist/tool_registry.js.map +1 -0
- package/dist/tool_schema.d.ts +22 -0
- package/dist/tool_schema.d.ts.map +1 -0
- package/dist/tool_schema.js +163 -0
- package/dist/tool_schema.js.map +1 -0
- package/package.json +12 -7
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** SDK-owned OpenTelemetry runtime. It never registers a global provider. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ArmorIQTelemetryRuntime = void 0;
|
|
5
|
+
const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
|
|
6
|
+
const sdk_trace_node_1 = require("@opentelemetry/sdk-trace-node");
|
|
7
|
+
const resources_1 = require("@opentelemetry/resources");
|
|
8
|
+
const otlp_exporter_base_1 = require("@opentelemetry/otlp-exporter-base");
|
|
9
|
+
const node_http_1 = require("@opentelemetry/otlp-exporter-base/node-http");
|
|
10
|
+
const otlp_transformer_1 = require("@opentelemetry/otlp-transformer");
|
|
11
|
+
const otel_config_1 = require("./otel-config");
|
|
12
|
+
const policy_lease_1 = require("./policy-lease");
|
|
13
|
+
const content_capture_1 = require("./content-capture");
|
|
14
|
+
const otel_shutdown_1 = require("./otel-shutdown");
|
|
15
|
+
const otel_export_ceiling_1 = require("./otel-export-ceiling");
|
|
16
|
+
const SENSITIVE_KEY = /(?:api[_-]?key|auth(?:orization)?|bearer|cookie|credential|password|passphrase|secret|private[_-]?key|ssn|card(?:_number)?)/i;
|
|
17
|
+
const SECRET_TOKEN_KEY = /(?:^|[_.-])token(?:$|[_.-])/i;
|
|
18
|
+
const USAGE_TOKEN_KEY = /^gen_ai\.usage\.(?:input|output|cache_read|cache_write)_tokens$/;
|
|
19
|
+
const EXTERNAL_SAFE_NUMBER_ATTRIBUTES = new Set([
|
|
20
|
+
'armoriq.intent_plan_item_count',
|
|
21
|
+
'armoriq.intent_plan_item_ordinal',
|
|
22
|
+
'armoriq.duration_ms',
|
|
23
|
+
'armoriq.command.exit_code',
|
|
24
|
+
'armoriq.execution.retry_count',
|
|
25
|
+
'armoriq.execution.timeout',
|
|
26
|
+
'armoriq.output.stdout_bytes',
|
|
27
|
+
'armoriq.output.stderr_bytes',
|
|
28
|
+
'armoriq.approval.wait_ms',
|
|
29
|
+
'gen_ai.usage.cost_usd',
|
|
30
|
+
'armoriq.timing.provenance',
|
|
31
|
+
'armoriq.cost.provenance',
|
|
32
|
+
]);
|
|
33
|
+
// Finish reasons are low-cardinality metadata. Keep at most 16 values and
|
|
34
|
+
// inspect no more than 64 candidates so one adapter value cannot become a
|
|
35
|
+
// large telemetry payload or an unbounded sanitization loop.
|
|
36
|
+
const MAX_FINISH_REASONS = 16;
|
|
37
|
+
const MAX_FINISH_REASON_CANDIDATES = 64;
|
|
38
|
+
const SENSITIVE_VALUE = /(?:\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b|\b(?:ak|sk|pk|rk|ghp|github_pat)_[A-Za-z0-9_-]{12,}\b|-----BEGIN [A-Z0-9 ]+-----)/i;
|
|
39
|
+
const EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
|
|
40
|
+
const CONTENT_KEY = /^(?:armoriq\.(?:content\.|observability\.error\.summary$)|gen_ai\.(?:prompt|completion|input\.messages|output\.messages|tool\.(?:args|arguments|result|call\.(?:arguments|result))))$/;
|
|
41
|
+
const EXTERNAL_METADATA_ATTRIBUTES = new Set([
|
|
42
|
+
'gen_ai.request.model',
|
|
43
|
+
'gen_ai.response.model',
|
|
44
|
+
'gen_ai.system',
|
|
45
|
+
'gen_ai.operation.name',
|
|
46
|
+
'gen_ai.agent.name',
|
|
47
|
+
'gen_ai.response.finish_reasons',
|
|
48
|
+
'gen_ai.tool.name',
|
|
49
|
+
'gen_ai.tool.call.id',
|
|
50
|
+
'gen_ai.usage.input_tokens',
|
|
51
|
+
'gen_ai.usage.output_tokens',
|
|
52
|
+
'gen_ai.usage.cache_read_tokens',
|
|
53
|
+
'gen_ai.usage.cache_write_tokens',
|
|
54
|
+
'gen_ai.usage.cost_usd',
|
|
55
|
+
'armoriq.timing.provenance',
|
|
56
|
+
'armoriq.cost.provenance',
|
|
57
|
+
'gen_ai.task.outcome',
|
|
58
|
+
'error.type',
|
|
59
|
+
'armoriq.intent_plan_item_count',
|
|
60
|
+
'armoriq.intent_plan_item_ordinal',
|
|
61
|
+
'armoriq.duration_ms',
|
|
62
|
+
'armoriq.tool.name',
|
|
63
|
+
'armoriq.tool.type',
|
|
64
|
+
'armoriq.tool.call_id',
|
|
65
|
+
'armoriq.tool.outcome',
|
|
66
|
+
'armoriq.operation.category',
|
|
67
|
+
'armoriq.mcp.server',
|
|
68
|
+
'armoriq.intent_plan_id',
|
|
69
|
+
'armoriq.intent_plan_item_id',
|
|
70
|
+
'process.executable.name',
|
|
71
|
+
'armoriq.command.exit_code',
|
|
72
|
+
'armoriq.execution.retry_count',
|
|
73
|
+
'armoriq.execution.timeout',
|
|
74
|
+
'armoriq.execution.killed',
|
|
75
|
+
'armoriq.output.stdout_bytes',
|
|
76
|
+
'armoriq.output.stderr_bytes',
|
|
77
|
+
'armoriq.output.truncated',
|
|
78
|
+
'armoriq.delegation.id',
|
|
79
|
+
'armoriq.delegation.outcome',
|
|
80
|
+
'armoriq.approval.outcome',
|
|
81
|
+
'armoriq.approval.wait_ms',
|
|
82
|
+
'armoriq.observability.capture.mode',
|
|
83
|
+
'armoriq.observability.policy.revision',
|
|
84
|
+
'armoriq.observability.content.input.state',
|
|
85
|
+
'armoriq.observability.content.input.reason_code',
|
|
86
|
+
'armoriq.observability.content.output.state',
|
|
87
|
+
'armoriq.observability.content.output.reason_code',
|
|
88
|
+
'armoriq.observability.content.tool_arguments.state',
|
|
89
|
+
'armoriq.observability.content.tool_arguments.reason_code',
|
|
90
|
+
'armoriq.observability.content.tool_result.state',
|
|
91
|
+
'armoriq.observability.content.tool_result.reason_code',
|
|
92
|
+
'armoriq.observability.error.summary',
|
|
93
|
+
'armoriq.observability.error.summary.state',
|
|
94
|
+
'armoriq.observability.error.summary.reason_code',
|
|
95
|
+
'armoriq.observability.content.reason_code',
|
|
96
|
+
'armoriq.observability.redaction.applied',
|
|
97
|
+
'armoriq.observability.content.truncated',
|
|
98
|
+
]);
|
|
99
|
+
const GEN_AI_OPERATION_NAMES = new Set([
|
|
100
|
+
'chat',
|
|
101
|
+
'generate_content',
|
|
102
|
+
'text_completion',
|
|
103
|
+
'embeddings',
|
|
104
|
+
'create_agent',
|
|
105
|
+
'invoke_agent',
|
|
106
|
+
'invoke_workflow',
|
|
107
|
+
'plan',
|
|
108
|
+
'execute_tool',
|
|
109
|
+
]);
|
|
110
|
+
const EXTERNAL_POLICY_METADATA_ATTRIBUTES = new Set([
|
|
111
|
+
'armoriq.policy.name', 'armoriq.policy.decision', 'armoriq.policy.source',
|
|
112
|
+
'armoriq.policy.reason_code', 'armoriq.policy.default_action',
|
|
113
|
+
]);
|
|
114
|
+
const EXTERNAL_POLICY_ENHANCED_ATTRIBUTES = new Set(['armoriq.policy.version']);
|
|
115
|
+
const EXTERNAL_POLICY_DEBUG_ATTRIBUTES = new Set(['armoriq.policy.id', 'armoriq.policy.rule_id']);
|
|
116
|
+
const EXTERNAL_CONTENT_ATTRIBUTES = new Set([
|
|
117
|
+
'gen_ai.input.messages', 'gen_ai.output.messages',
|
|
118
|
+
'gen_ai.tool.call.arguments', 'gen_ai.tool.call.result',
|
|
119
|
+
]);
|
|
120
|
+
const CLOSE_DEADLINE_MS = 1_500;
|
|
121
|
+
const FIRST_EXTERNAL_AUTHORITY_WAIT_MS = 1_400;
|
|
122
|
+
function bounded(promise, timeoutMillis = CLOSE_DEADLINE_MS) {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const timer = setTimeout(() => resolve(undefined), timeoutMillis);
|
|
125
|
+
promise.then((value) => {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
resolve(value);
|
|
128
|
+
}, (error) => {
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
reject(error);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function cleanString(value, length = 256) {
|
|
135
|
+
if (SENSITIVE_VALUE.test(value))
|
|
136
|
+
return '[REDACTED:CREDENTIAL]';
|
|
137
|
+
if (EMAIL.test(value))
|
|
138
|
+
return '[REDACTED:EMAIL]';
|
|
139
|
+
return value.slice(0, length);
|
|
140
|
+
}
|
|
141
|
+
function safeValue(key, value, external) {
|
|
142
|
+
if (SENSITIVE_KEY.test(key) || (SECRET_TOKEN_KEY.test(key) && !USAGE_TOKEN_KEY.test(key))) {
|
|
143
|
+
return '[REDACTED:SENSITIVE_KEY]';
|
|
144
|
+
}
|
|
145
|
+
if (key === 'gen_ai.response.finish_reasons') {
|
|
146
|
+
if (!Array.isArray(value))
|
|
147
|
+
return undefined;
|
|
148
|
+
const reasons = [];
|
|
149
|
+
for (const reason of value.slice(0, MAX_FINISH_REASON_CANDIDATES)) {
|
|
150
|
+
if (typeof reason !== 'string')
|
|
151
|
+
continue;
|
|
152
|
+
const sanitized = external ? (0, otel_config_1.sanitizeExternalTelemetryValue)(reason, 256) : cleanString(reason);
|
|
153
|
+
if (sanitized !== undefined)
|
|
154
|
+
reasons.push(sanitized);
|
|
155
|
+
if (reasons.length === MAX_FINISH_REASONS)
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
return reasons;
|
|
159
|
+
}
|
|
160
|
+
if (typeof value === 'string') {
|
|
161
|
+
if (external && key === 'armoriq.intent_plan_id' && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value))
|
|
162
|
+
return value;
|
|
163
|
+
if (external)
|
|
164
|
+
return (0, otel_config_1.sanitizeExternalTelemetryValue)(value, CONTENT_KEY.test(key) ? 8 * 1024 : 256);
|
|
165
|
+
return cleanString(value, CONTENT_KEY.test(key) ? 4096 : 256);
|
|
166
|
+
}
|
|
167
|
+
if (typeof value === 'boolean')
|
|
168
|
+
return value;
|
|
169
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
170
|
+
// External numeric attributes are limited to semantic token counters.
|
|
171
|
+
// Other numeric values may be customer identifiers with no useful key hint.
|
|
172
|
+
if (external &&
|
|
173
|
+
!USAGE_TOKEN_KEY.test(key) &&
|
|
174
|
+
!EXTERNAL_SAFE_NUMBER_ATTRIBUTES.has(key))
|
|
175
|
+
return undefined;
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
function validExternalId(value) {
|
|
181
|
+
return /^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,127}$/.test(value) && (0, otel_config_1.sanitizeExternalTelemetryValue)(value, 128) !== undefined;
|
|
182
|
+
}
|
|
183
|
+
function hasOwn(value, key) {
|
|
184
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Manages one private exporter, or borrows a caller-owned provider. A runtime
|
|
188
|
+
* is request/session scoped and `close()` is idempotent and fail-open.
|
|
189
|
+
*/
|
|
190
|
+
class ArmorIQTelemetryRuntime {
|
|
191
|
+
config;
|
|
192
|
+
backendEndpoint;
|
|
193
|
+
apiKey;
|
|
194
|
+
sdkVersion;
|
|
195
|
+
leaseFetcher;
|
|
196
|
+
lease = null;
|
|
197
|
+
refreshing = null;
|
|
198
|
+
firstExternalAuthorityAwaited = false;
|
|
199
|
+
externalAuthorityUnavailable = false;
|
|
200
|
+
nextRefreshAt = 0;
|
|
201
|
+
provider = null;
|
|
202
|
+
policyLeaseAbort = null;
|
|
203
|
+
ownedAgents = new Set();
|
|
204
|
+
closed = false;
|
|
205
|
+
constructor(options) {
|
|
206
|
+
this.config = (0, otel_config_1.resolveOtelConfig)(options.options);
|
|
207
|
+
this.backendEndpoint = options.backendEndpoint.replace(/\/$/, '');
|
|
208
|
+
this.apiKey = options.apiKey;
|
|
209
|
+
this.sdkVersion = options.sdkVersion;
|
|
210
|
+
this.leaseFetcher = options.leaseFetcher;
|
|
211
|
+
if (this.config.enabled && this.config.exporter !== 'provider') {
|
|
212
|
+
(0, otel_shutdown_1.registerOwnedTelemetryRuntime)(this);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async policySnapshot(options = {}) {
|
|
216
|
+
if (!this.config.enabled || this.closed)
|
|
217
|
+
return Object.freeze({ ...(0, policy_lease_1.fallbackPolicyLease)(), captureMode: 'off' });
|
|
218
|
+
if (this.externalAuthorityUnavailable && this.config.exporter === 'external') {
|
|
219
|
+
return Object.freeze({ ...(0, policy_lease_1.fallbackPolicyLease)(), captureMode: 'off' });
|
|
220
|
+
}
|
|
221
|
+
if (!options.awaitFresh && this.lease?.authoritative && (0, policy_lease_1.isLeaseCurrent)(this.lease))
|
|
222
|
+
return this.applyCeiling(this.lease);
|
|
223
|
+
const refresh = this.refreshInBackground(options.awaitFresh === true);
|
|
224
|
+
const mustAwaitExternalAuthority = this.config.exporter === 'external' && !this.firstExternalAuthorityAwaited;
|
|
225
|
+
if (mustAwaitExternalAuthority)
|
|
226
|
+
this.firstExternalAuthorityAwaited = true;
|
|
227
|
+
if ((options.awaitFresh || mustAwaitExternalAuthority) && refresh) {
|
|
228
|
+
const fresh = await bounded(refresh, mustAwaitExternalAuthority ? FIRST_EXTERNAL_AUTHORITY_WAIT_MS : CLOSE_DEADLINE_MS);
|
|
229
|
+
if (fresh) {
|
|
230
|
+
this.lease = fresh;
|
|
231
|
+
if (mustAwaitExternalAuthority && !fresh.authoritative) {
|
|
232
|
+
this.externalAuthorityUnavailable = true;
|
|
233
|
+
return Object.freeze({ ...fresh, captureMode: 'off' });
|
|
234
|
+
}
|
|
235
|
+
return this.applyCeiling(this.lease);
|
|
236
|
+
}
|
|
237
|
+
if (mustAwaitExternalAuthority) {
|
|
238
|
+
this.externalAuthorityUnavailable = true;
|
|
239
|
+
this.policyLeaseAbort?.abort();
|
|
240
|
+
}
|
|
241
|
+
return this.applyCeiling((0, policy_lease_1.fallbackPolicyLease)());
|
|
242
|
+
}
|
|
243
|
+
return this.applyCeiling(this.lease && (0, policy_lease_1.isLeaseCurrent)(this.lease) ? this.lease : (0, policy_lease_1.fallbackPolicyLease)());
|
|
244
|
+
}
|
|
245
|
+
/** Start at most one control-plane request; request callers never await it. */
|
|
246
|
+
refreshInBackground(force = false) {
|
|
247
|
+
if (this.refreshing)
|
|
248
|
+
return this.refreshing;
|
|
249
|
+
if (!force && Date.now() < this.nextRefreshAt)
|
|
250
|
+
return null;
|
|
251
|
+
this.nextRefreshAt = Date.now() + 1_000;
|
|
252
|
+
this.refreshing = this.fetchPolicyLease()
|
|
253
|
+
.then((lease) => {
|
|
254
|
+
this.lease = lease;
|
|
255
|
+
return lease;
|
|
256
|
+
})
|
|
257
|
+
.finally(() => {
|
|
258
|
+
this.refreshing = null;
|
|
259
|
+
});
|
|
260
|
+
return this.refreshing;
|
|
261
|
+
}
|
|
262
|
+
applyCeiling(lease) {
|
|
263
|
+
let captureMode = (0, otel_config_1.lowerCaptureMode)(lease.captureMode, this.config.captureMode);
|
|
264
|
+
// No destination — including the first-party ArmorIQ exporter — may
|
|
265
|
+
// create or export a span until current organization authority permits
|
|
266
|
+
// it. `fallbackPolicyLease()` is a guess, not an answer: creating a span
|
|
267
|
+
// under it and only finding out afterward that the organization's real
|
|
268
|
+
// (in-flight) answer is `off` would export telemetry the org disabled,
|
|
269
|
+
// with no way to retroactively un-create it. Fail closed instead;
|
|
270
|
+
// `policySnapshot()`'s background refresh means this self-corrects on
|
|
271
|
+
// the next call once a real answer arrives, without ever blocking here.
|
|
272
|
+
if (!lease.authoritative) {
|
|
273
|
+
captureMode = 'off';
|
|
274
|
+
}
|
|
275
|
+
const debugExpired = captureMode === 'debug' && (!lease.debugExpiresAt || lease.debugExpiresAt.getTime() <= Date.now());
|
|
276
|
+
if (debugExpired)
|
|
277
|
+
captureMode = 'enhanced';
|
|
278
|
+
if (this.config.exporter === 'provider') {
|
|
279
|
+
// Provider mode hands spans to a TracerProvider the caller owns and
|
|
280
|
+
// configured. The export-time ceiling wrappers below are attached only
|
|
281
|
+
// to the pipeline this SDK builds for itself, so they never see spans
|
|
282
|
+
// once they are queued inside the caller's own BatchSpanProcessor --
|
|
283
|
+
// mutating a caller-owned provider to attach them would break the
|
|
284
|
+
// ownership contract. Rather than promise a revocation guarantee this
|
|
285
|
+
// SDK cannot enforce on a pipeline it does not own, provider mode is
|
|
286
|
+
// capped at `metadata` here, at the one place that decides capture
|
|
287
|
+
// mode for every destination: governed content is then never captured
|
|
288
|
+
// in the first place, so none can ever be sitting in that queue when a
|
|
289
|
+
// lease flips.
|
|
290
|
+
captureMode = (0, otel_config_1.lowerCaptureMode)(captureMode, 'metadata');
|
|
291
|
+
}
|
|
292
|
+
const externalContentAllowed = lease.authoritative
|
|
293
|
+
&& (captureMode === 'enhanced' || captureMode === 'debug')
|
|
294
|
+
&& lease.contentCaptureAllowed
|
|
295
|
+
&& lease.externalContentCaptureAllowed
|
|
296
|
+
&& !debugExpired;
|
|
297
|
+
const contentReasonCode = captureMode === 'off'
|
|
298
|
+
? 'capture_mode_off'
|
|
299
|
+
: captureMode === 'metadata'
|
|
300
|
+
? 'capture_mode_metadata'
|
|
301
|
+
: debugExpired ? 'policy_expired'
|
|
302
|
+
: !lease.contentCaptureAllowed ? 'organization_content_disabled'
|
|
303
|
+
: !lease.externalContentCaptureAllowed ? 'external_content_disabled'
|
|
304
|
+
: lease.authoritative ? 'captured' : 'policy_unavailable';
|
|
305
|
+
return Object.freeze({ ...lease, captureMode, externalContentAllowed, contentReasonCode });
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* The ceiling-applied lease this runtime already knows about, computed
|
|
309
|
+
* synchronously from whatever is cached. Never performs network I/O and
|
|
310
|
+
* never blocks: a batching span processor flushes on its own timer, from
|
|
311
|
+
* whatever thread that is, and must get an immediate answer. When nothing
|
|
312
|
+
* current is cached, this fails closed to the same `authoritative: false`
|
|
313
|
+
* fallback `policySnapshot()` uses, which `applyCeiling` maps to `off`.
|
|
314
|
+
*
|
|
315
|
+
* This is the true export-time gate's policy read: `CeilingReapplyingSpanExporter`
|
|
316
|
+
* and `CeilingReapplyingSpanProcessor` (otel-export-ceiling.ts) call it
|
|
317
|
+
* immediately before a buffered span is allowed to leave the process, so a
|
|
318
|
+
* span queued under a since-revoked lease is re-governed against whatever
|
|
319
|
+
* is authoritative right now -- not whatever was authoritative when the
|
|
320
|
+
* span was created or ended.
|
|
321
|
+
*/
|
|
322
|
+
currentCeilingSnapshot() {
|
|
323
|
+
if (!this.config.enabled) {
|
|
324
|
+
return this.applyCeiling(Object.freeze({ ...(0, policy_lease_1.fallbackPolicyLease)(), captureMode: 'off' }));
|
|
325
|
+
}
|
|
326
|
+
if (this.lease && (0, policy_lease_1.isLeaseCurrent)(this.lease))
|
|
327
|
+
return this.applyCeiling(this.lease);
|
|
328
|
+
if (this.closed) {
|
|
329
|
+
return this.applyCeiling(Object.freeze({ ...(0, policy_lease_1.fallbackPolicyLease)(), captureMode: 'off' }));
|
|
330
|
+
}
|
|
331
|
+
// Cache is missing or stale. Kick a background refresh for the *next*
|
|
332
|
+
// export to see, but this call itself must never block on it.
|
|
333
|
+
this.refreshInBackground(false);
|
|
334
|
+
return this.applyCeiling((0, policy_lease_1.fallbackPolicyLease)());
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Re-govern a span's already-baked attributes under a (possibly new)
|
|
338
|
+
* policy snapshot. Shares `contentAuthority` -- the exact function that
|
|
339
|
+
* decides content authority at span-creation time -- with
|
|
340
|
+
* `safeAttributesWithAuthority`, so there is exactly one place that
|
|
341
|
+
* defines "is content allowed right now." When it is, every content-bearing
|
|
342
|
+
* key present is re-bounded against the current mode's limits (via
|
|
343
|
+
* `reboundCapturedJson`): a session whose lease never changes and whose
|
|
344
|
+
* mode never changes must export exactly what it would have exported
|
|
345
|
+
* before this method existed, but content captured under debug's looser
|
|
346
|
+
* limits cannot keep those limits once the ceiling has downgraded to
|
|
347
|
+
* enhanced. When capture is not allowed at all, every key in
|
|
348
|
+
* `CONTENT_ATTRIBUTE_KEYS` present on the span is replaced with the same
|
|
349
|
+
* withheld-content markers `projectTelemetryContent` produces at creation
|
|
350
|
+
* time for a disallowed capture mode.
|
|
351
|
+
*/
|
|
352
|
+
reapplyContentCeiling(snapshot, attributes) {
|
|
353
|
+
const external = this.config.exporter === 'external' || this.config.exporter === 'provider';
|
|
354
|
+
const authority = this.contentAuthority(snapshot, external);
|
|
355
|
+
const canCapture = authority.contentAllowed && (authority.mode === 'enhanced' || authority.mode === 'debug');
|
|
356
|
+
if (canCapture) {
|
|
357
|
+
const next = { ...attributes };
|
|
358
|
+
// A public span attribute can be forged by any framework adapter or
|
|
359
|
+
// caller, so the two capture-diagnostic flags are never trusted here.
|
|
360
|
+
// Drop whatever the span carries and re-derive the facts from actually
|
|
361
|
+
// re-binding the captured content under this ceiling. The trusted facts
|
|
362
|
+
// recorded by a prior governance pass are restored separately from the
|
|
363
|
+
// private per-span record (see reapplyCeiling in otel-export-ceiling.ts).
|
|
364
|
+
delete next[content_capture_1.ATTR_REDACTION_APPLIED];
|
|
365
|
+
delete next[content_capture_1.ATTR_CONTENT_TRUNCATED];
|
|
366
|
+
let truncated = false;
|
|
367
|
+
let redacted = false;
|
|
368
|
+
for (const key of [
|
|
369
|
+
'gen_ai.input.messages', 'gen_ai.output.messages',
|
|
370
|
+
'gen_ai.tool.call.arguments', 'gen_ai.tool.call.result',
|
|
371
|
+
'armoriq.content.input', 'armoriq.content.output',
|
|
372
|
+
'armoriq.observability.error.summary',
|
|
373
|
+
]) {
|
|
374
|
+
const value = next[key];
|
|
375
|
+
if (typeof value !== 'string')
|
|
376
|
+
continue;
|
|
377
|
+
const rebound = (0, content_capture_1.reboundCapturedJson)(value, authority.mode);
|
|
378
|
+
next[key] = rebound.value;
|
|
379
|
+
truncated ||= rebound.truncated;
|
|
380
|
+
redacted ||= rebound.redacted;
|
|
381
|
+
}
|
|
382
|
+
if (redacted)
|
|
383
|
+
next[content_capture_1.ATTR_REDACTION_APPLIED] = true;
|
|
384
|
+
if (truncated)
|
|
385
|
+
next[content_capture_1.ATTR_CONTENT_TRUNCATED] = true;
|
|
386
|
+
return next;
|
|
387
|
+
}
|
|
388
|
+
const hasInput = hasOwn(attributes, 'gen_ai.input.messages') || hasOwn(attributes, 'armoriq.content.input');
|
|
389
|
+
const hasOutput = hasOwn(attributes, 'gen_ai.output.messages') || hasOwn(attributes, 'armoriq.content.output');
|
|
390
|
+
const hasToolArguments = hasOwn(attributes, 'gen_ai.tool.call.arguments');
|
|
391
|
+
const hasToolResult = hasOwn(attributes, 'gen_ai.tool.call.result');
|
|
392
|
+
// A span whose only governed field is an error summary (e.g. a
|
|
393
|
+
// zero-argument tool/policy check that later errors) still needs
|
|
394
|
+
// purging: `armoriq.observability.error.summary` is real, only
|
|
395
|
+
// lightly-redacted error-message text, and its presence is itself proof
|
|
396
|
+
// the span was captured under an enhanced/debug lease.
|
|
397
|
+
const hasErrorSummary = hasOwn(attributes, 'armoriq.observability.error.summary');
|
|
398
|
+
if (!hasInput && !hasOutput && !hasToolArguments && !hasToolResult && !hasErrorSummary) {
|
|
399
|
+
// Nothing governed is present on this span to purge (it was already
|
|
400
|
+
// metadata-only, or off, when it was created).
|
|
401
|
+
return { ...attributes };
|
|
402
|
+
}
|
|
403
|
+
const presence = {};
|
|
404
|
+
if (hasInput)
|
|
405
|
+
presence.input = undefined;
|
|
406
|
+
if (hasOutput)
|
|
407
|
+
presence.output = undefined;
|
|
408
|
+
if (hasToolArguments)
|
|
409
|
+
presence.toolArguments = undefined;
|
|
410
|
+
if (hasToolResult)
|
|
411
|
+
presence.toolResult = undefined;
|
|
412
|
+
if (hasErrorSummary)
|
|
413
|
+
presence.errorSummary = undefined;
|
|
414
|
+
const projected = (0, content_capture_1.projectTelemetryContent)(presence, authority);
|
|
415
|
+
const next = { ...attributes };
|
|
416
|
+
for (const key of content_capture_1.CONTENT_ATTRIBUTE_KEYS)
|
|
417
|
+
delete next[key];
|
|
418
|
+
Object.assign(next, projected.attributes);
|
|
419
|
+
next['armoriq.observability.capture.mode'] = snapshot.captureMode;
|
|
420
|
+
next['armoriq.observability.policy.revision'] = snapshot.revision;
|
|
421
|
+
return next;
|
|
422
|
+
}
|
|
423
|
+
async fetchPolicyLease() {
|
|
424
|
+
const controller = new AbortController();
|
|
425
|
+
this.policyLeaseAbort = controller;
|
|
426
|
+
const timeout = setTimeout(() => controller.abort(), 5_000);
|
|
427
|
+
try {
|
|
428
|
+
if (this.leaseFetcher)
|
|
429
|
+
return await this.leaseFetcher(controller.signal);
|
|
430
|
+
const response = await fetch(`${this.backendEndpoint}/observability/policy/lease`, {
|
|
431
|
+
headers: { 'X-API-Key': this.apiKey },
|
|
432
|
+
signal: controller.signal,
|
|
433
|
+
});
|
|
434
|
+
if (!response.ok)
|
|
435
|
+
throw new Error(`policy lease rejected (${response.status})`);
|
|
436
|
+
return (0, policy_lease_1.parsePolicyLease)(await response.json());
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
// Never alter the application result because telemetry is unavailable.
|
|
440
|
+
return (0, policy_lease_1.fallbackPolicyLease)();
|
|
441
|
+
}
|
|
442
|
+
finally {
|
|
443
|
+
clearTimeout(timeout);
|
|
444
|
+
if (this.policyLeaseAbort === controller)
|
|
445
|
+
this.policyLeaseAbort = null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
tracer(snapshot) {
|
|
449
|
+
if (this.closed || !this.config.enabled || snapshot.captureMode === 'off' || !(0, policy_lease_1.isLeaseCurrent)(snapshot))
|
|
450
|
+
return null;
|
|
451
|
+
try {
|
|
452
|
+
if (this.config.exporter === 'provider') {
|
|
453
|
+
return this.config.tracerProvider?.getTracer('armoriq-sdk', this.sdkVersion) ?? null;
|
|
454
|
+
}
|
|
455
|
+
return this.privateProvider()?.getTracer('armoriq-sdk', this.sdkVersion) ?? null;
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
privateProvider() {
|
|
462
|
+
if (this.provider || this.closed || !this.config.enabled)
|
|
463
|
+
return this.provider;
|
|
464
|
+
const isExternal = this.config.exporter === 'external';
|
|
465
|
+
const url = isExternal ? this.config.endpoint : `${this.backendEndpoint}/v1/traces`;
|
|
466
|
+
if (!url)
|
|
467
|
+
return null;
|
|
468
|
+
try {
|
|
469
|
+
const exporter = this.createExporter(url, isExternal ? this.config.headers : { 'x-api-key': this.apiKey });
|
|
470
|
+
this.provider = new sdk_trace_node_1.NodeTracerProvider({
|
|
471
|
+
resource: (0, resources_1.resourceFromAttributes)({
|
|
472
|
+
'service.name': this.config.serviceName,
|
|
473
|
+
'service.version': this.sdkVersion,
|
|
474
|
+
...this.config.resourceAttributes,
|
|
475
|
+
}),
|
|
476
|
+
// A span queued under a permissive lease may still be sitting in
|
|
477
|
+
// BatchSpanProcessor's in-memory buffer when the lease becomes
|
|
478
|
+
// stricter (or is revoked). Wrap both the exporter -- the true
|
|
479
|
+
// export-time gate: it runs right before spans leave the process,
|
|
480
|
+
// however long they were queued -- and the processor -- closing the
|
|
481
|
+
// narrower window between an attribute being set and `span.end()`
|
|
482
|
+
// being called -- so whatever policy is authoritative right now,
|
|
483
|
+
// not whatever was authoritative at creation time, governs what
|
|
484
|
+
// actually ships.
|
|
485
|
+
spanProcessors: [
|
|
486
|
+
new otel_export_ceiling_1.CeilingReapplyingSpanProcessor(new sdk_trace_base_1.BatchSpanProcessor(new otel_export_ceiling_1.CeilingReapplyingSpanExporter(exporter, this), { exportTimeoutMillis: Math.min(this.config.timeoutMillis, CLOSE_DEADLINE_MS) }), this),
|
|
487
|
+
],
|
|
488
|
+
});
|
|
489
|
+
return this.provider;
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Construct the OTLP delegate from explicit values. The convenience
|
|
497
|
+
* exporter reads process.env while merging defaults, which could leak a
|
|
498
|
+
* caller's external headers into an ArmorIQ-only exporter.
|
|
499
|
+
*/
|
|
500
|
+
createExporter(url, headers) {
|
|
501
|
+
const agentFactory = async (protocol) => {
|
|
502
|
+
const { Agent } = protocol === 'http:' ? await import('node:http') : await import('node:https');
|
|
503
|
+
const agent = new Agent({ keepAlive: false });
|
|
504
|
+
this.ownedAgents.add(agent);
|
|
505
|
+
return agent;
|
|
506
|
+
};
|
|
507
|
+
return new otlp_exporter_base_1.OTLPExporterBase((0, node_http_1.createOtlpHttpExportDelegate)({
|
|
508
|
+
url,
|
|
509
|
+
// The wire serializer is protobuf. Content type is SDK-owned and must
|
|
510
|
+
// be set after arbitrary caller headers.
|
|
511
|
+
headers: () => ({ ...headers, 'content-type': 'application/x-protobuf' }),
|
|
512
|
+
timeoutMillis: Math.min(this.config.timeoutMillis, CLOSE_DEADLINE_MS),
|
|
513
|
+
concurrencyLimit: 1,
|
|
514
|
+
compression: 'none',
|
|
515
|
+
agentFactory,
|
|
516
|
+
}, otlp_transformer_1.ProtobufTraceSerializer));
|
|
517
|
+
}
|
|
518
|
+
safeAttributes(snapshot, attributes, context) {
|
|
519
|
+
return this.safeAttributesWithAuthority(snapshot, attributes, context, false);
|
|
520
|
+
}
|
|
521
|
+
/** Final tool boundary: retain safe lifecycle facts and content-state markers even when capture is now off. */
|
|
522
|
+
finalToolAttributes(snapshot, attributes, context) {
|
|
523
|
+
return this.safeAttributesWithAuthority(snapshot, attributes, context, true);
|
|
524
|
+
}
|
|
525
|
+
safeAttributesWithAuthority(snapshot, attributes, context, allowOffMarkers) {
|
|
526
|
+
if (this.closed || !this.config.enabled || !(0, policy_lease_1.isLeaseCurrent)(snapshot))
|
|
527
|
+
return {};
|
|
528
|
+
if (snapshot.captureMode === 'off' && !allowOffMarkers)
|
|
529
|
+
return {};
|
|
530
|
+
const external = this.config.exporter === 'external' || this.config.exporter === 'provider';
|
|
531
|
+
const result = {};
|
|
532
|
+
const content = this.extractContent(attributes);
|
|
533
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
534
|
+
if (CONTENT_KEY.test(key))
|
|
535
|
+
continue;
|
|
536
|
+
if (key === 'gen_ai.operation.name' &&
|
|
537
|
+
(typeof value !== 'string' || !GEN_AI_OPERATION_NAMES.has(value))) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
if (external && !this.externalAttributeAllowed(key, snapshot.captureMode))
|
|
541
|
+
continue;
|
|
542
|
+
const sanitized = safeValue(key, value, external);
|
|
543
|
+
if (sanitized !== undefined)
|
|
544
|
+
result[key] = sanitized;
|
|
545
|
+
}
|
|
546
|
+
const authority = this.contentAuthority(snapshot, external);
|
|
547
|
+
const projected = (0, content_capture_1.projectTelemetryContent)(content, authority);
|
|
548
|
+
for (const [key, value] of Object.entries(projected.attributes)) {
|
|
549
|
+
if (!external || EXTERNAL_METADATA_ATTRIBUTES.has(key) || EXTERNAL_CONTENT_ATTRIBUTES.has(key)) {
|
|
550
|
+
const sanitized = safeValue(key, value, external);
|
|
551
|
+
if (sanitized !== undefined)
|
|
552
|
+
result[key] = sanitized;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// The durable ArmorIQ ingest contract predates GenAI semantic attributes.
|
|
556
|
+
// Preserve the standard attributes above, and mirror only its canonical
|
|
557
|
+
// content keys for internal normalized reads. These aliases are never
|
|
558
|
+
// allowed through a third-party exporter.
|
|
559
|
+
if (!external) {
|
|
560
|
+
const input = projected.attributes['gen_ai.input.messages'];
|
|
561
|
+
const output = projected.attributes['gen_ai.output.messages'];
|
|
562
|
+
if (typeof input === 'string')
|
|
563
|
+
result['armoriq.content.input'] = input;
|
|
564
|
+
if (typeof output === 'string')
|
|
565
|
+
result['armoriq.content.output'] = output;
|
|
566
|
+
}
|
|
567
|
+
result['armoriq.observability.capture.mode'] = snapshot.captureMode;
|
|
568
|
+
result['armoriq.observability.policy.revision'] = snapshot.revision;
|
|
569
|
+
if (external) {
|
|
570
|
+
const sessionId = (0, otel_config_1.sanitizeExternalTelemetryValue)(context.sessionId, 128);
|
|
571
|
+
if (sessionId)
|
|
572
|
+
result['session.id'] = sessionId;
|
|
573
|
+
if (context.userId && validExternalId(context.userId))
|
|
574
|
+
result['user.id'] = context.userId;
|
|
575
|
+
}
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
externalAttributeAllowed(key, mode) {
|
|
579
|
+
if (EXTERNAL_METADATA_ATTRIBUTES.has(key) || EXTERNAL_POLICY_METADATA_ATTRIBUTES.has(key))
|
|
580
|
+
return true;
|
|
581
|
+
if ((mode === 'enhanced' || mode === 'debug') && EXTERNAL_POLICY_ENHANCED_ATTRIBUTES.has(key))
|
|
582
|
+
return true;
|
|
583
|
+
return mode === 'debug' && EXTERNAL_POLICY_DEBUG_ATTRIBUTES.has(key);
|
|
584
|
+
}
|
|
585
|
+
/** Shared by span-creation attribute assembly and export-time re-governance. */
|
|
586
|
+
contentAuthority(snapshot, external) {
|
|
587
|
+
const authorityCurrent = snapshot.authoritative && (0, policy_lease_1.isLeaseCurrent)(snapshot);
|
|
588
|
+
const contentAllowed = authorityCurrent
|
|
589
|
+
&& snapshot.contentCaptureAllowed
|
|
590
|
+
&& (snapshot.captureMode === 'enhanced' || snapshot.captureMode === 'debug')
|
|
591
|
+
&& (!external || snapshot.externalContentAllowed);
|
|
592
|
+
let reasonCode = snapshot.contentReasonCode;
|
|
593
|
+
if (!authorityCurrent)
|
|
594
|
+
reasonCode = 'policy_unavailable';
|
|
595
|
+
else if (snapshot.captureMode === 'off')
|
|
596
|
+
reasonCode = 'capture_mode_off';
|
|
597
|
+
else if (snapshot.captureMode === 'metadata')
|
|
598
|
+
reasonCode = 'capture_mode_metadata';
|
|
599
|
+
else if (snapshot.contentReasonCode === 'policy_expired')
|
|
600
|
+
reasonCode = 'policy_expired';
|
|
601
|
+
else if (!snapshot.contentCaptureAllowed)
|
|
602
|
+
reasonCode = 'organization_content_disabled';
|
|
603
|
+
else if (external && !snapshot.externalContentAllowed)
|
|
604
|
+
reasonCode = 'external_content_disabled';
|
|
605
|
+
return { mode: snapshot.captureMode, external, contentAllowed, reasonCode };
|
|
606
|
+
}
|
|
607
|
+
extractContent(attributes) {
|
|
608
|
+
const result = {};
|
|
609
|
+
if (Object.prototype.hasOwnProperty.call(attributes, 'gen_ai.input.messages'))
|
|
610
|
+
result.input = attributes['gen_ai.input.messages'];
|
|
611
|
+
else if (Object.prototype.hasOwnProperty.call(attributes, 'gen_ai.prompt'))
|
|
612
|
+
result.input = attributes['gen_ai.prompt'];
|
|
613
|
+
else if (Object.prototype.hasOwnProperty.call(attributes, 'armoriq.content.input'))
|
|
614
|
+
result.input = attributes['armoriq.content.input'];
|
|
615
|
+
if (Object.prototype.hasOwnProperty.call(attributes, 'gen_ai.output.messages'))
|
|
616
|
+
result.output = attributes['gen_ai.output.messages'];
|
|
617
|
+
else if (Object.prototype.hasOwnProperty.call(attributes, 'gen_ai.completion'))
|
|
618
|
+
result.output = attributes['gen_ai.completion'];
|
|
619
|
+
else if (Object.prototype.hasOwnProperty.call(attributes, 'armoriq.content.output'))
|
|
620
|
+
result.output = attributes['armoriq.content.output'];
|
|
621
|
+
if (Object.prototype.hasOwnProperty.call(attributes, 'gen_ai.tool.call.arguments'))
|
|
622
|
+
result.toolArguments = attributes['gen_ai.tool.call.arguments'];
|
|
623
|
+
else if (Object.prototype.hasOwnProperty.call(attributes, 'gen_ai.tool.arguments'))
|
|
624
|
+
result.toolArguments = attributes['gen_ai.tool.arguments'];
|
|
625
|
+
if (Object.prototype.hasOwnProperty.call(attributes, 'gen_ai.tool.call.result'))
|
|
626
|
+
result.toolResult = attributes['gen_ai.tool.call.result'];
|
|
627
|
+
else if (Object.prototype.hasOwnProperty.call(attributes, 'gen_ai.tool.result'))
|
|
628
|
+
result.toolResult = attributes['gen_ai.tool.result'];
|
|
629
|
+
if (Object.prototype.hasOwnProperty.call(attributes, 'armoriq.observability.error.summary'))
|
|
630
|
+
result.errorSummary = attributes['armoriq.observability.error.summary'];
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
/** Stable, externally safe root span names prevent identifier leakage. */
|
|
634
|
+
externalTraceName(value) {
|
|
635
|
+
if (this.config.exporter !== 'external' && this.config.exporter !== 'provider')
|
|
636
|
+
return value;
|
|
637
|
+
return (0, otel_config_1.sanitizeExternalTelemetryValue)(value, 128) ?? 'armoriq.agent.run';
|
|
638
|
+
}
|
|
639
|
+
async forceFlush() {
|
|
640
|
+
if (this.config.exporter === 'provider')
|
|
641
|
+
return;
|
|
642
|
+
try {
|
|
643
|
+
await bounded(this.provider?.forceFlush() ?? Promise.resolve());
|
|
644
|
+
}
|
|
645
|
+
catch {
|
|
646
|
+
// Export failure is intentionally fail-open.
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
async close() {
|
|
650
|
+
if (this.closed)
|
|
651
|
+
return;
|
|
652
|
+
this.closed = true;
|
|
653
|
+
(0, otel_shutdown_1.unregisterOwnedTelemetryRuntime)(this);
|
|
654
|
+
this.policyLeaseAbort?.abort();
|
|
655
|
+
this.policyLeaseAbort = null;
|
|
656
|
+
if (this.config.exporter === 'provider')
|
|
657
|
+
return;
|
|
658
|
+
const provider = this.provider;
|
|
659
|
+
this.provider = null;
|
|
660
|
+
try {
|
|
661
|
+
await bounded(provider?.shutdown() ?? Promise.resolve());
|
|
662
|
+
}
|
|
663
|
+
catch {
|
|
664
|
+
// Export failure is intentionally fail-open.
|
|
665
|
+
}
|
|
666
|
+
finally {
|
|
667
|
+
for (const agent of this.ownedAgents) {
|
|
668
|
+
try {
|
|
669
|
+
agent.destroy();
|
|
670
|
+
}
|
|
671
|
+
catch {
|
|
672
|
+
// Agents are SDK-owned and are best-effort only.
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
this.ownedAgents.clear();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
exports.ArmorIQTelemetryRuntime = ArmorIQTelemetryRuntime;
|
|
680
|
+
//# sourceMappingURL=otel-runtime.js.map
|