@telemetry-dev/bedrock 0.1.9 → 0.1.12

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 CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  First-class AWS Bedrock instrumentation for the telemetry.dev TypeScript SDK.
4
4
 
5
+ Docs: https://telemetry.dev/docs/integrations/bedrock
6
+
5
7
  This package instruments AWS SDK for JavaScript v3 clients for:
6
8
 
7
9
  - `@aws-sdk/client-bedrock-runtime`
@@ -29,15 +31,18 @@ pnpm add @telemetry-dev/sdk @telemetry-dev/bedrock @aws-sdk/client-bedrock-runti
29
31
  pnpm add @aws-sdk/client-bedrock-agent-runtime
30
32
  ```
31
33
 
34
+ Requires `@aws-sdk/client-bedrock-runtime` 3.600.0 or a later 3.x release (and the same range for the optional `@aws-sdk/client-bedrock-agent-runtime`), and `@telemetry-dev/sdk`, which needs Node.js 20.19.0 or later.
35
+
32
36
  ## Quickstart
33
37
 
38
+ `init()` reads your project API key from the `TELEMETRY_DEV_API_KEY` environment variable, so set it before you start the app.
39
+
34
40
  ```ts
35
41
  import { BedrockRuntimeClient, ConverseCommand } from "@aws-sdk/client-bedrock-runtime";
36
42
  import { init, shutdown } from "@telemetry-dev/sdk";
37
43
  import { wrapBedrock } from "@telemetry-dev/bedrock";
38
44
 
39
45
  init({
40
- apiKey: process.env.TELEMETRY_DEV_API_KEY,
41
46
  serviceName: "bedrock-app",
42
47
  environment: "production",
43
48
  });
@@ -98,9 +103,22 @@ instrumentBedrockAgents({ captureAgentTrace: false });
98
103
 
99
104
  ## Options
100
105
 
101
- | Option | Default | Applies to | Notes |
102
- | ------------------- | ------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
103
- | `captureAgentTrace` | `false` | Agent Runtime streams | Aggregates trace usage and counts by default. When enabled, also attaches raw trace events as `td.metadata.agent_trace` after SDK masking/truncation. |
106
+ | Option | Default | Applies to | Notes |
107
+ | ------------------- | ------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
108
+ | `captureAgentTrace` | `false` | Agent Runtime streams | Aggregates trace usage and counts by default. When enabled, also attaches trace events as `td.metadata.agent_trace`, filtered as described below. |
109
+
110
+ `td.metadata.agent_trace` is truncated but never passed to your `mask`, so each trace event is rebuilt from an explicit allowlist instead:
111
+
112
+ - Envelope fields: `agentId`, `agentAliasId`, `agentVersion`, `sessionId`, `collaboratorName`, `eventTime`, and the alias ARNs in `callerChain`.
113
+ - Content the agent sends to a model, tool, or guardrail (`modelInvocationInput`, `invocationInput`, `inputAssessments`) is kept as received when `captureInput` is on.
114
+ - Content that comes back (`modelInvocationOutput`, `rationale`, `observation`, `outputAssessments`, custom orchestration `event`) is kept as received when `captureOutput` is on.
115
+ - A failure reason (`failureTrace.failureReason`, and `td.metadata.failure_reason`) is free-form text, so it is kept only when both are on. `td.metadata.failure` always records that the invocation failed, with `td.metadata.failure_code` when AWS sends one.
116
+ - When a `mask` is set, all of this content is withheld.
117
+ - Withheld content and the trace structure around it keep only trace ids, step types, the foundation model, guardrail action, failure code, and the usage, timings, and `clientRequestId` from AWS's trace `metadata`.
118
+
119
+ Any other field, including unknown trace types and the AWS SDK's `$unknown` union member, is omitted even when both capture options are on; a content part that is kept is exported whole, including any fields inside it that are not listed here. Errors are not covered by this filtering: AWS exceptions, including errors raised inside the stream, still record the provider's error message on the span.
120
+
121
+ Output file names in `td.metadata.output_files` follow `captureOutput` the same way, and are withheld when a `mask` is set; file types are always kept.
104
122
 
105
123
  ## Signal coverage
106
124
 
@@ -138,8 +156,11 @@ InvokeModel captures provider-native JSON bodies verbatim. Non-JSON bodies are n
138
156
  - Instrumentation is fail-open. Normalization, span updates, and span endings are guarded so telemetry failures do not break caller code.
139
157
  - Requests are never modified. This is especially important for AWS SigV4 signing and for application-level request object reuse.
140
158
  - Streams are pull-through. The wrapper does not pre-buffer chunks and therefore preserves backpressure.
141
- - Streams end spans exactly once. Exhaustion records full output and finish reason; early `break`, `return()`, `close()`, abort, or stream errors record partial output.
159
+ - Streams end spans exactly once. Exhaustion records full output and finish reason; early `break`, `return()`, `close()`, abort, or stream errors record partial output and set `telemetry.dev.capture.truncated`.
160
+ - Stream reconstruction (output, Agent trace events, and Agent output-file metadata) retains up to 48 KiB and 1,000 items; events past the bound still pass through to the caller. The configured `maxAttributeLength` does not shrink this bound; the core applies it to the exported attribute after the mask runs.
161
+ - A stream cut at the bound, stopped early, or failed sets `telemetry.dev.capture.truncated`. With a mask configured, its incomplete reconstructed output is omitted because the mask cannot inspect the complete value. Agent trace events and output-file metadata share the bound, but dropping them only sets the attribute; Agent text is omitted only when a text chunk was dropped or the stream stopped early or failed. A normally exhausted Agent stream that returned control keeps its complete `returnControl` output even if earlier text or metadata passed the bound; an early stop or failure still omits the output when a mask is configured.
142
162
  - `AbortSignal` passed to `send(command, { abortSignal })` is forwarded untouched. Pre-response aborts end the span with the thrown abort error. Mid-stream aborts end the span with partial output and the stream error.
163
+ - `Rerank` input and results are bounded to 48 KiB and 1,000 items. A value over the bound sets `telemetry.dev.capture.truncated`. Without a mask, the bounded part is kept. With a mask configured, that value is omitted because the mask cannot inspect the complete value. Input is omitted before the span starts, so a custom `sampler` sees the bounded input when no mask is configured; with an older `@telemetry-dev/sdk` that cannot report whether a mask is configured, cut input is omitted as if one were.
143
164
  - Callback-style `send(command, callback)` and `send(command, options, callback)` calls are instrumented; callbacks receive the original data/error while the span is ended before callback invocation.
144
165
 
145
166
  ## Coverage gaps
@@ -156,3 +177,7 @@ The following calls pass through unless a supported operation above is used:
156
177
  - `AgenticRetrieveStream`
157
178
 
158
179
  Provider-native `InvokeModel` usage is best-effort. Anthropic Claude, Amazon Titan/Nova, Meta Llama, and Titan embeddings expose known token fields. Cohere and Mistral text models do not consistently include usage in the JSON body; spans still capture request/response bodies and finish reasons where present.
180
+
181
+ ## License
182
+
183
+ MIT. See [LICENSE](https://github.com/telemetry-dev/telemetry/blob/main/packages/bedrock/LICENSE).
package/dist/agents.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { r as createInstrumentedSend, t as AGENT_HANDLERS } from "./internal-handlers-Byxp14OZ.mjs";
1
+ import { r as createInstrumentedSend, t as AGENT_HANDLERS } from "./internal-handlers-CLP2wXDh.mjs";
2
2
  import { BedrockAgentRuntimeClient } from "@aws-sdk/client-bedrock-agent-runtime";
3
3
  //#region src/agents.ts
4
4
  const WRAPPED = /* @__PURE__ */ new WeakSet();
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as RUNTIME_HANDLERS, r as createInstrumentedSend } from "./internal-handlers-Byxp14OZ.mjs";
1
+ import { n as RUNTIME_HANDLERS, r as createInstrumentedSend } from "./internal-handlers-CLP2wXDh.mjs";
2
2
  import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime";
3
3
  //#region src/index.ts
4
4
  const WRAPPED = /* @__PURE__ */ new WeakSet();
@@ -1,4 +1,5 @@
1
- import { boundedCapture, captureEnabled, startSpan } from "@telemetry-dev/sdk";
1
+ import * as telemetrySdk from "@telemetry-dev/sdk";
2
+ import { boundedCapture, boundedCaptureDetails, captureEnabled, startSpan } from "@telemetry-dev/sdk";
2
3
  //#region src/internal.ts
3
4
  const PROVIDER = "amazon-bedrock";
4
5
  const STREAM_FINALIZER = typeof FinalizationRegistry === "function" ? new FinalizationRegistry((finish) => finish()) : void 0;
@@ -12,7 +13,7 @@ function createInstrumentedSend(originalSend, options, handlers) {
12
13
  const input = commandInput(rawCommand);
13
14
  const t0 = performance.now();
14
15
  const spanType = safe(() => handler.spanType(input)) ?? "span";
15
- const span = startSpan(safe(() => handler.spanName(input)) ?? name ?? "bedrock", {
16
+ const span = startRequestSpan(safe(() => handler.spanName(input)) ?? name ?? "bedrock", {
16
17
  type: spanType,
17
18
  ...safe(() => handler.requestFields(input, options)) ?? {}
18
19
  });
@@ -103,6 +104,33 @@ function withCacheInclusiveInput(fields) {
103
104
  }
104
105
  };
105
106
  }
107
+ function capturesWithMask(span) {
108
+ return span.capturePolicy?.mask ?? true;
109
+ }
110
+ function isCut(fields) {
111
+ return fields?.attributes?.["telemetry.dev.capture.truncated"] === true;
112
+ }
113
+ function captureMaskedBeforeStart() {
114
+ const read = telemetrySdk.captureMasked;
115
+ try {
116
+ return typeof read === "function" ? read() : true;
117
+ } catch {
118
+ return true;
119
+ }
120
+ }
121
+ function startRequestSpan(name, options) {
122
+ return startSpan(name, options.input !== void 0 && isCut(options) && captureMaskedBeforeStart() ? {
123
+ ...options,
124
+ input: void 0
125
+ } : options);
126
+ }
127
+ /** Ends a non-streamed span, withholding output cut to the retention bound from a mask. */
128
+ function endSpanWithBoundedOutput(span, fields) {
129
+ endSpan(span, fields.output !== void 0 && isCut(fields) && capturesWithMask(span) ? {
130
+ ...fields,
131
+ output: void 0
132
+ } : fields);
133
+ }
106
134
  function endSpan(span, fields) {
107
135
  try {
108
136
  span.end(withCacheInclusiveInput(fields));
@@ -209,6 +237,46 @@ function parseJson(value) {
209
237
  return;
210
238
  }
211
239
  }
240
+ const STREAM_CAPTURE_MAX_BYTES = 48 * 1024;
241
+ const STREAM_CAPTURE_MAX_ITEMS = 1e3;
242
+ const CAPTURE_TRUNCATED_ATTRIBUTE = "telemetry.dev.capture.truncated";
243
+ const LEGACY_CAPTURE_POLICY = {
244
+ output: true,
245
+ mask: true,
246
+ maxAttributeLength: 65536
247
+ };
248
+ /** Tracks the fixed byte and item bound before a stream state retains a value. */
249
+ var StreamRetention = class {
250
+ bytes = 0;
251
+ items = 0;
252
+ truncated = false;
253
+ accept(value) {
254
+ if (this.truncated) return false;
255
+ const measured = boundedCaptureDetails(value, {
256
+ maxBytes: STREAM_CAPTURE_MAX_BYTES - this.bytes,
257
+ maxItems: STREAM_CAPTURE_MAX_ITEMS - this.items
258
+ });
259
+ if (measured.truncated) {
260
+ this.truncated = true;
261
+ return false;
262
+ }
263
+ this.bytes += measured.bytes;
264
+ this.items += measured.items;
265
+ return true;
266
+ }
267
+ };
268
+ function finishStream(state, span, partial) {
269
+ const fields = state.finish(partial);
270
+ const interrupted = partial || fields.error !== void 0;
271
+ if (!interrupted && !state.retention.truncated) return fields;
272
+ fields.attributes = {
273
+ ...fields.attributes,
274
+ [CAPTURE_TRUNCATED_ATTRIBUTE]: true
275
+ };
276
+ const outputTruncated = state.outputTruncated?.() ?? state.retention.truncated;
277
+ if ((span.capturePolicy ?? LEGACY_CAPTURE_POLICY).mask && (interrupted || outputTruncated)) delete fields.output;
278
+ return fields;
279
+ }
212
280
  function wrapAsyncIterable(iterable, state, span, t0, baseFields, trackOutputChunks = true) {
213
281
  if (!iterable || !isAsyncIterable(iterable)) {
214
282
  endSpan(span, baseFields);
@@ -238,15 +306,15 @@ function wrapAsyncIterable(iterable, state, span, t0, baseFields, trackOutputChu
238
306
  }
239
307
  completed = true;
240
308
  } catch (error) {
241
- endOnce(mergeFields(state.finish(true), awsMetadataFields(metadataOf(error)), { error: asError(error) }));
309
+ endOnce(mergeFields(finishStream(state, span, true), awsMetadataFields(metadataOf(error)), { error: asError(error) }));
242
310
  throw error;
243
311
  } finally {
244
- if (!ended) endOnce(state.finish(!completed));
312
+ if (!ended) endOnce(finishStream(state, span, !completed));
245
313
  }
246
314
  };
247
315
  const wrapped = {};
248
316
  if (isRecord(iterable)) for (const key of Reflect.ownKeys(iterable)) wrapped[key] = iterable[key];
249
- const finishAbandoned = () => endOnce(safe(() => state.finish(true)));
317
+ const finishAbandoned = () => endOnce(safe(() => finishStream(state, span, true)));
250
318
  wrapped[Symbol.asyncIterator] = () => {
251
319
  const iterator = generator();
252
320
  STREAM_FINALIZER?.register(iterator, finishAbandoned, unregisterToken);
@@ -340,17 +408,27 @@ function inlineAgentRequestFields(input) {
340
408
  });
341
409
  }
342
410
  var AgentStreamState = class {
411
+ retention = new StreamRetention();
343
412
  text = "";
413
+ textRejected = false;
344
414
  usage = {};
345
415
  traceEventCount = 0;
416
+ failed = false;
417
+ failureCode;
346
418
  failureReason;
347
419
  guardrailAction;
348
420
  returnControl;
421
+ sawReturnControl = false;
422
+ returnControlRejected = false;
349
423
  traces = [];
350
424
  error;
351
425
  errorFields;
352
426
  citationCount = 0;
353
427
  outputFiles = [];
428
+ contentCapture = {
429
+ input: captureEnabled("input") && !captureMaskedBeforeStart(),
430
+ output: captureEnabled("output") && !captureMaskedBeforeStart()
431
+ };
354
432
  constructor(options) {
355
433
  this.options = options;
356
434
  }
@@ -365,30 +443,58 @@ var AgentStreamState = class {
365
443
  }
366
444
  const chunk = isRecord(raw.chunk) ? raw.chunk : void 0;
367
445
  const bytes = chunk?.bytes;
368
- if (bytes !== void 0) this.text += bytesToString(bytes) ?? "";
446
+ const piece = bytes === void 0 ? void 0 : bytesToString(bytes);
447
+ if (piece) if (this.retention.accept(piece)) this.text += piece;
448
+ else this.textRejected = true;
369
449
  const citations = arrayValue((isRecord(chunk?.attribution) ? chunk.attribution : void 0)?.citations);
370
450
  if (citations) this.citationCount += citations.length;
371
- const files = isRecord(raw.files) ? arrayValue(raw.files.files) : void 0;
372
- if (files) this.outputFiles.push(...files.map(sanitizeOutputFile));
451
+ const sanitizedFiles = (isRecord(raw.files) ? arrayValue(raw.files.files) : void 0)?.map((file) => sanitizeOutputFile(file, this.contentCapture.output));
452
+ if (sanitizedFiles && this.retention.accept(sanitizedFiles)) this.outputFiles.push(...sanitizedFiles);
373
453
  const returnControl = isRecord(raw.returnControl) ? raw.returnControl : void 0;
374
- if (returnControl) this.returnControl = returnControl;
454
+ if (returnControl) {
455
+ this.sawReturnControl = true;
456
+ if (new StreamRetention().accept(returnControl)) this.returnControl = returnControl;
457
+ else {
458
+ this.returnControl = void 0;
459
+ this.returnControlRejected = true;
460
+ this.retention.truncated = true;
461
+ }
462
+ }
375
463
  const traceEvent = isRecord(raw.trace) ? raw.trace : void 0;
376
464
  if (traceEvent) {
377
465
  this.traceEventCount += 1;
378
- if (this.options.captureAgentTrace) this.traces.push(traceEvent);
379
- collectTrace(traceEvent, (usage) => this.addUsage(usage));
380
- const failure = findKey(traceEvent, "failureTrace");
381
- if (isRecord(failure)) this.failureReason = stringValue(failure.failureReason) ?? this.failureReason;
382
- const guardrail = findKey(traceEvent, "guardrailTrace");
466
+ if (this.options.captureAgentTrace) {
467
+ const retained = projectTrace(traceEvent, TRACE_PART, this.contentCapture);
468
+ if (this.retention.accept(retained)) this.traces.push(retained);
469
+ }
470
+ const trace = isRecord(traceEvent.trace) ? traceEvent.trace : {};
471
+ for (const step of MODEL_INVOCATION_STEPS) {
472
+ const member = isRecord(trace[step]) ? trace[step] : void 0;
473
+ const output = isRecord(member?.modelInvocationOutput) ? member.modelInvocationOutput : {};
474
+ this.addUsage(isRecord(output.metadata) ? output.metadata.usage : void 0);
475
+ }
476
+ const failure = trace.failureTrace;
477
+ if (isRecord(failure)) {
478
+ this.failed = true;
479
+ this.failureCode = numberValue(failure.failureCode) ?? this.failureCode;
480
+ if (this.contentCapture.input && this.contentCapture.output) this.failureReason = stringValue(failure.failureReason) ?? this.failureReason;
481
+ }
482
+ const guardrail = trace.guardrailTrace;
383
483
  if (isRecord(guardrail)) this.guardrailAction = stringValue(guardrail.action) ?? this.guardrailAction;
384
484
  }
385
485
  }
486
+ outputTruncated() {
487
+ if (this.returnControl !== void 0) return false;
488
+ return this.textRejected || this.returnControlRejected;
489
+ }
386
490
  finish() {
387
491
  const metadata = omitUndefined({
388
492
  trace_event_count: this.traceEventCount || void 0,
493
+ failure: this.failed ? true : void 0,
494
+ failure_code: this.failureCode,
389
495
  failure_reason: this.failureReason,
390
496
  guardrail_action: this.guardrailAction,
391
- return_control: this.returnControl ? true : void 0,
497
+ return_control: this.sawReturnControl ? true : void 0,
392
498
  citation_count: this.citationCount || void 0,
393
499
  output_files: this.outputFiles.length > 0 ? this.outputFiles : void 0,
394
500
  agent_trace: this.options.captureAgentTrace && this.traces.length > 0 ? this.traces : void 0
@@ -418,11 +524,11 @@ var AgentStreamState = class {
418
524
  else if (inputTokens !== void 0 || outputTokens !== void 0) this.usage.totalTokens = (this.usage.inputTokens ?? 0) + (this.usage.outputTokens ?? 0);
419
525
  }
420
526
  };
421
- function sanitizeOutputFile(value) {
527
+ function sanitizeOutputFile(value, keepName) {
422
528
  const raw = value;
423
529
  if (!isRecord(raw)) return void 0;
424
530
  return omitUndefined({
425
- name: stringValue(raw.name),
531
+ name: keepName ? stringValue(raw.name) : void 0,
426
532
  type: stringValue(raw.type)
427
533
  });
428
534
  }
@@ -533,6 +639,7 @@ function ragResponseFields(output) {
533
639
  });
534
640
  }
535
641
  var RagStreamState = class {
642
+ retention = new StreamRetention();
536
643
  text = "";
537
644
  citationCount = 0;
538
645
  guardrailAction;
@@ -547,8 +654,8 @@ var RagStreamState = class {
547
654
  this.errorFields = streamError.fields;
548
655
  return;
549
656
  }
550
- const output = isRecord(raw.output) ? raw.output : void 0;
551
- this.text += stringValue(output?.text) ?? "";
657
+ const text = stringValue((isRecord(raw.output) ? raw.output : void 0)?.text);
658
+ if (text && this.retention.accept(text)) this.text += text;
552
659
  if (raw.citation) this.citationCount += 1;
553
660
  const guardrail = isRecord(raw.guardrail) ? raw.guardrail : void 0;
554
661
  this.guardrailAction = stringValue(guardrail?.action) ?? this.guardrailAction;
@@ -578,6 +685,7 @@ function flowRequestFields(input) {
578
685
  });
579
686
  }
580
687
  var FlowStreamState = class {
688
+ retention = new StreamRetention();
581
689
  outputs = [];
582
690
  completionReason;
583
691
  error;
@@ -592,9 +700,9 @@ var FlowStreamState = class {
592
700
  return;
593
701
  }
594
702
  const output = isRecord(raw.flowOutputEvent) ? raw.flowOutputEvent : void 0;
595
- if (output) this.outputs.push(output.content);
703
+ if (output && this.retention.accept(output.content)) this.outputs.push(output.content);
596
704
  const inputRequest = isRecord(raw.flowMultiTurnInputRequestEvent) ? raw.flowMultiTurnInputRequestEvent : void 0;
597
- if (inputRequest) this.outputs.push(inputRequest.content);
705
+ if (inputRequest && this.retention.accept(inputRequest.content)) this.outputs.push(inputRequest.content);
598
706
  const completion = isRecord(raw.flowCompletionEvent) ? raw.flowCompletionEvent : void 0;
599
707
  this.completionReason = stringValue(completion?.completionReason) ?? this.completionReason;
600
708
  }
@@ -612,22 +720,111 @@ function flowName(input) {
612
720
  function ragName(input) {
613
721
  return `retrieve_and_generate ${basenameArn(ragModel(input)) ?? "knowledge-base"}`;
614
722
  }
615
- function collectTrace(value, onUsage) {
616
- const raw = value;
617
- if (!isRecord(raw)) return;
618
- const output = isRecord(raw.modelInvocationOutput) ? raw.modelInvocationOutput : void 0;
619
- if (output) onUsage((isRecord(output.metadata) ? output.metadata : void 0)?.usage);
620
- for (const child of Object.values(raw)) if (isRecord(child)) collectTrace(child, onUsage);
621
- else if (Array.isArray(child)) child.forEach((item) => collectTrace(item, onUsage));
622
- }
623
- function findKey(value, key) {
624
- const raw = value;
625
- if (!isRecord(raw)) return void 0;
626
- if (raw[key] !== void 0) return raw[key];
627
- for (const child of Object.values(raw)) {
628
- const found = findKey(child, key);
629
- if (found !== void 0) return found;
723
+ /** Content kept as received when every listed capture is on; otherwise reduced to `withheld`. */
724
+ var TraceContent = class {
725
+ constructor(needs, withheld) {
726
+ this.needs = needs;
727
+ this.withheld = withheld;
728
+ }
729
+ };
730
+ const SCALAR = "scalar";
731
+ const TRACE_METADATA = {
732
+ usage: {
733
+ inputTokens: SCALAR,
734
+ outputTokens: SCALAR
735
+ },
736
+ startTime: SCALAR,
737
+ endTime: SCALAR,
738
+ totalTimeMs: SCALAR,
739
+ operationTotalTimeMs: SCALAR,
740
+ clientRequestId: SCALAR
741
+ };
742
+ const MODEL_INVOCATION_INPUT = new TraceContent(["input"], {
743
+ traceId: SCALAR,
744
+ type: SCALAR,
745
+ foundationModel: SCALAR
746
+ });
747
+ const INVOCATION_INPUT = new TraceContent(["input"], {
748
+ traceId: SCALAR,
749
+ invocationType: SCALAR
750
+ });
751
+ const MODEL_INVOCATION_OUTPUT = new TraceContent(["output"], {
752
+ traceId: SCALAR,
753
+ metadata: TRACE_METADATA
754
+ });
755
+ const OBSERVATION = new TraceContent(["output"], {
756
+ traceId: SCALAR,
757
+ type: SCALAR
758
+ });
759
+ const MODEL_INVOCATION_STEPS = [
760
+ "preProcessingTrace",
761
+ "orchestrationTrace",
762
+ "postProcessingTrace",
763
+ "routingClassifierTrace"
764
+ ];
765
+ const PROCESSING_TRACE = {
766
+ modelInvocationInput: MODEL_INVOCATION_INPUT,
767
+ modelInvocationOutput: MODEL_INVOCATION_OUTPUT
768
+ };
769
+ const TRACE_PART = {
770
+ agentId: SCALAR,
771
+ agentAliasId: SCALAR,
772
+ agentVersion: SCALAR,
773
+ sessionId: SCALAR,
774
+ collaboratorName: SCALAR,
775
+ eventTime: SCALAR,
776
+ callerChain: [{ agentAliasArn: SCALAR }],
777
+ trace: {
778
+ preProcessingTrace: PROCESSING_TRACE,
779
+ postProcessingTrace: PROCESSING_TRACE,
780
+ orchestrationTrace: {
781
+ modelInvocationInput: MODEL_INVOCATION_INPUT,
782
+ invocationInput: INVOCATION_INPUT,
783
+ modelInvocationOutput: MODEL_INVOCATION_OUTPUT,
784
+ rationale: new TraceContent(["output"], { traceId: SCALAR }),
785
+ observation: OBSERVATION
786
+ },
787
+ routingClassifierTrace: {
788
+ modelInvocationInput: MODEL_INVOCATION_INPUT,
789
+ invocationInput: INVOCATION_INPUT,
790
+ modelInvocationOutput: MODEL_INVOCATION_OUTPUT,
791
+ observation: OBSERVATION
792
+ },
793
+ customOrchestrationTrace: {
794
+ traceId: SCALAR,
795
+ event: new TraceContent(["output"])
796
+ },
797
+ guardrailTrace: {
798
+ traceId: SCALAR,
799
+ action: SCALAR,
800
+ metadata: TRACE_METADATA,
801
+ inputAssessments: new TraceContent(["input"]),
802
+ outputAssessments: new TraceContent(["output"])
803
+ },
804
+ failureTrace: {
805
+ traceId: SCALAR,
806
+ failureCode: SCALAR,
807
+ metadata: TRACE_METADATA,
808
+ failureReason: new TraceContent(["input", "output"])
809
+ }
810
+ }
811
+ };
812
+ function projectTrace(value, shape, capture) {
813
+ if (shape instanceof TraceContent) {
814
+ if (shape.needs.every((kind) => capture[kind])) return value ?? void 0;
815
+ return shape.withheld === void 0 ? void 0 : projectTrace(value, shape.withheld, capture);
816
+ }
817
+ if (shape === SCALAR) return [
818
+ "string",
819
+ "number",
820
+ "boolean"
821
+ ].includes(typeof value) || value instanceof Date ? value : void 0;
822
+ if (Array.isArray(shape)) {
823
+ if (!Array.isArray(value)) return void 0;
824
+ return value.map((item) => projectTrace(item, shape[0], capture)).filter((item) => item !== void 0);
630
825
  }
826
+ if (!isRecord(value)) return void 0;
827
+ return omitUndefined(Object.fromEntries(Object.entries(shape).map(([field, child]) => [field, projectTrace(value[field], child, capture)])));
631
828
  }
632
829
  //#endregion
633
830
  //#region src/converse.ts
@@ -801,6 +998,7 @@ function s3Uri(value) {
801
998
  return bucket && key ? `s3://${bucket}/${key}` : void 0;
802
999
  }
803
1000
  var ConverseStreamState = class {
1001
+ retention = new StreamRetention();
804
1002
  role = "assistant";
805
1003
  blocks = /* @__PURE__ */ new Map();
806
1004
  stopReason;
@@ -820,7 +1018,7 @@ var ConverseStreamState = class {
820
1018
  const messageStart = isRecord(event.messageStart) ? event.messageStart : void 0;
821
1019
  if (messageStart) this.role = stringValue(messageStart.role) ?? this.role;
822
1020
  const blockStart = isRecord(event.contentBlockStart) ? event.contentBlockStart : void 0;
823
- if (blockStart) {
1021
+ if (blockStart && this.retention.accept(blockStart.start ?? {})) {
824
1022
  const index = numberValue(blockStart.contentBlockIndex) ?? this.blocks.size;
825
1023
  const start = isRecord(blockStart.start) ? blockStart.start : void 0;
826
1024
  const toolUse = isRecord(start?.toolUse) ? start?.toolUse : void 0;
@@ -847,9 +1045,9 @@ var ConverseStreamState = class {
847
1045
  });
848
1046
  }
849
1047
  const deltaEvent = isRecord(event.contentBlockDelta) ? event.contentBlockDelta : void 0;
850
- if (deltaEvent) {
1048
+ const delta = isRecord(deltaEvent?.delta) ? deltaEvent.delta : {};
1049
+ if (deltaEvent && this.retention.accept(delta)) {
851
1050
  const index = numberValue(deltaEvent.contentBlockIndex) ?? 0;
852
- const delta = isRecord(deltaEvent.delta) ? deltaEvent.delta : {};
853
1051
  const block = this.blocks.get(index) ?? {
854
1052
  kind: "text",
855
1053
  text: ""
@@ -1058,12 +1256,7 @@ function nativeResponseFields(model, body, metadata) {
1058
1256
  return withHeaderUsage({
1059
1257
  output: body,
1060
1258
  finishReason: stringValue(body.stop_reason),
1061
- usage: omitUndefined({
1062
- inputTokens: numberValue(usage.input_tokens),
1063
- outputTokens: numberValue(usage.output_tokens),
1064
- cacheReadInputTokens: numberValue(usage.cache_read_input_tokens),
1065
- cacheCreationInputTokens: numberValue(usage.cache_creation_input_tokens)
1066
- })
1259
+ ...claudeUsageFields(usage)
1067
1260
  }, metadata);
1068
1261
  }
1069
1262
  if (lower.includes("meta.llama")) return omitUndefined({
@@ -1088,6 +1281,35 @@ function nativeResponseFields(model, body, metadata) {
1088
1281
  }, metadata);
1089
1282
  return withHeaderUsage({ output: body }, metadata);
1090
1283
  }
1284
+ const CLAUDE_USAGE_KEYS = {
1285
+ inputTokens: "input_tokens",
1286
+ outputTokens: "output_tokens",
1287
+ cacheReadInputTokens: "cache_read_input_tokens",
1288
+ cacheCreationInputTokens: "cache_creation_input_tokens"
1289
+ };
1290
+ function claudeUsageFields(record) {
1291
+ const usage = {};
1292
+ for (const [key, source] of Object.entries(CLAUDE_USAGE_KEYS)) {
1293
+ const value = numberValue(record[source]);
1294
+ if (value !== void 0) usage[key] = value;
1295
+ }
1296
+ const iterations = record.iterations;
1297
+ if (!Array.isArray(iterations)) return { usage };
1298
+ const first = Math.max(0, iterations.length - STREAM_CAPTURE_MAX_ITEMS);
1299
+ for (let index = first; index < iterations.length; index += 1) {
1300
+ const entry = iterations[index];
1301
+ if (!isRecord(entry) || entry.type !== "compaction") continue;
1302
+ for (const [key, source] of Object.entries(CLAUDE_USAGE_KEYS)) {
1303
+ const value = numberValue(entry[source]);
1304
+ const target = key;
1305
+ if (value !== void 0) usage[target] = (usage[target] ?? 0) + value;
1306
+ }
1307
+ }
1308
+ return {
1309
+ usage,
1310
+ ...first > 0 ? { attributes: { "telemetry.dev.capture.truncated": true } } : {}
1311
+ };
1312
+ }
1091
1313
  function withHeaderUsage(fields, metadata) {
1092
1314
  if (fields.usage && Object.keys(fields.usage).length > 0) return fields;
1093
1315
  return mergeFields(fields, headerUsageFields(metadata));
@@ -1109,9 +1331,13 @@ function headerNumber(value) {
1109
1331
  return numberValue(value);
1110
1332
  }
1111
1333
  var InvokeModelStreamState = class {
1334
+ retention = new StreamRetention();
1112
1335
  chunks = [];
1113
1336
  text = "";
1114
1337
  usage;
1338
+ finalUsage = {};
1339
+ startUsage = {};
1340
+ compaction = {};
1115
1341
  finishReason;
1116
1342
  error;
1117
1343
  errorFields;
@@ -1129,28 +1355,53 @@ var InvokeModelStreamState = class {
1129
1355
  const raw = chunk?.bytes;
1130
1356
  return (typeof raw === "string" || raw instanceof Uint8Array) && raw.length > 0;
1131
1357
  }
1132
- this.chunks.push(parsed);
1133
- this.text += textFromProviderChunk(parsed);
1358
+ if (this.retention.accept(parsed)) {
1359
+ this.chunks.push(parsed);
1360
+ this.text += textFromProviderChunk(parsed);
1361
+ }
1134
1362
  if (isRecord(parsed)) {
1135
1363
  const delta = isRecord(parsed.delta) ? parsed.delta : void 0;
1136
1364
  const message = isRecord(parsed.message) ? parsed.message : void 0;
1137
1365
  const messageUsage = isRecord(message?.usage) ? message?.usage : void 0;
1138
1366
  const usage = isRecord(parsed.usage) ? parsed.usage : void 0;
1367
+ for (const [key, source] of Object.entries(CLAUDE_USAGE_KEYS)) {
1368
+ const value = numberValue(usage?.[source]);
1369
+ if (value !== void 0) this.finalUsage[key] = value;
1370
+ }
1371
+ this.startUsage = omitUndefined({
1372
+ ...this.startUsage,
1373
+ inputTokens: numberValue(messageUsage?.input_tokens) ?? this.startUsage.inputTokens,
1374
+ outputTokens: numberValue(messageUsage?.output_tokens) ?? this.startUsage.outputTokens,
1375
+ cacheReadInputTokens: numberValue(messageUsage?.cache_read_input_tokens) ?? this.startUsage.cacheReadInputTokens,
1376
+ cacheCreationInputTokens: numberValue(messageUsage?.cache_creation_input_tokens) ?? this.startUsage.cacheCreationInputTokens
1377
+ });
1378
+ const iterations = Array.isArray(usage?.iterations) ? usage.iterations : messageUsage?.iterations;
1379
+ if (Array.isArray(iterations)) this.compaction = claudeUsageFields({ iterations });
1139
1380
  const metrics = isRecord(parsed["amazon-bedrock-invocationMetrics"]) ? parsed["amazon-bedrock-invocationMetrics"] : void 0;
1140
1381
  const outputs = Array.isArray(parsed.outputs) && isRecord(parsed.outputs[0]) ? parsed.outputs[0] : void 0;
1141
1382
  const generation = Array.isArray(parsed.generations) && isRecord(parsed.generations[0]) ? parsed.generations[0] : void 0;
1142
1383
  this.finishReason = stringValue(delta?.stop_reason) ?? stringValue(parsed.stop_reason) ?? stringValue(outputs?.stop_reason) ?? stringValue(generation?.finish_reason) ?? stringValue(parsed.completionReason) ?? this.finishReason;
1143
1384
  this.usage = omitUndefined({
1144
1385
  ...this.usage,
1145
- inputTokens: numberValue(messageUsage?.input_tokens) ?? numberValue(metrics?.inputTokenCount) ?? numberValue(parsed.inputTextTokenCount) ?? this.usage?.inputTokens,
1386
+ inputTokens: numberValue(usage?.input_tokens) ?? numberValue(metrics?.inputTokenCount) ?? numberValue(parsed.inputTextTokenCount) ?? this.usage?.inputTokens,
1146
1387
  outputTokens: numberValue(usage?.output_tokens) ?? numberValue(metrics?.outputTokenCount) ?? numberValue(parsed.totalOutputTextTokenCount) ?? this.usage?.outputTokens,
1147
- cacheReadInputTokens: numberValue(messageUsage?.cache_read_input_tokens) ?? this.usage?.cacheReadInputTokens,
1148
- cacheCreationInputTokens: numberValue(messageUsage?.cache_creation_input_tokens) ?? this.usage?.cacheCreationInputTokens
1388
+ cacheReadInputTokens: numberValue(usage?.cache_read_input_tokens) ?? this.usage?.cacheReadInputTokens,
1389
+ cacheCreationInputTokens: numberValue(usage?.cache_creation_input_tokens) ?? this.usage?.cacheCreationInputTokens
1149
1390
  });
1150
1391
  }
1151
1392
  return providerChunkHasOutput(parsed);
1152
1393
  }
1153
1394
  finish() {
1395
+ const usage = {
1396
+ ...this.startUsage,
1397
+ ...this.usage,
1398
+ ...this.finalUsage
1399
+ };
1400
+ for (const key of Object.keys(CLAUDE_USAGE_KEYS)) {
1401
+ const value = this.compaction.usage?.[key];
1402
+ const baseKey = key === "outputTokens" ? "outputTokens" : "inputTokens";
1403
+ if (value !== void 0 && (this.finalUsage[baseKey] !== void 0 || this.usage?.[baseKey] === void 0)) usage[key] = (usage[key] ?? 0) + value;
1404
+ }
1154
1405
  return mergeFields(omitUndefined({
1155
1406
  output: this.text ? [{
1156
1407
  role: "assistant",
@@ -1161,7 +1412,8 @@ var InvokeModelStreamState = class {
1161
1412
  finish_reason: this.finishReason
1162
1413
  }] : this.chunks,
1163
1414
  finishReason: this.finishReason,
1164
- usage: this.usage,
1415
+ usage,
1416
+ attributes: this.compaction.attributes,
1165
1417
  error: this.error
1166
1418
  }), this.errorFields);
1167
1419
  }
@@ -1278,7 +1530,7 @@ const AGENT_HANDLERS = {
1278
1530
  spanType: () => "span",
1279
1531
  requestFields: rerankRequestFields,
1280
1532
  onResult(result, span) {
1281
- endSpan(span, rerankResponseFields(result));
1533
+ endSpanWithBoundedOutput(span, rerankResponseFields(result));
1282
1534
  return result;
1283
1535
  }
1284
1536
  },