diff --git a/.changeset/silly-nights-walk.md b/.changeset/silly-nights-walk.md
new file mode 100644
index 000000000..1ae0dd0db
--- /dev/null
+++ b/.changeset/silly-nights-walk.md
@@ -0,0 +1,5 @@
+---
+"braintrust": minor
+---
+
+feat: Add support for `@strands-agents/sdk`
diff --git a/e2e/config/pr-comment-scenarios.json b/e2e/config/pr-comment-scenarios.json
index ee98b2fba..2bfa0dfab 100644
--- a/e2e/config/pr-comment-scenarios.json
+++ b/e2e/config/pr-comment-scenarios.json
@@ -27,6 +27,12 @@
"metadataScenario": "pi-coding-agent-instrumentation",
"variants": [{ "variantKey": "pi-coding-agent-v079", "label": "v0.79" }]
},
+ {
+ "scenarioDirName": "strands-agent-sdk-instrumentation",
+ "label": "Strands Agent SDK Instrumentation",
+ "metadataScenario": "strands-agent-sdk-instrumentation",
+ "variants": [{ "variantKey": "strands-agent-sdk-v160", "label": "v1.6.0" }]
+ },
{
"scenarioDirName": "openai-agents-instrumentation",
"label": "OpenAI Agents Instrumentation",
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/__cassettes__/strands-agent-sdk-v160.cassette.json b/e2e/scenarios/strands-agent-sdk-instrumentation/__cassettes__/strands-agent-sdk-v160.cassette.json
new file mode 100644
index 000000000..729e3aebc
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/__cassettes__/strands-agent-sdk-v160.cassette.json
@@ -0,0 +1,730 @@
+{
+ "entries": [
+ {
+ "callIndex": 0,
+ "id": "f4cfeb0f63426af9",
+ "matchKey": "POST api.openai.com/v1/chat/completions",
+ "recordedAt": "2026-06-18T14:19:59.210Z",
+ "request": {
+ "body": {
+ "kind": "json",
+ "value": {
+ "max_completion_tokens": 160,
+ "messages": [
+ {
+ "content": "You are a deterministic test agent. Always call lookup_weather exactly once for Vienna, then answer exactly STRANDS_AGENT_TOOL_OK.",
+ "role": "system"
+ },
+ {
+ "content": [
+ {
+ "text": "Use lookup_weather for Vienna, then answer exactly STRANDS_AGENT_TOOL_OK.",
+ "type": "text"
+ }
+ ],
+ "role": "user"
+ }
+ ],
+ "model": "gpt-4o-mini-2024-07-18",
+ "stream": true,
+ "stream_options": {
+ "include_usage": true
+ },
+ "temperature": 0,
+ "tools": [
+ {
+ "function": {
+ "description": "Return a deterministic weather report for one city.",
+ "name": "lookup_weather",
+ "parameters": {
+ "properties": {
+ "city": {
+ "type": "string"
+ }
+ },
+ "required": ["city"],
+ "type": "object"
+ }
+ },
+ "type": "function"
+ }
+ ]
+ }
+ },
+ "headers": {},
+ "method": "POST",
+ "url": "https://api.openai.com/v1/chat/completions"
+ },
+ "response": {
+ "body": {
+ "chunks": [
+ "data: {\"id\":\"chatcmpl-Ds7t0xMACYpkfXid5mwnMs3P59cxm\",\"object\":\"chat.completion.chunk\",\"created\":1781792398,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"index\":0,\"id\":\"call_mbDgh0RdovSgQqERgsAGv77i\",\"type\":\"function\",\"function\":{\"name\":\"lookup_weather\",\"arguments\":\"\"}}],\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"d\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t0xMACYpkfXid5mwnMs3P59cxm\",\"object\":\"chat.completion.chunk\",\"created\":1781792398,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"{\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"fhceWzbWvJk1Wl\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t0xMACYpkfXid5mwnMs3P59cxm\",\"object\":\"chat.completion.chunk\",\"created\":1781792398,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"city\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"41TlwUxKk60NR\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t0xMACYpkfXid5mwnMs3P59cxm\",\"object\":\"chat.completion.chunk\",\"created\":1781792398,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\":\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"y2vJLhN1HrSi\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t0xMACYpkfXid5mwnMs3P59cxm\",\"object\":\"chat.completion.chunk\",\"created\":1781792398,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"Vien\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"SdZayNuwo2w2O\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t0xMACYpkfXid5mwnMs3P59cxm\",\"object\":\"chat.completion.chunk\",\"created\":1781792398,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"na\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"ohiNThF0hMH83Ow\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t0xMACYpkfXid5mwnMs3P59cxm\",\"object\":\"chat.completion.chunk\",\"created\":1781792398,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\"}\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"4tz04kET3JTwEL\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t0xMACYpkfXid5mwnMs3P59cxm\",\"object\":\"chat.completion.chunk\",\"created\":1781792398,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"tool_calls\"}],\"usage\":null,\"obfuscation\":\"msNzf5UbMatL9Vh\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t0xMACYpkfXid5mwnMs3P59cxm\",\"object\":\"chat.completion.chunk\",\"created\":1781792398,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[],\"usage\":{\"prompt_tokens\":86,\"completion_tokens\":15,\"total_tokens\":101,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"Yk2aPOEJd\"}",
+ "data: [DONE]"
+ ],
+ "kind": "sse"
+ },
+ "headers": {
+ "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray",
+ "alt-svc": "h3=\":443\"; ma=86400",
+ "cf-cache-status": "DYNAMIC",
+ "cf-ray": "a0daee9a0d43c24f-VIE",
+ "connection": "keep-alive",
+ "content-type": "text/event-stream; charset=utf-8",
+ "date": "Thu, 18 Jun 2026 14:19:59 GMT",
+ "openai-organization": "braintrust-data",
+ "openai-processing-ms": "569",
+ "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9",
+ "openai-version": "2020-10-01",
+ "server": "cloudflare",
+ "set-cookie": "[REDACTED]",
+ "strict-transport-security": "max-age=31536000; includeSubDomains; preload",
+ "transfer-encoding": "chunked",
+ "x-content-type-options": "nosniff",
+ "x-openai-proxy-wasm": "v0.1",
+ "x-ratelimit-limit-requests": "30000",
+ "x-ratelimit-limit-tokens": "150000000",
+ "x-ratelimit-remaining-requests": "29999",
+ "x-ratelimit-remaining-tokens": "149999945",
+ "x-ratelimit-reset-requests": "2ms",
+ "x-ratelimit-reset-tokens": "0s",
+ "x-request-id": "req_ac0653ff238e4af29d4358857853f412"
+ },
+ "status": 200,
+ "statusText": "OK"
+ }
+ },
+ {
+ "callIndex": 1,
+ "id": "7c32ae1e1e08f309",
+ "matchKey": "POST api.openai.com/v1/chat/completions",
+ "recordedAt": "2026-06-18T14:19:59.941Z",
+ "request": {
+ "body": {
+ "kind": "json",
+ "value": {
+ "max_completion_tokens": 160,
+ "messages": [
+ {
+ "content": "You are a deterministic test agent. Always call lookup_weather exactly once for Vienna, then answer exactly STRANDS_AGENT_TOOL_OK.",
+ "role": "system"
+ },
+ {
+ "content": [
+ {
+ "text": "Use lookup_weather for Vienna, then answer exactly STRANDS_AGENT_TOOL_OK.",
+ "type": "text"
+ }
+ ],
+ "role": "user"
+ },
+ {
+ "content": "",
+ "role": "assistant",
+ "tool_calls": [
+ {
+ "function": {
+ "arguments": "{\"city\":\"Vienna\"}",
+ "name": "lookup_weather"
+ },
+ "id": "call_mbDgh0RdovSgQqERgsAGv77i",
+ "type": "function"
+ }
+ ]
+ },
+ {
+ "content": "{\"city\":\"Vienna\",\"forecast\":\"sunny\",\"marker\":\"STRANDS_TOOL_OK\"}",
+ "role": "tool",
+ "tool_call_id": "call_mbDgh0RdovSgQqERgsAGv77i"
+ }
+ ],
+ "model": "gpt-4o-mini-2024-07-18",
+ "stream": true,
+ "stream_options": {
+ "include_usage": true
+ },
+ "temperature": 0,
+ "tools": [
+ {
+ "function": {
+ "description": "Return a deterministic weather report for one city.",
+ "name": "lookup_weather",
+ "parameters": {
+ "properties": {
+ "city": {
+ "type": "string"
+ }
+ },
+ "required": ["city"],
+ "type": "object"
+ }
+ },
+ "type": "function"
+ }
+ ]
+ }
+ },
+ "headers": {},
+ "method": "POST",
+ "url": "https://api.openai.com/v1/chat/completions"
+ },
+ "response": {
+ "body": {
+ "chunks": [
+ "data: {\"id\":\"chatcmpl-Ds7t1OFfsEw81aE4BxobsBfZHfyMN\",\"object\":\"chat.completion.chunk\",\"created\":1781792399,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"kVrGlr9Yr\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t1OFfsEw81aE4BxobsBfZHfyMN\",\"object\":\"chat.completion.chunk\",\"created\":1781792399,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"STR\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"rBjUFZSw\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t1OFfsEw81aE4BxobsBfZHfyMN\",\"object\":\"chat.completion.chunk\",\"created\":1781792399,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ANDS\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"4mcymcO\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t1OFfsEw81aE4BxobsBfZHfyMN\",\"object\":\"chat.completion.chunk\",\"created\":1781792399,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"_AGENT\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"p6WTr\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t1OFfsEw81aE4BxobsBfZHfyMN\",\"object\":\"chat.completion.chunk\",\"created\":1781792399,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"_TOOL\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"5hvYdn\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t1OFfsEw81aE4BxobsBfZHfyMN\",\"object\":\"chat.completion.chunk\",\"created\":1781792399,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"_OK\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"nyoupvL1\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t1OFfsEw81aE4BxobsBfZHfyMN\",\"object\":\"chat.completion.chunk\",\"created\":1781792399,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":null,\"obfuscation\":\"3Xmze\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t1OFfsEw81aE4BxobsBfZHfyMN\",\"object\":\"chat.completion.chunk\",\"created\":1781792399,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_a1aad5391f\",\"choices\":[],\"usage\":{\"prompt_tokens\":127,\"completion_tokens\":6,\"total_tokens\":133,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"XEQVXM5Sk\"}",
+ "data: [DONE]"
+ ],
+ "kind": "sse"
+ },
+ "headers": {
+ "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray",
+ "alt-svc": "h3=\":443\"; ma=86400",
+ "cf-cache-status": "DYNAMIC",
+ "cf-ray": "a0daee9ffb07c24f-VIE",
+ "connection": "keep-alive",
+ "content-type": "text/event-stream; charset=utf-8",
+ "date": "Thu, 18 Jun 2026 14:19:59 GMT",
+ "openai-organization": "braintrust-data",
+ "openai-processing-ms": "310",
+ "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9",
+ "openai-version": "2020-10-01",
+ "server": "cloudflare",
+ "set-cookie": "[REDACTED]",
+ "strict-transport-security": "max-age=31536000; includeSubDomains; preload",
+ "transfer-encoding": "chunked",
+ "x-content-type-options": "nosniff",
+ "x-openai-proxy-wasm": "v0.1",
+ "x-ratelimit-limit-requests": "30000",
+ "x-ratelimit-limit-tokens": "150000000",
+ "x-ratelimit-remaining-requests": "29999",
+ "x-ratelimit-remaining-tokens": "149999927",
+ "x-ratelimit-reset-requests": "2ms",
+ "x-ratelimit-reset-tokens": "0s",
+ "x-request-id": "req_94b59424c7104cb79041e8fe3bf23997"
+ },
+ "status": 200,
+ "statusText": "OK"
+ }
+ },
+ {
+ "callIndex": 2,
+ "id": "7f682068be947c96",
+ "matchKey": "POST api.openai.com/v1/chat/completions",
+ "recordedAt": "2026-06-18T14:20:00.867Z",
+ "request": {
+ "body": {
+ "kind": "json",
+ "value": {
+ "max_completion_tokens": 160,
+ "messages": [
+ {
+ "content": "Reply exactly STRANDS_AGENT_STREAM_OK.",
+ "role": "system"
+ },
+ {
+ "content": [
+ {
+ "text": "Reply exactly STRANDS_AGENT_STREAM_OK.",
+ "type": "text"
+ }
+ ],
+ "role": "user"
+ }
+ ],
+ "model": "gpt-4o-mini-2024-07-18",
+ "stream": true,
+ "stream_options": {
+ "include_usage": true
+ },
+ "temperature": 0
+ }
+ },
+ "headers": {},
+ "method": "POST",
+ "url": "https://api.openai.com/v1/chat/completions"
+ },
+ "response": {
+ "body": {
+ "chunks": [
+ "data: {\"id\":\"chatcmpl-Ds7t23uscJUyEKe9f1uqu2KfrPBPx\",\"object\":\"chat.completion.chunk\",\"created\":1781792400,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_7396530e6a\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"sGYTuEwid\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t23uscJUyEKe9f1uqu2KfrPBPx\",\"object\":\"chat.completion.chunk\",\"created\":1781792400,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_7396530e6a\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"STR\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"tTU9TS1L\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t23uscJUyEKe9f1uqu2KfrPBPx\",\"object\":\"chat.completion.chunk\",\"created\":1781792400,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_7396530e6a\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ANDS\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"mrRmvZE\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t23uscJUyEKe9f1uqu2KfrPBPx\",\"object\":\"chat.completion.chunk\",\"created\":1781792400,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_7396530e6a\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"_AGENT\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"q8eX4\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t23uscJUyEKe9f1uqu2KfrPBPx\",\"object\":\"chat.completion.chunk\",\"created\":1781792400,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_7396530e6a\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"_STREAM\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"I5KH\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t23uscJUyEKe9f1uqu2KfrPBPx\",\"object\":\"chat.completion.chunk\",\"created\":1781792400,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_7396530e6a\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"_OK\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"S90gyPz5\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t23uscJUyEKe9f1uqu2KfrPBPx\",\"object\":\"chat.completion.chunk\",\"created\":1781792400,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_7396530e6a\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"STniGBQr0X\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t23uscJUyEKe9f1uqu2KfrPBPx\",\"object\":\"chat.completion.chunk\",\"created\":1781792400,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_7396530e6a\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":null,\"obfuscation\":\"AVoEr\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t23uscJUyEKe9f1uqu2KfrPBPx\",\"object\":\"chat.completion.chunk\",\"created\":1781792400,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_7396530e6a\",\"choices\":[],\"usage\":{\"prompt_tokens\":27,\"completion_tokens\":6,\"total_tokens\":33,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"JcJMXbgYUjG\"}",
+ "data: [DONE]"
+ ],
+ "kind": "sse"
+ },
+ "headers": {
+ "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray",
+ "alt-svc": "h3=\":443\"; ma=86400",
+ "cf-cache-status": "DYNAMIC",
+ "cf-ray": "a0daeea48fdec24f-VIE",
+ "connection": "keep-alive",
+ "content-type": "text/event-stream; charset=utf-8",
+ "date": "Thu, 18 Jun 2026 14:20:00 GMT",
+ "openai-organization": "braintrust-data",
+ "openai-processing-ms": "451",
+ "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9",
+ "openai-version": "2020-10-01",
+ "server": "cloudflare",
+ "set-cookie": "[REDACTED]",
+ "strict-transport-security": "max-age=31536000; includeSubDomains; preload",
+ "transfer-encoding": "chunked",
+ "x-content-type-options": "nosniff",
+ "x-openai-proxy-wasm": "v0.1",
+ "x-ratelimit-limit-requests": "30000",
+ "x-ratelimit-limit-tokens": "150000000",
+ "x-ratelimit-remaining-requests": "29999",
+ "x-ratelimit-remaining-tokens": "149999977",
+ "x-ratelimit-reset-requests": "2ms",
+ "x-ratelimit-reset-tokens": "0s",
+ "x-request-id": "req_80eb7af70057450e8d6b7874ad1cfbb9"
+ },
+ "status": 200,
+ "statusText": "OK"
+ }
+ },
+ {
+ "callIndex": 3,
+ "id": "f1affedbdb5d1da0",
+ "matchKey": "POST api.openai.com/v1/chat/completions",
+ "recordedAt": "2026-06-18T14:20:01.798Z",
+ "request": {
+ "body": {
+ "kind": "json",
+ "value": {
+ "max_completion_tokens": 160,
+ "messages": [
+ {
+ "content": "Reply exactly GRAPH_RESEARCH_DONE.",
+ "role": "system"
+ },
+ {
+ "content": [
+ {
+ "text": "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK.",
+ "type": "text"
+ }
+ ],
+ "role": "user"
+ }
+ ],
+ "model": "gpt-4o-mini-2024-07-18",
+ "stream": true,
+ "stream_options": {
+ "include_usage": true
+ },
+ "temperature": 0
+ }
+ },
+ "headers": {},
+ "method": "POST",
+ "url": "https://api.openai.com/v1/chat/completions"
+ },
+ "response": {
+ "body": {
+ "chunks": [
+ "data: {\"id\":\"chatcmpl-Ds7t3HDUrqAqy5Xyr6iNo53LIq7GQ\",\"object\":\"chat.completion.chunk\",\"created\":1781792401,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_eee5e0b136\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"MBxO0hTG4\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t3HDUrqAqy5Xyr6iNo53LIq7GQ\",\"object\":\"chat.completion.chunk\",\"created\":1781792401,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_eee5e0b136\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"GRAPH\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"hVJYgn\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t3HDUrqAqy5Xyr6iNo53LIq7GQ\",\"object\":\"chat.completion.chunk\",\"created\":1781792401,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_eee5e0b136\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"_RE\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"P2aJfC2Z\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t3HDUrqAqy5Xyr6iNo53LIq7GQ\",\"object\":\"chat.completion.chunk\",\"created\":1781792401,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_eee5e0b136\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"SEARCH\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"fygZc\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t3HDUrqAqy5Xyr6iNo53LIq7GQ\",\"object\":\"chat.completion.chunk\",\"created\":1781792401,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_eee5e0b136\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"_DONE\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"Ata5gu\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t3HDUrqAqy5Xyr6iNo53LIq7GQ\",\"object\":\"chat.completion.chunk\",\"created\":1781792401,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_eee5e0b136\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":null,\"obfuscation\":\"6MdN4\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t3HDUrqAqy5Xyr6iNo53LIq7GQ\",\"object\":\"chat.completion.chunk\",\"created\":1781792401,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_eee5e0b136\",\"choices\":[],\"usage\":{\"prompt_tokens\":35,\"completion_tokens\":4,\"total_tokens\":39,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"7uMhMC58XRN\"}",
+ "data: [DONE]"
+ ],
+ "kind": "sse"
+ },
+ "headers": {
+ "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray",
+ "alt-svc": "h3=\":443\"; ma=86400",
+ "cf-cache-status": "DYNAMIC",
+ "cf-ray": "a0daeeaa3db1c24f-VIE",
+ "connection": "keep-alive",
+ "content-type": "text/event-stream; charset=utf-8",
+ "date": "Thu, 18 Jun 2026 14:20:01 GMT",
+ "openai-organization": "braintrust-data",
+ "openai-processing-ms": "411",
+ "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9",
+ "openai-version": "2020-10-01",
+ "server": "cloudflare",
+ "set-cookie": "[REDACTED]",
+ "strict-transport-security": "max-age=31536000; includeSubDomains; preload",
+ "transfer-encoding": "chunked",
+ "x-content-type-options": "nosniff",
+ "x-openai-proxy-wasm": "v0.1",
+ "x-ratelimit-limit-requests": "30000",
+ "x-ratelimit-limit-tokens": "150000000",
+ "x-ratelimit-remaining-requests": "29999",
+ "x-ratelimit-remaining-tokens": "149999970",
+ "x-ratelimit-reset-requests": "2ms",
+ "x-ratelimit-reset-tokens": "0s",
+ "x-request-id": "req_06d536c5ee4e4e74ba18e62bc13edc08"
+ },
+ "status": 200,
+ "statusText": "OK"
+ }
+ },
+ {
+ "callIndex": 4,
+ "id": "da2ccaeedff7ef8b",
+ "matchKey": "POST api.openai.com/v1/chat/completions",
+ "recordedAt": "2026-06-18T14:20:02.321Z",
+ "request": {
+ "body": {
+ "kind": "json",
+ "value": {
+ "max_completion_tokens": 160,
+ "messages": [
+ {
+ "content": "Reply exactly STRANDS_GRAPH_OK.",
+ "role": "system"
+ },
+ {
+ "content": [
+ {
+ "text": "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK.",
+ "type": "text"
+ },
+ {
+ "text": "[node: graph-researcher]",
+ "type": "text"
+ },
+ {
+ "text": "GRAPH_RESEARCH_DONE",
+ "type": "text"
+ }
+ ],
+ "role": "user"
+ }
+ ],
+ "model": "gpt-4o-mini-2024-07-18",
+ "stream": true,
+ "stream_options": {
+ "include_usage": true
+ },
+ "temperature": 0
+ }
+ },
+ "headers": {},
+ "method": "POST",
+ "url": "https://api.openai.com/v1/chat/completions"
+ },
+ "response": {
+ "body": {
+ "chunks": [
+ "data: {\"id\":\"chatcmpl-Ds7t4bzueUCNI3ncT3VmgLobzJIiR\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_944bbf963c\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"FxdXLCgEG\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4bzueUCNI3ncT3VmgLobzJIiR\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_944bbf963c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"STR\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"4ZrLh7F4\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4bzueUCNI3ncT3VmgLobzJIiR\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_944bbf963c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ANDS\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"rbZEnTq\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4bzueUCNI3ncT3VmgLobzJIiR\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_944bbf963c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"_GRAPH\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"XvTmC\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4bzueUCNI3ncT3VmgLobzJIiR\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_944bbf963c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"_OK\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"njpC1qwi\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4bzueUCNI3ncT3VmgLobzJIiR\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_944bbf963c\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":null,\"obfuscation\":\"3Ncel\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4bzueUCNI3ncT3VmgLobzJIiR\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_944bbf963c\",\"choices\":[],\"usage\":{\"prompt_tokens\":46,\"completion_tokens\":4,\"total_tokens\":50,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"dtu7yeODzeT\"}",
+ "data: [DONE]"
+ ],
+ "kind": "sse"
+ },
+ "headers": {
+ "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray",
+ "alt-svc": "h3=\":443\"; ma=86400",
+ "cf-cache-status": "DYNAMIC",
+ "cf-ray": "a0daeeb00b23c24f-VIE",
+ "connection": "keep-alive",
+ "content-type": "text/event-stream; charset=utf-8",
+ "date": "Thu, 18 Jun 2026 14:20:02 GMT",
+ "openai-organization": "braintrust-data",
+ "openai-processing-ms": "156",
+ "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9",
+ "openai-version": "2020-10-01",
+ "server": "cloudflare",
+ "set-cookie": "[REDACTED]",
+ "strict-transport-security": "max-age=31536000; includeSubDomains; preload",
+ "transfer-encoding": "chunked",
+ "x-content-type-options": "nosniff",
+ "x-openai-proxy-wasm": "v0.1",
+ "x-ratelimit-limit-requests": "30000",
+ "x-ratelimit-limit-tokens": "150000000",
+ "x-ratelimit-remaining-requests": "29999",
+ "x-ratelimit-remaining-tokens": "149999957",
+ "x-ratelimit-reset-requests": "2ms",
+ "x-ratelimit-reset-tokens": "0s",
+ "x-request-id": "req_8376ea31b4be41d1b3947e9f0e24a7d8"
+ },
+ "status": 200,
+ "statusText": "OK"
+ }
+ },
+ {
+ "callIndex": 5,
+ "id": "bb7637a47f479eeb",
+ "matchKey": "POST api.openai.com/v1/chat/completions",
+ "recordedAt": "2026-06-18T14:20:03.435Z",
+ "request": {
+ "body": {
+ "kind": "json",
+ "value": {
+ "max_completion_tokens": 160,
+ "messages": [
+ {
+ "content": "Use the structured output schema. Hand off to swarm-finisher with message finish with STRANDS_SWARM_OK.",
+ "role": "system"
+ },
+ {
+ "content": [
+ {
+ "text": "Route to the finisher, then finish exactly STRANDS_SWARM_OK.",
+ "type": "text"
+ }
+ ],
+ "role": "user"
+ }
+ ],
+ "model": "gpt-4o-mini-2024-07-18",
+ "stream": true,
+ "stream_options": {
+ "include_usage": true
+ },
+ "temperature": 0,
+ "tools": [
+ {
+ "function": {
+ "description": "This tool MUST only be invoked as the last and final tool before returning the completed result to the caller.\nDecide whether to hand off to another agent or produce a final response.",
+ "name": "strands_structured_output",
+ "parameters": {
+ "additionalProperties": false,
+ "description": "Decide whether to hand off to another agent or produce a final response.",
+ "properties": {
+ "agentId": {
+ "description": "Target agent to hand off to. Omit to end the conversation.\n\nAvailable agents:\n- swarm-finisher: Finishes the test response.",
+ "enum": ["swarm-finisher"],
+ "type": "string"
+ },
+ "context": {
+ "additionalProperties": {},
+ "description": "Structured data to pass to the next agent.",
+ "propertyNames": {
+ "type": "string"
+ },
+ "type": "object"
+ },
+ "message": {
+ "description": "Instructions for the next agent, or the final response if no handoff.",
+ "type": "string"
+ }
+ },
+ "required": ["message"],
+ "type": "object"
+ }
+ },
+ "type": "function"
+ }
+ ]
+ }
+ },
+ "headers": {},
+ "method": "POST",
+ "url": "https://api.openai.com/v1/chat/completions"
+ },
+ "response": {
+ "body": {
+ "chunks": [
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"index\":0,\"id\":\"call_q0XD2r6QQ5O4VmphUgNKD2lK\",\"type\":\"function\",\"function\":{\"name\":\"strands_structured_output\",\"arguments\":\"\"}}],\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"gpc2Tp\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"{\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"YCaS5KhCvDdSs5\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"agent\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"1SQNycCAQIPr\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"Id\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"kF5s4OdnSY9QMWK\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\":\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"xFPOvTWkaTj6\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"sw\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"oZAYm5yVYyvz6GR\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"arm\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"IbmobzpjfcyXrg\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"-fin\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"HkbTZKKRchqaX\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"isher\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"j4cpWsZAWqsW\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\",\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"YcW990fbz2Li\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"message\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"kzmtmveIaG\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\":\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"8fFMoZ7ETfR3\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"finish\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"z9cw9cXjRsF\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\" with\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"Ki1eJkmCG5bo\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\" STR\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"LbeCuCssJyCnD\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"ANDS\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"4NYR7UkuwofER\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"_SW\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"bf4p5vqz5uQaok\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"ARM\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"HlOTt5rpoFhAJg\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"_OK\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"3UF348Nkhe1UEu\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\"}\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"G4A9qqtl8St5ZN\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"tool_calls\"}],\"usage\":null,\"obfuscation\":\"qfJQ4JZl1XnGCuz\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t4UcdNamY4iQXZeDa5IiJmkUfB\",\"object\":\"chat.completion.chunk\",\"created\":1781792402,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_f4c4edc0d3\",\"choices\":[],\"usage\":{\"prompt_tokens\":198,\"completion_tokens\":31,\"total_tokens\":229,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"PD3WHLUv\"}",
+ "data: [DONE]"
+ ],
+ "kind": "sse"
+ },
+ "headers": {
+ "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray",
+ "alt-svc": "h3=\":443\"; ma=86400",
+ "cf-cache-status": "DYNAMIC",
+ "cf-ray": "a0daeeb36e6ac24f-VIE",
+ "connection": "keep-alive",
+ "content-type": "text/event-stream; charset=utf-8",
+ "date": "Thu, 18 Jun 2026 14:20:03 GMT",
+ "openai-organization": "braintrust-data",
+ "openai-processing-ms": "499",
+ "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9",
+ "openai-version": "2020-10-01",
+ "server": "cloudflare",
+ "set-cookie": "[REDACTED]",
+ "strict-transport-security": "max-age=31536000; includeSubDomains; preload",
+ "transfer-encoding": "chunked",
+ "x-content-type-options": "nosniff",
+ "x-openai-proxy-wasm": "v0.1",
+ "x-ratelimit-limit-requests": "30000",
+ "x-ratelimit-limit-tokens": "150000000",
+ "x-ratelimit-remaining-requests": "29999",
+ "x-ratelimit-remaining-tokens": "149999955",
+ "x-ratelimit-reset-requests": "2ms",
+ "x-ratelimit-reset-tokens": "0s",
+ "x-request-id": "req_67461b3d77f349cca9fabc366c293bda"
+ },
+ "status": 200,
+ "statusText": "OK"
+ }
+ },
+ {
+ "callIndex": 6,
+ "id": "85b37d48b99f35cb",
+ "matchKey": "POST api.openai.com/v1/chat/completions",
+ "recordedAt": "2026-06-18T14:20:04.202Z",
+ "request": {
+ "body": {
+ "kind": "json",
+ "value": {
+ "max_completion_tokens": 160,
+ "messages": [
+ {
+ "content": "Use the structured output schema. Do not hand off. Set the final message to exactly STRANDS_SWARM_OK.",
+ "role": "system"
+ },
+ {
+ "content": [
+ {
+ "text": "finish with STRANDS_SWARM_OK",
+ "type": "text"
+ }
+ ],
+ "role": "user"
+ }
+ ],
+ "model": "gpt-4o-mini-2024-07-18",
+ "stream": true,
+ "stream_options": {
+ "include_usage": true
+ },
+ "temperature": 0,
+ "tools": [
+ {
+ "function": {
+ "description": "This tool MUST only be invoked as the last and final tool before returning the completed result to the caller.\nDecide whether to hand off to another agent or produce a final response.",
+ "name": "strands_structured_output",
+ "parameters": {
+ "additionalProperties": false,
+ "description": "Decide whether to hand off to another agent or produce a final response.",
+ "properties": {
+ "agentId": {
+ "description": "Target agent to hand off to. Omit to end the conversation.\n\nAvailable agents:\n- swarm-router: Routes work to the finisher agent.",
+ "enum": ["swarm-router"],
+ "type": "string"
+ },
+ "context": {
+ "additionalProperties": {},
+ "description": "Structured data to pass to the next agent.",
+ "propertyNames": {
+ "type": "string"
+ },
+ "type": "object"
+ },
+ "message": {
+ "description": "Instructions for the next agent, or the final response if no handoff.",
+ "type": "string"
+ }
+ },
+ "required": ["message"],
+ "type": "object"
+ }
+ },
+ "type": "function"
+ }
+ ]
+ }
+ },
+ "headers": {},
+ "method": "POST",
+ "url": "https://api.openai.com/v1/chat/completions"
+ },
+ "response": {
+ "body": {
+ "chunks": [
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"index\":0,\"id\":\"call_2hbwdCfA3AJO5su7xD5IHmbT\",\"type\":\"function\",\"function\":{\"name\":\"strands_structured_output\",\"arguments\":\"\"}}],\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"BiW62l\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"{\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"o14K0rT6u8peCL\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"message\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"iizawaSWNb\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\":\\\"\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"bfYDroT2n9Gl\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"STR\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"OhGIjlLT0Cc5a6\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"ANDS\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"ChFEkPtiPvbMj\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"_SW\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"0JyNjkKbLxxVan\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"ARM\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"a5HRAPBmVc5Ckl\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"_OK\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"GjKfBH1aESoOFo\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\"}\"}}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"wGmvYKD5Nwe6Cr\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"tool_calls\"}],\"usage\":null,\"obfuscation\":\"SXJfbLraNcwxXTt\"}",
+ "data: {\"id\":\"chatcmpl-Ds7t56eG4oGPTtWeZhLIcWLGBKbHT\",\"object\":\"chat.completion.chunk\",\"created\":1781792403,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_899b586ef5\",\"choices\":[],\"usage\":{\"prompt_tokens\":191,\"completion_tokens\":21,\"total_tokens\":212,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"SRX6AAUf\"}",
+ "data: [DONE]"
+ ],
+ "kind": "sse"
+ },
+ "headers": {
+ "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray",
+ "alt-svc": "h3=\":443\"; ma=86400",
+ "cf-cache-status": "DYNAMIC",
+ "cf-ray": "a0daeeba4d27c24f-VIE",
+ "connection": "keep-alive",
+ "content-type": "text/event-stream; charset=utf-8",
+ "date": "Thu, 18 Jun 2026 14:20:04 GMT",
+ "openai-organization": "braintrust-data",
+ "openai-processing-ms": "349",
+ "openai-project": "proj_vsCSXafhhByzWOThMrJcZiw9",
+ "openai-version": "2020-10-01",
+ "server": "cloudflare",
+ "set-cookie": "[REDACTED]",
+ "strict-transport-security": "max-age=31536000; includeSubDomains; preload",
+ "transfer-encoding": "chunked",
+ "x-content-type-options": "nosniff",
+ "x-openai-proxy-wasm": "v0.1",
+ "x-ratelimit-limit-requests": "30000",
+ "x-ratelimit-limit-tokens": "150000000",
+ "x-ratelimit-remaining-requests": "29999",
+ "x-ratelimit-remaining-tokens": "149999965",
+ "x-ratelimit-reset-requests": "2ms",
+ "x-ratelimit-reset-tokens": "0s",
+ "x-request-id": "req_21ee960568de454ca2662e264189dff4"
+ },
+ "status": 200,
+ "statusText": "OK"
+ }
+ }
+ ],
+ "meta": {
+ "createdAt": "2026-06-18T14:16:44.922Z"
+ }
+}
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-auto-hook.span-tree.json b/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-auto-hook.span-tree.json
new file mode 100644
index 000000000..f45ed1f21
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-auto-hook.span-tree.json
@@ -0,0 +1,548 @@
+{
+ "span_tree": [
+ {
+ "name": "strands-agent-sdk-instrumentation-root",
+ "type": "task",
+ "children": [
+ {
+ "name": "strands-agent-invoke-operation",
+ "children": [
+ {
+ "name": "Agent: weather-agent",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "toolUse"
+ },
+ "metrics": {
+ "completion_tokens": 15,
+ "duration": "",
+ "prompt_tokens": 86,
+ "tokens": 101
+ }
+ },
+ {
+ "name": "tool: lookup_weather",
+ "type": "tool",
+ "children": [],
+ "input": {
+ "city": "Vienna"
+ },
+ "output": {
+ "toolResult": {
+ "content": [
+ {
+ "json": {
+ "city": "Vienna",
+ "forecast": "sunny",
+ "marker": "STRANDS_TOOL_OK"
+ }
+ }
+ ],
+ "status": "success",
+ "toolUseId": ""
+ }
+ },
+ "metadata": {
+ "gen_ai.tool.name": "lookup_weather",
+ "provider": "strands",
+ "strands.operation": "tool.call",
+ "strands.tool.name": "lookup_weather"
+ },
+ "metrics": {
+ "duration": ""
+ }
+ },
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 6,
+ "duration": "",
+ "prompt_tokens": 127,
+ "tokens": 133
+ }
+ }
+ ],
+ "input": "Use lookup_weather for Vienna, then answer exactly STRANDS_AGENT_TOOL_OK.",
+ "output": {
+ "content": [
+ {
+ "text": "STRANDS_AGENT_TOOL_OK"
+ }
+ ],
+ "metadata": {
+ "usage": {
+ "inputTokens": 127,
+ "outputTokens": 6,
+ "totalTokens": 133
+ }
+ },
+ "role": "assistant"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "weather-agent",
+ "strands.agent.name": "weather-agent",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 213,
+ "tokens": 234
+ }
+ }
+ ],
+ "metadata": {
+ "operation": "agent-invoke"
+ }
+ },
+ {
+ "name": "strands-agent-stream-operation",
+ "children": [
+ {
+ "name": "Agent: stream-agent",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 6,
+ "duration": "",
+ "prompt_tokens": 27,
+ "tokens": 33
+ }
+ }
+ ],
+ "input": "Reply exactly STRANDS_AGENT_STREAM_OK.",
+ "output": {
+ "content": [
+ {
+ "text": "STRANDS_AGENT_STREAM_OK."
+ }
+ ],
+ "metadata": {
+ "usage": {
+ "inputTokens": 27,
+ "outputTokens": 6,
+ "totalTokens": 33
+ }
+ },
+ "role": "assistant"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "stream-agent",
+ "strands.agent.name": "stream-agent",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 6,
+ "duration": "",
+ "prompt_tokens": 27,
+ "tokens": 33
+ }
+ }
+ ],
+ "metadata": {
+ "operation": "agent-stream"
+ }
+ },
+ {
+ "name": "strands-graph-invoke-operation",
+ "children": [
+ {
+ "name": "Strands Graph",
+ "type": "task",
+ "children": [
+ {
+ "name": "node: graph-researcher",
+ "type": "task",
+ "children": [
+ {
+ "name": "Agent: graph-researcher",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 35,
+ "tokens": 39
+ }
+ }
+ ],
+ "input": "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK.",
+ "output": {
+ "content": [
+ {
+ "text": "GRAPH_RESEARCH_DONE"
+ }
+ ],
+ "metadata": {
+ "usage": {
+ "inputTokens": 35,
+ "outputTokens": 4,
+ "totalTokens": 39
+ }
+ },
+ "role": "assistant"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "graph-researcher",
+ "strands.agent.name": "graph-researcher",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 35,
+ "tokens": 39
+ }
+ }
+ ],
+ "output": "GRAPH_RESEARCH_DONE",
+ "metadata": {
+ "provider": "strands",
+ "strands.node.id": "graph-researcher",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 35,
+ "strands.node.duration_ms": "",
+ "tokens": 39
+ }
+ },
+ {
+ "name": "node: graph-writer",
+ "type": "task",
+ "children": [
+ {
+ "name": "Agent: graph-writer",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 46,
+ "tokens": 50
+ }
+ }
+ ],
+ "input": [
+ {
+ "text": "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK."
+ },
+ {
+ "text": "[node: graph-researcher]"
+ },
+ {
+ "text": "GRAPH_RESEARCH_DONE"
+ }
+ ],
+ "output": {
+ "content": [
+ {
+ "text": "STRANDS_GRAPH_OK"
+ }
+ ],
+ "metadata": {
+ "usage": {
+ "inputTokens": 46,
+ "outputTokens": 4,
+ "totalTokens": 50
+ }
+ },
+ "role": "assistant"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "graph-writer",
+ "strands.agent.name": "graph-writer",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 46,
+ "tokens": 50
+ }
+ }
+ ],
+ "output": "STRANDS_GRAPH_OK",
+ "metadata": {
+ "provider": "strands",
+ "strands.node.id": "graph-writer",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 46,
+ "strands.node.duration_ms": "",
+ "tokens": 50
+ }
+ }
+ ],
+ "input": "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK.",
+ "output": "STRANDS_GRAPH_OK",
+ "metadata": {
+ "provider": "strands",
+ "strands.handoffs": [
+ {
+ "source": "graph-researcher",
+ "targets": [
+ "graph-writer"
+ ]
+ }
+ ],
+ "strands.operation": "Graph.stream",
+ "strands.orchestrator.id": "weather-graph",
+ "strands.status": "COMPLETED"
+ },
+ "metrics": {
+ "completion_tokens": 8,
+ "duration": "",
+ "prompt_tokens": 81,
+ "tokens": 89
+ }
+ }
+ ],
+ "metadata": {
+ "operation": "graph-invoke"
+ }
+ },
+ {
+ "name": "strands-swarm-invoke-operation",
+ "children": [
+ {
+ "name": "Strands Swarm",
+ "type": "task",
+ "children": [
+ {
+ "name": "node: swarm-router",
+ "type": "task",
+ "children": [
+ {
+ "name": "Agent: swarm-router",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "toolUse"
+ },
+ "metrics": {
+ "completion_tokens": 31,
+ "duration": "",
+ "prompt_tokens": 198,
+ "tokens": 229
+ }
+ }
+ ],
+ "input": "Route to the finisher, then finish exactly STRANDS_SWARM_OK.",
+ "output": {
+ "agentId": "",
+ "message": "finish with STRANDS_SWARM_OK"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "swarm-router",
+ "strands.agent.name": "swarm-router",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "toolUse"
+ },
+ "metrics": {
+ "completion_tokens": 31,
+ "duration": "",
+ "prompt_tokens": 198,
+ "tokens": 229
+ }
+ }
+ ],
+ "output": {
+ "agentId": "",
+ "message": "finish with STRANDS_SWARM_OK"
+ },
+ "metadata": {
+ "provider": "strands",
+ "strands.node.id": "swarm-router",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ },
+ "metrics": {
+ "completion_tokens": 31,
+ "duration": "",
+ "prompt_tokens": 198,
+ "strands.node.duration_ms": "",
+ "tokens": 229
+ }
+ },
+ {
+ "name": "node: swarm-finisher",
+ "type": "task",
+ "children": [
+ {
+ "name": "Agent: swarm-finisher",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "toolUse"
+ },
+ "metrics": {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "tokens": 212
+ }
+ }
+ ],
+ "input": [
+ {
+ "text": "finish with STRANDS_SWARM_OK"
+ }
+ ],
+ "output": {
+ "message": "STRANDS_SWARM_OK"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "swarm-finisher",
+ "strands.agent.name": "swarm-finisher",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "toolUse"
+ },
+ "metrics": {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "tokens": 212
+ }
+ }
+ ],
+ "output": {
+ "message": "STRANDS_SWARM_OK"
+ },
+ "metadata": {
+ "provider": "strands",
+ "strands.node.id": "swarm-finisher",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ },
+ "metrics": {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "strands.node.duration_ms": "",
+ "tokens": 212
+ }
+ }
+ ],
+ "input": "Route to the finisher, then finish exactly STRANDS_SWARM_OK.",
+ "output": "STRANDS_SWARM_OK",
+ "metadata": {
+ "provider": "strands",
+ "strands.handoffs": [
+ {
+ "source": "swarm-router",
+ "targets": [
+ "swarm-finisher"
+ ]
+ }
+ ],
+ "strands.operation": "Swarm.stream",
+ "strands.orchestrator.id": "weather-swarm",
+ "strands.status": "COMPLETED"
+ },
+ "metrics": {
+ "completion_tokens": 52,
+ "duration": "",
+ "prompt_tokens": 389,
+ "tokens": 441
+ }
+ }
+ ],
+ "metadata": {
+ "operation": "swarm-invoke"
+ }
+ }
+ ],
+ "metadata": {
+ "scenario": "strands-agent-sdk-instrumentation"
+ }
+ }
+ ]
+}
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-auto-hook.span-tree.txt b/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-auto-hook.span-tree.txt
new file mode 100644
index 000000000..e07b7d0a6
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-auto-hook.span-tree.txt
@@ -0,0 +1,432 @@
+span_tree:
+└── strands-agent-sdk-instrumentation-root [task]
+ metadata: {
+ "scenario": "strands-agent-sdk-instrumentation"
+ }
+ ├── strands-agent-invoke-operation
+ │ metadata: {
+ │ "operation": "agent-invoke"
+ │ }
+ │ └── Agent: weather-agent [task]
+ │ input: "Use lookup_weather for Vienna, then answer exactly STRANDS_AGENT_TOOL_OK."
+ │ output: {
+ │ "content": [
+ │ {
+ │ "text": "STRANDS_AGENT_TOOL_OK"
+ │ }
+ │ ],
+ │ "metadata": {
+ │ "usage": {
+ │ "inputTokens": 127,
+ │ "outputTokens": 6,
+ │ "totalTokens": 133
+ │ }
+ │ },
+ │ "role": "assistant"
+ │ }
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.agent.id": "weather-agent",
+ │ "strands.agent.name": "weather-agent",
+ │ "strands.operation": "Agent.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 21,
+ │ "duration": "",
+ │ "prompt_tokens": 213,
+ │ "tokens": 234
+ │ }
+ │ ├── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ │ metadata: {
+ │ │ "model": "gpt-4o-mini-2024-07-18",
+ │ │ "provider": "openai",
+ │ │ "strands.operation": "model.stream",
+ │ │ "strands.stop_reason": "toolUse"
+ │ │ }
+ │ │ metrics: {
+ │ │ "completion_tokens": 15,
+ │ │ "duration": "",
+ │ │ "prompt_tokens": 86,
+ │ │ "tokens": 101
+ │ │ }
+ │ ├── tool: lookup_weather [tool]
+ │ │ input: {
+ │ │ "city": "Vienna"
+ │ │ }
+ │ │ output: {
+ │ │ "toolResult": {
+ │ │ "content": [
+ │ │ {
+ │ │ "json": {
+ │ │ "city": "Vienna",
+ │ │ "forecast": "sunny",
+ │ │ "marker": "STRANDS_TOOL_OK"
+ │ │ }
+ │ │ }
+ │ │ ],
+ │ │ "status": "success",
+ │ │ "toolUseId": ""
+ │ │ }
+ │ │ }
+ │ │ metadata: {
+ │ │ "gen_ai.tool.name": "lookup_weather",
+ │ │ "provider": "strands",
+ │ │ "strands.operation": "tool.call",
+ │ │ "strands.tool.name": "lookup_weather"
+ │ │ }
+ │ │ metrics: {
+ │ │ "duration": ""
+ │ │ }
+ │ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.operation": "model.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 6,
+ │ "duration": "",
+ │ "prompt_tokens": 127,
+ │ "tokens": 133
+ │ }
+ ├── strands-agent-stream-operation
+ │ metadata: {
+ │ "operation": "agent-stream"
+ │ }
+ │ └── Agent: stream-agent [task]
+ │ input: "Reply exactly STRANDS_AGENT_STREAM_OK."
+ │ output: {
+ │ "content": [
+ │ {
+ │ "text": "STRANDS_AGENT_STREAM_OK."
+ │ }
+ │ ],
+ │ "metadata": {
+ │ "usage": {
+ │ "inputTokens": 27,
+ │ "outputTokens": 6,
+ │ "totalTokens": 33
+ │ }
+ │ },
+ │ "role": "assistant"
+ │ }
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.agent.id": "stream-agent",
+ │ "strands.agent.name": "stream-agent",
+ │ "strands.operation": "Agent.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 6,
+ │ "duration": "",
+ │ "prompt_tokens": 27,
+ │ "tokens": 33
+ │ }
+ │ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.operation": "model.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 6,
+ │ "duration": "",
+ │ "prompt_tokens": 27,
+ │ "tokens": 33
+ │ }
+ ├── strands-graph-invoke-operation
+ │ metadata: {
+ │ "operation": "graph-invoke"
+ │ }
+ │ └── Strands Graph [task]
+ │ input: "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK."
+ │ output: "STRANDS_GRAPH_OK"
+ │ metadata: {
+ │ "provider": "strands",
+ │ "strands.handoffs": [
+ │ {
+ │ "source": "graph-researcher",
+ │ "targets": [
+ │ "graph-writer"
+ │ ]
+ │ }
+ │ ],
+ │ "strands.operation": "Graph.stream",
+ │ "strands.orchestrator.id": "weather-graph",
+ │ "strands.status": "COMPLETED"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 8,
+ │ "duration": "",
+ │ "prompt_tokens": 81,
+ │ "tokens": 89
+ │ }
+ │ ├── node: graph-researcher [task]
+ │ │ output: "GRAPH_RESEARCH_DONE"
+ │ │ metadata: {
+ │ │ "provider": "strands",
+ │ │ "strands.node.id": "graph-researcher",
+ │ │ "strands.node.status": "COMPLETED",
+ │ │ "strands.node.type": "agentNode",
+ │ │ "strands.operation": "node.call"
+ │ │ }
+ │ │ metrics: {
+ │ │ "completion_tokens": 4,
+ │ │ "duration": "",
+ │ │ "prompt_tokens": 35,
+ │ │ "strands.node.duration_ms": "",
+ │ │ "tokens": 39
+ │ │ }
+ │ │ └── Agent: graph-researcher [task]
+ │ │ input: "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK."
+ │ │ output: {
+ │ │ "content": [
+ │ │ {
+ │ │ "text": "GRAPH_RESEARCH_DONE"
+ │ │ }
+ │ │ ],
+ │ │ "metadata": {
+ │ │ "usage": {
+ │ │ "inputTokens": 35,
+ │ │ "outputTokens": 4,
+ │ │ "totalTokens": 39
+ │ │ }
+ │ │ },
+ │ │ "role": "assistant"
+ │ │ }
+ │ │ metadata: {
+ │ │ "model": "gpt-4o-mini-2024-07-18",
+ │ │ "provider": "openai",
+ │ │ "strands.agent.id": "graph-researcher",
+ │ │ "strands.agent.name": "graph-researcher",
+ │ │ "strands.operation": "Agent.stream",
+ │ │ "strands.stop_reason": "endTurn"
+ │ │ }
+ │ │ metrics: {
+ │ │ "completion_tokens": 4,
+ │ │ "duration": "",
+ │ │ "prompt_tokens": 35,
+ │ │ "tokens": 39
+ │ │ }
+ │ │ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ │ metadata: {
+ │ │ "model": "gpt-4o-mini-2024-07-18",
+ │ │ "provider": "openai",
+ │ │ "strands.operation": "model.stream",
+ │ │ "strands.stop_reason": "endTurn"
+ │ │ }
+ │ │ metrics: {
+ │ │ "completion_tokens": 4,
+ │ │ "duration": "",
+ │ │ "prompt_tokens": 35,
+ │ │ "tokens": 39
+ │ │ }
+ │ └── node: graph-writer [task]
+ │ output: "STRANDS_GRAPH_OK"
+ │ metadata: {
+ │ "provider": "strands",
+ │ "strands.node.id": "graph-writer",
+ │ "strands.node.status": "COMPLETED",
+ │ "strands.node.type": "agentNode",
+ │ "strands.operation": "node.call"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 4,
+ │ "duration": "",
+ │ "prompt_tokens": 46,
+ │ "strands.node.duration_ms": "",
+ │ "tokens": 50
+ │ }
+ │ └── Agent: graph-writer [task]
+ │ input: [
+ │ {
+ │ "text": "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK."
+ │ },
+ │ {
+ │ "text": "[node: graph-researcher]"
+ │ },
+ │ {
+ │ "text": "GRAPH_RESEARCH_DONE"
+ │ }
+ │ ]
+ │ output: {
+ │ "content": [
+ │ {
+ │ "text": "STRANDS_GRAPH_OK"
+ │ }
+ │ ],
+ │ "metadata": {
+ │ "usage": {
+ │ "inputTokens": 46,
+ │ "outputTokens": 4,
+ │ "totalTokens": 50
+ │ }
+ │ },
+ │ "role": "assistant"
+ │ }
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.agent.id": "graph-writer",
+ │ "strands.agent.name": "graph-writer",
+ │ "strands.operation": "Agent.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 4,
+ │ "duration": "",
+ │ "prompt_tokens": 46,
+ │ "tokens": 50
+ │ }
+ │ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.operation": "model.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 4,
+ │ "duration": "",
+ │ "prompt_tokens": 46,
+ │ "tokens": 50
+ │ }
+ └── strands-swarm-invoke-operation
+ metadata: {
+ "operation": "swarm-invoke"
+ }
+ └── Strands Swarm [task]
+ input: "Route to the finisher, then finish exactly STRANDS_SWARM_OK."
+ output: "STRANDS_SWARM_OK"
+ metadata: {
+ "provider": "strands",
+ "strands.handoffs": [
+ {
+ "source": "swarm-router",
+ "targets": [
+ "swarm-finisher"
+ ]
+ }
+ ],
+ "strands.operation": "Swarm.stream",
+ "strands.orchestrator.id": "weather-swarm",
+ "strands.status": "COMPLETED"
+ }
+ metrics: {
+ "completion_tokens": 52,
+ "duration": "",
+ "prompt_tokens": 389,
+ "tokens": 441
+ }
+ ├── node: swarm-router [task]
+ │ output: {
+ │ "agentId": "",
+ │ "message": "finish with STRANDS_SWARM_OK"
+ │ }
+ │ metadata: {
+ │ "provider": "strands",
+ │ "strands.node.id": "swarm-router",
+ │ "strands.node.status": "COMPLETED",
+ │ "strands.node.type": "agentNode",
+ │ "strands.operation": "node.call"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 31,
+ │ "duration": "",
+ │ "prompt_tokens": 198,
+ │ "strands.node.duration_ms": "",
+ │ "tokens": 229
+ │ }
+ │ └── Agent: swarm-router [task]
+ │ input: "Route to the finisher, then finish exactly STRANDS_SWARM_OK."
+ │ output: {
+ │ "agentId": "",
+ │ "message": "finish with STRANDS_SWARM_OK"
+ │ }
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.agent.id": "swarm-router",
+ │ "strands.agent.name": "swarm-router",
+ │ "strands.operation": "Agent.stream",
+ │ "strands.stop_reason": "toolUse"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 31,
+ │ "duration": "",
+ │ "prompt_tokens": 198,
+ │ "tokens": 229
+ │ }
+ │ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.operation": "model.stream",
+ │ "strands.stop_reason": "toolUse"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 31,
+ │ "duration": "",
+ │ "prompt_tokens": 198,
+ │ "tokens": 229
+ │ }
+ └── node: swarm-finisher [task]
+ output: {
+ "message": "STRANDS_SWARM_OK"
+ }
+ metadata: {
+ "provider": "strands",
+ "strands.node.id": "swarm-finisher",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ }
+ metrics: {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "strands.node.duration_ms": "",
+ "tokens": 212
+ }
+ └── Agent: swarm-finisher [task]
+ input: [
+ {
+ "text": "finish with STRANDS_SWARM_OK"
+ }
+ ]
+ output: {
+ "message": "STRANDS_SWARM_OK"
+ }
+ metadata: {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "swarm-finisher",
+ "strands.agent.name": "swarm-finisher",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "toolUse"
+ }
+ metrics: {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "tokens": 212
+ }
+ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ metadata: {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "toolUse"
+ }
+ metrics: {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "tokens": 212
+ }
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-wrapped.span-tree.json b/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-wrapped.span-tree.json
new file mode 100644
index 000000000..ef147ae2b
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-wrapped.span-tree.json
@@ -0,0 +1,634 @@
+{
+ "span_tree": [
+ {
+ "name": "strands-agent-sdk-instrumentation-root",
+ "type": "task",
+ "children": [
+ {
+ "name": "strands-agent-invoke-operation",
+ "children": [
+ {
+ "name": "Agent: weather-agent",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "toolUse"
+ },
+ "metrics": {
+ "completion_tokens": 15,
+ "duration": "",
+ "prompt_tokens": 86,
+ "tokens": 101
+ }
+ },
+ {
+ "name": "tool: lookup_weather",
+ "type": "tool",
+ "children": [],
+ "input": {
+ "city": "Vienna"
+ },
+ "output": {
+ "toolResult": {
+ "content": [
+ {
+ "json": {
+ "city": "Vienna",
+ "forecast": "sunny",
+ "marker": "STRANDS_TOOL_OK"
+ }
+ }
+ ],
+ "status": "success",
+ "toolUseId": ""
+ }
+ },
+ "metadata": {
+ "gen_ai.tool.name": "lookup_weather",
+ "provider": "strands",
+ "strands.operation": "tool.call",
+ "strands.tool.name": "lookup_weather"
+ },
+ "metrics": {
+ "duration": ""
+ }
+ },
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 6,
+ "duration": "",
+ "prompt_tokens": 127,
+ "tokens": 133
+ }
+ }
+ ],
+ "input": "Use lookup_weather for Vienna, then answer exactly STRANDS_AGENT_TOOL_OK.",
+ "output": {
+ "content": [
+ {
+ "text": "STRANDS_AGENT_TOOL_OK"
+ }
+ ],
+ "metadata": {
+ "usage": {
+ "inputTokens": 127,
+ "outputTokens": 6,
+ "totalTokens": 133
+ }
+ },
+ "role": "assistant"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "weather-agent",
+ "strands.agent.name": "weather-agent",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 213,
+ "tokens": 234
+ }
+ }
+ ],
+ "metadata": {
+ "operation": "agent-invoke"
+ }
+ },
+ {
+ "name": "strands-agent-stream-operation",
+ "children": [
+ {
+ "name": "Agent: stream-agent",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 6,
+ "duration": "",
+ "prompt_tokens": 27,
+ "tokens": 33
+ }
+ }
+ ],
+ "input": "Reply exactly STRANDS_AGENT_STREAM_OK.",
+ "output": {
+ "content": [
+ {
+ "text": "STRANDS_AGENT_STREAM_OK."
+ }
+ ],
+ "metadata": {
+ "usage": {
+ "inputTokens": 27,
+ "outputTokens": 6,
+ "totalTokens": 33
+ }
+ },
+ "role": "assistant"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "stream-agent",
+ "strands.agent.name": "stream-agent",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 6,
+ "duration": "",
+ "prompt_tokens": 27,
+ "tokens": 33
+ }
+ }
+ ],
+ "metadata": {
+ "operation": "agent-stream"
+ }
+ },
+ {
+ "name": "strands-graph-invoke-operation",
+ "children": [
+ {
+ "name": "Strands Graph",
+ "type": "task",
+ "children": [
+ {
+ "name": "node: graph-researcher",
+ "type": "task",
+ "children": [
+ {
+ "name": "Agent: graph-researcher",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 35,
+ "tokens": 39
+ }
+ }
+ ],
+ "input": "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK.",
+ "output": {
+ "content": [
+ {
+ "text": "GRAPH_RESEARCH_DONE"
+ }
+ ],
+ "metadata": {
+ "usage": {
+ "inputTokens": 35,
+ "outputTokens": 4,
+ "totalTokens": 39
+ }
+ },
+ "role": "assistant"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "graph-researcher",
+ "strands.agent.name": "graph-researcher",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 35,
+ "tokens": 39
+ }
+ }
+ ],
+ "output": "GRAPH_RESEARCH_DONE",
+ "metadata": {
+ "provider": "strands",
+ "strands.node.id": "graph-researcher",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 35,
+ "strands.node.duration_ms": "",
+ "tokens": 39
+ }
+ },
+ {
+ "name": "node: graph-writer",
+ "type": "task",
+ "children": [
+ {
+ "name": "Agent: graph-writer",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 46,
+ "tokens": 50
+ }
+ }
+ ],
+ "input": [
+ {
+ "text": "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK."
+ },
+ {
+ "text": "[node: graph-researcher]"
+ },
+ {
+ "text": "GRAPH_RESEARCH_DONE"
+ }
+ ],
+ "output": {
+ "content": [
+ {
+ "text": "STRANDS_GRAPH_OK"
+ }
+ ],
+ "metadata": {
+ "usage": {
+ "inputTokens": 46,
+ "outputTokens": 4,
+ "totalTokens": 50
+ }
+ },
+ "role": "assistant"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "graph-writer",
+ "strands.agent.name": "graph-writer",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 46,
+ "tokens": 50
+ }
+ }
+ ],
+ "output": "STRANDS_GRAPH_OK",
+ "metadata": {
+ "provider": "strands",
+ "strands.node.id": "graph-writer",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ },
+ "metrics": {
+ "completion_tokens": 4,
+ "duration": "",
+ "prompt_tokens": 46,
+ "strands.node.duration_ms": "",
+ "tokens": 50
+ }
+ }
+ ],
+ "input": "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK.",
+ "output": "STRANDS_GRAPH_OK",
+ "metadata": {
+ "provider": "strands",
+ "strands.handoffs": [
+ {
+ "source": "graph-researcher",
+ "targets": [
+ "graph-writer"
+ ]
+ }
+ ],
+ "strands.operation": "Graph.stream",
+ "strands.orchestrator.id": "weather-graph",
+ "strands.status": "COMPLETED"
+ },
+ "metrics": {
+ "completion_tokens": 8,
+ "duration": "",
+ "prompt_tokens": 81,
+ "tokens": 89
+ }
+ }
+ ],
+ "metadata": {
+ "operation": "graph-invoke"
+ }
+ },
+ {
+ "name": "strands-overlap-parent-operation",
+ "children": [
+ {
+ "name": "Strands Graph",
+ "type": "task",
+ "children": [
+ {
+ "name": "node: overlap-node-a",
+ "type": "task",
+ "children": [],
+ "output": "OVERLAP_NODE_A_OK",
+ "metadata": {
+ "provider": "strands",
+ "strands.node.id": "overlap-node-a",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ },
+ "metrics": {
+ "duration": "",
+ "strands.node.duration_ms": ""
+ }
+ },
+ {
+ "name": "node: overlap-node-b",
+ "type": "task",
+ "children": [],
+ "output": "OVERLAP_NODE_B_OK",
+ "metadata": {
+ "provider": "strands",
+ "strands.node.id": "overlap-node-b",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ },
+ "metrics": {
+ "duration": "",
+ "strands.node.duration_ms": ""
+ }
+ }
+ ],
+ "input": "overlap graph input",
+ "output": "OVERLAP_GRAPH_OK",
+ "metadata": {
+ "provider": "strands",
+ "strands.operation": "Graph.stream",
+ "strands.orchestrator.id": "overlap-graph",
+ "strands.status": "COMPLETED"
+ },
+ "metrics": {
+ "duration": ""
+ }
+ },
+ {
+ "name": "Agent: overlap-agent",
+ "type": "task",
+ "children": [],
+ "input": "overlap-node-a work",
+ "output": {
+ "content": [
+ {
+ "text": "OVERLAP_AGENT_OK overlap-node-a work"
+ }
+ ],
+ "role": "assistant"
+ },
+ "metadata": {
+ "provider": "strands",
+ "strands.agent.id": "overlap-agent",
+ "strands.agent.name": "overlap-agent",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "endTurn"
+ },
+ "metrics": {
+ "completion_tokens": 1,
+ "duration": "",
+ "prompt_tokens": 1,
+ "tokens": 2
+ }
+ }
+ ],
+ "metadata": {
+ "operation": "overlap-parent"
+ }
+ },
+ {
+ "name": "strands-swarm-invoke-operation",
+ "children": [
+ {
+ "name": "Strands Swarm",
+ "type": "task",
+ "children": [
+ {
+ "name": "node: swarm-router",
+ "type": "task",
+ "children": [
+ {
+ "name": "Agent: swarm-router",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "toolUse"
+ },
+ "metrics": {
+ "completion_tokens": 31,
+ "duration": "",
+ "prompt_tokens": 198,
+ "tokens": 229
+ }
+ }
+ ],
+ "input": "Route to the finisher, then finish exactly STRANDS_SWARM_OK.",
+ "output": {
+ "agentId": "",
+ "message": "finish with STRANDS_SWARM_OK"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "swarm-router",
+ "strands.agent.name": "swarm-router",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "toolUse"
+ },
+ "metrics": {
+ "completion_tokens": 31,
+ "duration": "",
+ "prompt_tokens": 198,
+ "tokens": 229
+ }
+ }
+ ],
+ "output": {
+ "agentId": "",
+ "message": "finish with STRANDS_SWARM_OK"
+ },
+ "metadata": {
+ "provider": "strands",
+ "strands.node.id": "swarm-router",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ },
+ "metrics": {
+ "completion_tokens": 31,
+ "duration": "",
+ "prompt_tokens": 198,
+ "strands.node.duration_ms": "",
+ "tokens": 229
+ }
+ },
+ {
+ "name": "node: swarm-finisher",
+ "type": "task",
+ "children": [
+ {
+ "name": "Agent: swarm-finisher",
+ "type": "task",
+ "children": [
+ {
+ "name": "Strands model: gpt-4o-mini-2024-07-18",
+ "type": "llm",
+ "children": [],
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "toolUse"
+ },
+ "metrics": {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "tokens": 212
+ }
+ }
+ ],
+ "input": [
+ {
+ "text": "finish with STRANDS_SWARM_OK"
+ }
+ ],
+ "output": {
+ "message": "STRANDS_SWARM_OK"
+ },
+ "metadata": {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "swarm-finisher",
+ "strands.agent.name": "swarm-finisher",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "toolUse"
+ },
+ "metrics": {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "tokens": 212
+ }
+ }
+ ],
+ "output": {
+ "message": "STRANDS_SWARM_OK"
+ },
+ "metadata": {
+ "provider": "strands",
+ "strands.node.id": "swarm-finisher",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ },
+ "metrics": {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "strands.node.duration_ms": "",
+ "tokens": 212
+ }
+ }
+ ],
+ "input": "Route to the finisher, then finish exactly STRANDS_SWARM_OK.",
+ "output": "STRANDS_SWARM_OK",
+ "metadata": {
+ "provider": "strands",
+ "strands.handoffs": [
+ {
+ "source": "swarm-router",
+ "targets": [
+ "swarm-finisher"
+ ]
+ }
+ ],
+ "strands.operation": "Swarm.stream",
+ "strands.orchestrator.id": "weather-swarm",
+ "strands.status": "COMPLETED"
+ },
+ "metrics": {
+ "completion_tokens": 52,
+ "duration": "",
+ "prompt_tokens": 389,
+ "tokens": 441
+ }
+ }
+ ],
+ "metadata": {
+ "operation": "swarm-invoke"
+ }
+ }
+ ],
+ "metadata": {
+ "scenario": "strands-agent-sdk-instrumentation"
+ }
+ }
+ ]
+}
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-wrapped.span-tree.txt b/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-wrapped.span-tree.txt
new file mode 100644
index 000000000..998ea28c2
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/__snapshots__/strands-agent-sdk-v160-wrapped.span-tree.txt
@@ -0,0 +1,497 @@
+span_tree:
+└── strands-agent-sdk-instrumentation-root [task]
+ metadata: {
+ "scenario": "strands-agent-sdk-instrumentation"
+ }
+ ├── strands-agent-invoke-operation
+ │ metadata: {
+ │ "operation": "agent-invoke"
+ │ }
+ │ └── Agent: weather-agent [task]
+ │ input: "Use lookup_weather for Vienna, then answer exactly STRANDS_AGENT_TOOL_OK."
+ │ output: {
+ │ "content": [
+ │ {
+ │ "text": "STRANDS_AGENT_TOOL_OK"
+ │ }
+ │ ],
+ │ "metadata": {
+ │ "usage": {
+ │ "inputTokens": 127,
+ │ "outputTokens": 6,
+ │ "totalTokens": 133
+ │ }
+ │ },
+ │ "role": "assistant"
+ │ }
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.agent.id": "weather-agent",
+ │ "strands.agent.name": "weather-agent",
+ │ "strands.operation": "Agent.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 21,
+ │ "duration": "",
+ │ "prompt_tokens": 213,
+ │ "tokens": 234
+ │ }
+ │ ├── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ │ metadata: {
+ │ │ "model": "gpt-4o-mini-2024-07-18",
+ │ │ "provider": "openai",
+ │ │ "strands.operation": "model.stream",
+ │ │ "strands.stop_reason": "toolUse"
+ │ │ }
+ │ │ metrics: {
+ │ │ "completion_tokens": 15,
+ │ │ "duration": "",
+ │ │ "prompt_tokens": 86,
+ │ │ "tokens": 101
+ │ │ }
+ │ ├── tool: lookup_weather [tool]
+ │ │ input: {
+ │ │ "city": "Vienna"
+ │ │ }
+ │ │ output: {
+ │ │ "toolResult": {
+ │ │ "content": [
+ │ │ {
+ │ │ "json": {
+ │ │ "city": "Vienna",
+ │ │ "forecast": "sunny",
+ │ │ "marker": "STRANDS_TOOL_OK"
+ │ │ }
+ │ │ }
+ │ │ ],
+ │ │ "status": "success",
+ │ │ "toolUseId": ""
+ │ │ }
+ │ │ }
+ │ │ metadata: {
+ │ │ "gen_ai.tool.name": "lookup_weather",
+ │ │ "provider": "strands",
+ │ │ "strands.operation": "tool.call",
+ │ │ "strands.tool.name": "lookup_weather"
+ │ │ }
+ │ │ metrics: {
+ │ │ "duration": ""
+ │ │ }
+ │ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.operation": "model.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 6,
+ │ "duration": "",
+ │ "prompt_tokens": 127,
+ │ "tokens": 133
+ │ }
+ ├── strands-agent-stream-operation
+ │ metadata: {
+ │ "operation": "agent-stream"
+ │ }
+ │ └── Agent: stream-agent [task]
+ │ input: "Reply exactly STRANDS_AGENT_STREAM_OK."
+ │ output: {
+ │ "content": [
+ │ {
+ │ "text": "STRANDS_AGENT_STREAM_OK."
+ │ }
+ │ ],
+ │ "metadata": {
+ │ "usage": {
+ │ "inputTokens": 27,
+ │ "outputTokens": 6,
+ │ "totalTokens": 33
+ │ }
+ │ },
+ │ "role": "assistant"
+ │ }
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.agent.id": "stream-agent",
+ │ "strands.agent.name": "stream-agent",
+ │ "strands.operation": "Agent.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 6,
+ │ "duration": "",
+ │ "prompt_tokens": 27,
+ │ "tokens": 33
+ │ }
+ │ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.operation": "model.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 6,
+ │ "duration": "",
+ │ "prompt_tokens": 27,
+ │ "tokens": 33
+ │ }
+ ├── strands-graph-invoke-operation
+ │ metadata: {
+ │ "operation": "graph-invoke"
+ │ }
+ │ └── Strands Graph [task]
+ │ input: "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK."
+ │ output: "STRANDS_GRAPH_OK"
+ │ metadata: {
+ │ "provider": "strands",
+ │ "strands.handoffs": [
+ │ {
+ │ "source": "graph-researcher",
+ │ "targets": [
+ │ "graph-writer"
+ │ ]
+ │ }
+ │ ],
+ │ "strands.operation": "Graph.stream",
+ │ "strands.orchestrator.id": "weather-graph",
+ │ "strands.status": "COMPLETED"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 8,
+ │ "duration": "",
+ │ "prompt_tokens": 81,
+ │ "tokens": 89
+ │ }
+ │ ├── node: graph-researcher [task]
+ │ │ output: "GRAPH_RESEARCH_DONE"
+ │ │ metadata: {
+ │ │ "provider": "strands",
+ │ │ "strands.node.id": "graph-researcher",
+ │ │ "strands.node.status": "COMPLETED",
+ │ │ "strands.node.type": "agentNode",
+ │ │ "strands.operation": "node.call"
+ │ │ }
+ │ │ metrics: {
+ │ │ "completion_tokens": 4,
+ │ │ "duration": "",
+ │ │ "prompt_tokens": 35,
+ │ │ "strands.node.duration_ms": "",
+ │ │ "tokens": 39
+ │ │ }
+ │ │ └── Agent: graph-researcher [task]
+ │ │ input: "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK."
+ │ │ output: {
+ │ │ "content": [
+ │ │ {
+ │ │ "text": "GRAPH_RESEARCH_DONE"
+ │ │ }
+ │ │ ],
+ │ │ "metadata": {
+ │ │ "usage": {
+ │ │ "inputTokens": 35,
+ │ │ "outputTokens": 4,
+ │ │ "totalTokens": 39
+ │ │ }
+ │ │ },
+ │ │ "role": "assistant"
+ │ │ }
+ │ │ metadata: {
+ │ │ "model": "gpt-4o-mini-2024-07-18",
+ │ │ "provider": "openai",
+ │ │ "strands.agent.id": "graph-researcher",
+ │ │ "strands.agent.name": "graph-researcher",
+ │ │ "strands.operation": "Agent.stream",
+ │ │ "strands.stop_reason": "endTurn"
+ │ │ }
+ │ │ metrics: {
+ │ │ "completion_tokens": 4,
+ │ │ "duration": "",
+ │ │ "prompt_tokens": 35,
+ │ │ "tokens": 39
+ │ │ }
+ │ │ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ │ metadata: {
+ │ │ "model": "gpt-4o-mini-2024-07-18",
+ │ │ "provider": "openai",
+ │ │ "strands.operation": "model.stream",
+ │ │ "strands.stop_reason": "endTurn"
+ │ │ }
+ │ │ metrics: {
+ │ │ "completion_tokens": 4,
+ │ │ "duration": "",
+ │ │ "prompt_tokens": 35,
+ │ │ "tokens": 39
+ │ │ }
+ │ └── node: graph-writer [task]
+ │ output: "STRANDS_GRAPH_OK"
+ │ metadata: {
+ │ "provider": "strands",
+ │ "strands.node.id": "graph-writer",
+ │ "strands.node.status": "COMPLETED",
+ │ "strands.node.type": "agentNode",
+ │ "strands.operation": "node.call"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 4,
+ │ "duration": "",
+ │ "prompt_tokens": 46,
+ │ "strands.node.duration_ms": "",
+ │ "tokens": 50
+ │ }
+ │ └── Agent: graph-writer [task]
+ │ input: [
+ │ {
+ │ "text": "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK."
+ │ },
+ │ {
+ │ "text": "[node: graph-researcher]"
+ │ },
+ │ {
+ │ "text": "GRAPH_RESEARCH_DONE"
+ │ }
+ │ ]
+ │ output: {
+ │ "content": [
+ │ {
+ │ "text": "STRANDS_GRAPH_OK"
+ │ }
+ │ ],
+ │ "metadata": {
+ │ "usage": {
+ │ "inputTokens": 46,
+ │ "outputTokens": 4,
+ │ "totalTokens": 50
+ │ }
+ │ },
+ │ "role": "assistant"
+ │ }
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.agent.id": "graph-writer",
+ │ "strands.agent.name": "graph-writer",
+ │ "strands.operation": "Agent.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 4,
+ │ "duration": "",
+ │ "prompt_tokens": 46,
+ │ "tokens": 50
+ │ }
+ │ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.operation": "model.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 4,
+ │ "duration": "",
+ │ "prompt_tokens": 46,
+ │ "tokens": 50
+ │ }
+ ├── strands-overlap-parent-operation
+ │ metadata: {
+ │ "operation": "overlap-parent"
+ │ }
+ │ ├── Strands Graph [task]
+ │ │ input: "overlap graph input"
+ │ │ output: "OVERLAP_GRAPH_OK"
+ │ │ metadata: {
+ │ │ "provider": "strands",
+ │ │ "strands.operation": "Graph.stream",
+ │ │ "strands.orchestrator.id": "overlap-graph",
+ │ │ "strands.status": "COMPLETED"
+ │ │ }
+ │ │ metrics: {
+ │ │ "duration": ""
+ │ │ }
+ │ │ ├── node: overlap-node-a [task]
+ │ │ │ output: "OVERLAP_NODE_A_OK"
+ │ │ │ metadata: {
+ │ │ │ "provider": "strands",
+ │ │ │ "strands.node.id": "overlap-node-a",
+ │ │ │ "strands.node.status": "COMPLETED",
+ │ │ │ "strands.node.type": "agentNode",
+ │ │ │ "strands.operation": "node.call"
+ │ │ │ }
+ │ │ │ metrics: {
+ │ │ │ "duration": "",
+ │ │ │ "strands.node.duration_ms": ""
+ │ │ │ }
+ │ │ └── node: overlap-node-b [task]
+ │ │ output: "OVERLAP_NODE_B_OK"
+ │ │ metadata: {
+ │ │ "provider": "strands",
+ │ │ "strands.node.id": "overlap-node-b",
+ │ │ "strands.node.status": "COMPLETED",
+ │ │ "strands.node.type": "agentNode",
+ │ │ "strands.operation": "node.call"
+ │ │ }
+ │ │ metrics: {
+ │ │ "duration": "",
+ │ │ "strands.node.duration_ms": ""
+ │ │ }
+ │ └── Agent: overlap-agent [task]
+ │ input: "overlap-node-a work"
+ │ output: {
+ │ "content": [
+ │ {
+ │ "text": "OVERLAP_AGENT_OK overlap-node-a work"
+ │ }
+ │ ],
+ │ "role": "assistant"
+ │ }
+ │ metadata: {
+ │ "provider": "strands",
+ │ "strands.agent.id": "overlap-agent",
+ │ "strands.agent.name": "overlap-agent",
+ │ "strands.operation": "Agent.stream",
+ │ "strands.stop_reason": "endTurn"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 1,
+ │ "duration": "",
+ │ "prompt_tokens": 1,
+ │ "tokens": 2
+ │ }
+ └── strands-swarm-invoke-operation
+ metadata: {
+ "operation": "swarm-invoke"
+ }
+ └── Strands Swarm [task]
+ input: "Route to the finisher, then finish exactly STRANDS_SWARM_OK."
+ output: "STRANDS_SWARM_OK"
+ metadata: {
+ "provider": "strands",
+ "strands.handoffs": [
+ {
+ "source": "swarm-router",
+ "targets": [
+ "swarm-finisher"
+ ]
+ }
+ ],
+ "strands.operation": "Swarm.stream",
+ "strands.orchestrator.id": "weather-swarm",
+ "strands.status": "COMPLETED"
+ }
+ metrics: {
+ "completion_tokens": 52,
+ "duration": "",
+ "prompt_tokens": 389,
+ "tokens": 441
+ }
+ ├── node: swarm-router [task]
+ │ output: {
+ │ "agentId": "",
+ │ "message": "finish with STRANDS_SWARM_OK"
+ │ }
+ │ metadata: {
+ │ "provider": "strands",
+ │ "strands.node.id": "swarm-router",
+ │ "strands.node.status": "COMPLETED",
+ │ "strands.node.type": "agentNode",
+ │ "strands.operation": "node.call"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 31,
+ │ "duration": "",
+ │ "prompt_tokens": 198,
+ │ "strands.node.duration_ms": "",
+ │ "tokens": 229
+ │ }
+ │ └── Agent: swarm-router [task]
+ │ input: "Route to the finisher, then finish exactly STRANDS_SWARM_OK."
+ │ output: {
+ │ "agentId": "",
+ │ "message": "finish with STRANDS_SWARM_OK"
+ │ }
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.agent.id": "swarm-router",
+ │ "strands.agent.name": "swarm-router",
+ │ "strands.operation": "Agent.stream",
+ │ "strands.stop_reason": "toolUse"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 31,
+ │ "duration": "",
+ │ "prompt_tokens": 198,
+ │ "tokens": 229
+ │ }
+ │ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ │ metadata: {
+ │ "model": "gpt-4o-mini-2024-07-18",
+ │ "provider": "openai",
+ │ "strands.operation": "model.stream",
+ │ "strands.stop_reason": "toolUse"
+ │ }
+ │ metrics: {
+ │ "completion_tokens": 31,
+ │ "duration": "",
+ │ "prompt_tokens": 198,
+ │ "tokens": 229
+ │ }
+ └── node: swarm-finisher [task]
+ output: {
+ "message": "STRANDS_SWARM_OK"
+ }
+ metadata: {
+ "provider": "strands",
+ "strands.node.id": "swarm-finisher",
+ "strands.node.status": "COMPLETED",
+ "strands.node.type": "agentNode",
+ "strands.operation": "node.call"
+ }
+ metrics: {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "strands.node.duration_ms": "",
+ "tokens": 212
+ }
+ └── Agent: swarm-finisher [task]
+ input: [
+ {
+ "text": "finish with STRANDS_SWARM_OK"
+ }
+ ]
+ output: {
+ "message": "STRANDS_SWARM_OK"
+ }
+ metadata: {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.agent.id": "swarm-finisher",
+ "strands.agent.name": "swarm-finisher",
+ "strands.operation": "Agent.stream",
+ "strands.stop_reason": "toolUse"
+ }
+ metrics: {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "tokens": 212
+ }
+ └── Strands model: gpt-4o-mini-2024-07-18 [llm]
+ metadata: {
+ "model": "gpt-4o-mini-2024-07-18",
+ "provider": "openai",
+ "strands.operation": "model.stream",
+ "strands.stop_reason": "toolUse"
+ }
+ metrics: {
+ "completion_tokens": 21,
+ "duration": "",
+ "prompt_tokens": 191,
+ "tokens": 212
+ }
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/assertions.ts b/e2e/scenarios/strands-agent-sdk-instrumentation/assertions.ts
new file mode 100644
index 000000000..16f4bed37
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/assertions.ts
@@ -0,0 +1,496 @@
+import { beforeAll, describe, expect, test } from "vitest";
+import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server";
+import type { Json } from "../../helpers/normalize";
+import { resolveFileSnapshotPath } from "../../helpers/file-snapshot";
+import {
+ effectiveScenarioTimeoutMs,
+ withScenarioHarness,
+ type ScenarioRunContext,
+} from "../../helpers/scenario-harness";
+import {
+ matchSpanTreeSnapshot,
+ spanTreeFields,
+ type SpanTreeEntry,
+} from "../../helpers/span-tree";
+import { findChildSpans, findLatestSpan } from "../../helpers/trace-selectors";
+import { MODEL_NAME, ROOT_NAME, SCENARIO_NAME } from "./scenario.impl.mjs";
+
+type RunStrandsAgentSDKScenario = (harness: {
+ runNodeScenarioDir: (options: {
+ entry: string;
+ nodeArgs: string[];
+ runContext?: ScenarioRunContext;
+ scenarioDir: string;
+ timeoutMs: number;
+ }) => Promise;
+ runScenarioDir: (options: {
+ entry: string;
+ runContext?: ScenarioRunContext;
+ scenarioDir: string;
+ timeoutMs: number;
+ }) => Promise;
+}) => Promise;
+
+const SNAPSHOT_METADATA_KEYS = [
+ "gen_ai.tool.name",
+ "model",
+ "operation",
+ "provider",
+ "scenario",
+ "strands.agent.id",
+ "strands.agent.name",
+ "strands.handoffs",
+ "strands.model.api",
+ "strands.node.id",
+ "strands.node.status",
+ "strands.node.type",
+ "strands.operation",
+ "strands.orchestrator.id",
+ "strands.status",
+ "strands.stop_reason",
+ "strands.tool.name",
+] as const;
+
+const OPERATION_NAMES = [
+ "strands-agent-invoke-operation",
+ "strands-agent-stream-operation",
+ "strands-graph-invoke-operation",
+ "strands-overlap-parent-operation",
+ "strands-swarm-invoke-operation",
+];
+
+function pickSnapshotMetadata(
+ value: unknown,
+): Record | undefined {
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
+ return undefined;
+ }
+
+ const metadata = Object.fromEntries(
+ Object.entries(value).filter(([key]) =>
+ SNAPSHOT_METADATA_KEYS.includes(
+ key as (typeof SNAPSHOT_METADATA_KEYS)[number],
+ ),
+ ),
+ ) as Record;
+ return Object.keys(metadata).length > 0 ? metadata : undefined;
+}
+
+function normalizeValue(value: unknown): Json | undefined {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "string") {
+ return value.replace(/call_[A-Za-z0-9_-]+/g, "");
+ }
+ if (Array.isArray(value)) {
+ return value.map((entry) => normalizeValue(entry) ?? null) as Json;
+ }
+ if (value && typeof value === "object") {
+ return Object.fromEntries(
+ Object.entries(value).map(([key, entry]) => [
+ key,
+ normalizeValue(entry) ?? null,
+ ]),
+ ) as Json;
+ }
+ return value as Json;
+}
+
+function normalizeSnapshotMetrics(
+ value: unknown,
+): Record | undefined {
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
+ return undefined;
+ }
+
+ const metrics = Object.fromEntries(
+ Object.entries(value)
+ .sort(([left], [right]) => left.localeCompare(right))
+ .map(([key, entry]) => {
+ if (key === "start" || key === "end") {
+ return [key, ""];
+ }
+ if (
+ key === "duration" ||
+ key === "strands.duration_ms" ||
+ key === "strands.latency_ms" ||
+ key === "strands.node.duration_ms" ||
+ key === "strands.time_to_first_byte_ms"
+ ) {
+ return [key, ""];
+ }
+ return [key, normalizeValue(entry) ?? null];
+ }),
+ ) as Record;
+
+ return Object.keys(metrics).length > 0 ? metrics : undefined;
+}
+
+function snapshotFields(event: CapturedLogEvent) {
+ const fields = spanTreeFields(event);
+
+ return {
+ span_attributes: fields.span_attributes,
+ input:
+ event.span.type === "llm" || event.span.type === undefined
+ ? undefined
+ : normalizeValue(fields.input),
+ output:
+ event.span.type === "llm" || event.span.type === undefined
+ ? undefined
+ : normalizeValue(fields.output),
+ metadata: normalizeValue(pickSnapshotMetadata(fields.metadata)),
+ metrics: normalizeSnapshotMetrics(fields.metrics),
+ error: fields.error,
+ };
+}
+
+function latestEventsPerSpan(events: CapturedLogEvent[]): CapturedLogEvent[] {
+ const orderedSpanIds: string[] = [];
+ const latestBySpanId = new Map();
+
+ for (const event of events) {
+ if (!event.span.id) {
+ continue;
+ }
+ if (!latestBySpanId.has(event.span.id)) {
+ orderedSpanIds.push(event.span.id);
+ }
+ latestBySpanId.set(event.span.id, event);
+ }
+
+ return orderedSpanIds.flatMap((spanId) => {
+ const event = latestBySpanId.get(spanId);
+ return event ? [event] : [];
+ });
+}
+
+function summarize(events: CapturedLogEvent[]): SpanTreeEntry[] {
+ const latestEvents = latestEventsPerSpan(events);
+ const names = new Set([
+ ROOT_NAME,
+ ...OPERATION_NAMES,
+ "Agent: weather-agent",
+ "Agent: stream-agent",
+ "Agent: graph-researcher",
+ "Agent: graph-writer",
+ "Agent: overlap-agent",
+ "Agent: swarm-router",
+ "Agent: swarm-finisher",
+ `Strands model: ${MODEL_NAME}`,
+ "Strands Graph",
+ "Strands Swarm",
+ "node: graph-researcher",
+ "node: graph-writer",
+ "node: overlap-node-a",
+ "node: overlap-node-b",
+ "node: swarm-router",
+ "node: swarm-finisher",
+ "tool: lookup_weather",
+ ]);
+
+ return latestEvents
+ .filter((event) => event.span.name && names.has(event.span.name))
+ .map((event) => ({
+ event,
+ fields: snapshotFields(event),
+ }));
+}
+
+function findLatestChild(
+ events: CapturedLogEvent[],
+ name: string,
+ parentId: string | undefined,
+): CapturedLogEvent | undefined {
+ return findChildSpans(events, name, parentId).at(-1);
+}
+
+export function defineStrandsAgentSDKInstrumentationAssertions(options: {
+ name: string;
+ expectOverlapParentProbe?: boolean;
+ runScenario: RunStrandsAgentSDKScenario;
+ snapshotName: string;
+ testFileUrl: string;
+ timeoutMs: number;
+}): void {
+ const spanSnapshotPath = resolveFileSnapshotPath(
+ options.testFileUrl,
+ `${options.snapshotName}.span-tree.json`,
+ );
+ const timeoutMs = effectiveScenarioTimeoutMs(options.timeoutMs);
+ const testConfig = { timeout: timeoutMs };
+
+ describe(options.name, () => {
+ let events: CapturedLogEvent[] = [];
+ let setupError: string | undefined;
+
+ beforeAll(async () => {
+ try {
+ await withScenarioHarness(async (harness) => {
+ await options.runScenario(harness);
+ events = harness.events();
+ });
+ } catch (error) {
+ setupError = error instanceof Error ? error.message : String(error);
+ }
+ }, timeoutMs);
+
+ test("captures the root trace", testConfig, () => {
+ expect(setupError).toBeUndefined();
+ const root = findLatestSpan(events, ROOT_NAME);
+
+ expect(root).toBeDefined();
+ expect(root?.row.metadata).toMatchObject({ scenario: SCENARIO_NAME });
+ });
+
+ test("captures agent invoke and stream spans", testConfig, () => {
+ expect(setupError).toBeUndefined();
+ const invokeOperation = findLatestSpan(
+ events,
+ "strands-agent-invoke-operation",
+ );
+ const invokeAgent = findLatestChild(
+ events,
+ "Agent: weather-agent",
+ invokeOperation?.span.id,
+ );
+ const invokeModels = findChildSpans(
+ events,
+ `Strands model: ${MODEL_NAME}`,
+ invokeAgent?.span.id,
+ );
+ const toolSpan = findLatestChild(
+ events,
+ "tool: lookup_weather",
+ invokeAgent?.span.id,
+ );
+ const streamOperation = findLatestSpan(
+ events,
+ "strands-agent-stream-operation",
+ );
+ const streamAgent = findLatestChild(
+ events,
+ "Agent: stream-agent",
+ streamOperation?.span.id,
+ );
+ const streamModels = findChildSpans(
+ events,
+ `Strands model: ${MODEL_NAME}`,
+ streamAgent?.span.id,
+ );
+
+ expect(invokeAgent?.span.type).toBe("task");
+ expect(invokeAgent?.row.metadata).toMatchObject({
+ "strands.operation": "Agent.stream",
+ provider: "openai",
+ });
+ expect(JSON.stringify(invokeAgent?.output)).toContain(
+ "STRANDS_AGENT_TOOL_OK",
+ );
+
+ expect(invokeModels.length).toBeGreaterThanOrEqual(1);
+ expect(invokeModels[0]?.span.type).toBe("llm");
+ expect(invokeModels[0]?.row.metadata).toMatchObject({
+ model: MODEL_NAME,
+ provider: "openai",
+ "strands.operation": "model.stream",
+ });
+
+ expect(toolSpan?.span.type).toBe("tool");
+ expect(toolSpan?.input).toMatchObject({ city: "Vienna" });
+ expect(toolSpan?.row.metadata).toMatchObject({
+ "gen_ai.tool.name": "lookup_weather",
+ "strands.tool.name": "lookup_weather",
+ });
+ expect(toolSpan?.row.metadata?.["gen_ai.tool.call.id"]).toEqual(
+ expect.any(String),
+ );
+ expect(JSON.stringify(toolSpan?.output)).toContain("STRANDS_TOOL_OK");
+
+ expect(streamAgent?.span.type).toBe("task");
+ expect(JSON.stringify(streamAgent?.output)).toContain(
+ "STRANDS_AGENT_STREAM_OK",
+ );
+ expect(streamModels.length).toBeGreaterThanOrEqual(1);
+ });
+
+ test("captures graph and swarm orchestration spans", testConfig, () => {
+ expect(setupError).toBeUndefined();
+ const graphOperation = findLatestSpan(
+ events,
+ "strands-graph-invoke-operation",
+ );
+ const graph = findLatestChild(
+ events,
+ "Strands Graph",
+ graphOperation?.span.id,
+ );
+ const graphResearcherNode = findLatestChild(
+ events,
+ "node: graph-researcher",
+ graph?.span.id,
+ );
+ const graphWriterNode = findLatestChild(
+ events,
+ "node: graph-writer",
+ graph?.span.id,
+ );
+ const graphWriterAgent = findLatestChild(
+ events,
+ "Agent: graph-writer",
+ graphWriterNode?.span.id,
+ );
+ const swarmOperation = findLatestSpan(
+ events,
+ "strands-swarm-invoke-operation",
+ );
+ const swarm = findLatestChild(
+ events,
+ "Strands Swarm",
+ swarmOperation?.span.id,
+ );
+ const swarmRouterNode = findLatestChild(
+ events,
+ "node: swarm-router",
+ swarm?.span.id,
+ );
+ const swarmFinisherNode = findLatestChild(
+ events,
+ "node: swarm-finisher",
+ swarm?.span.id,
+ );
+ const swarmFinisherAgent = findLatestChild(
+ events,
+ "Agent: swarm-finisher",
+ swarmFinisherNode?.span.id,
+ );
+
+ expect(graph?.span.type).toBe("task");
+ expect(graph?.row.metadata).toMatchObject({
+ "strands.operation": "Graph.stream",
+ "strands.orchestrator.id": "weather-graph",
+ provider: "strands",
+ });
+ expect(JSON.stringify(graph?.output)).toContain("STRANDS_GRAPH_OK");
+ expect(graphResearcherNode?.row.metadata).toMatchObject({
+ "strands.node.id": "graph-researcher",
+ "strands.node.status": "COMPLETED",
+ });
+ expect(graphWriterNode?.row.metadata).toMatchObject({
+ "strands.node.id": "graph-writer",
+ "strands.node.status": "COMPLETED",
+ });
+ expect(graphWriterAgent?.span.parentIds).toContain(
+ graphWriterNode?.span.id,
+ );
+ expect(JSON.stringify(graphWriterAgent?.output)).toContain(
+ "STRANDS_GRAPH_OK",
+ );
+
+ expect(swarm?.span.type).toBe("task");
+ expect(swarm?.row.metadata).toMatchObject({
+ "strands.handoffs": [
+ { source: "swarm-router", targets: ["swarm-finisher"] },
+ ],
+ "strands.operation": "Swarm.stream",
+ "strands.orchestrator.id": "weather-swarm",
+ });
+ expect(JSON.stringify(swarm?.output)).toContain("STRANDS_SWARM_OK");
+ expect(swarmRouterNode?.row.metadata).toMatchObject({
+ "strands.node.id": "swarm-router",
+ "strands.node.status": "COMPLETED",
+ });
+ expect(swarmFinisherNode?.row.metadata).toMatchObject({
+ "strands.node.id": "swarm-finisher",
+ "strands.node.status": "COMPLETED",
+ });
+ expect(swarmFinisherAgent?.span.parentIds).toContain(
+ swarmFinisherNode?.span.id,
+ );
+ expect(JSON.stringify(swarmFinisherAgent?.output)).toContain(
+ "STRANDS_SWARM_OK",
+ );
+ });
+
+ if (options.expectOverlapParentProbe) {
+ test(
+ "does not attach ambiguous child streams to the wrong node",
+ testConfig,
+ () => {
+ expect(setupError).toBeUndefined();
+ const operation = findLatestSpan(
+ events,
+ "strands-overlap-parent-operation",
+ );
+ const graph = findLatestChild(
+ events,
+ "Strands Graph",
+ operation?.span.id,
+ );
+ const overlapNodeA = findLatestChild(
+ events,
+ "node: overlap-node-a",
+ graph?.span.id,
+ );
+ const overlapNodeB = findLatestChild(
+ events,
+ "node: overlap-node-b",
+ graph?.span.id,
+ );
+ const agentUnderOperation = findLatestChild(
+ events,
+ "Agent: overlap-agent",
+ operation?.span.id,
+ );
+ const agentUnderNodeA = findLatestChild(
+ events,
+ "Agent: overlap-agent",
+ overlapNodeA?.span.id,
+ );
+ const agentUnderNodeB = findLatestChild(
+ events,
+ "Agent: overlap-agent",
+ overlapNodeB?.span.id,
+ );
+
+ expect(graph?.span.type).toBe("task");
+ expect(overlapNodeA?.row.metadata).toMatchObject({
+ "strands.node.id": "overlap-node-a",
+ "strands.node.status": "COMPLETED",
+ });
+ expect(overlapNodeB?.row.metadata).toMatchObject({
+ "strands.node.id": "overlap-node-b",
+ "strands.node.status": "COMPLETED",
+ });
+ expect(agentUnderOperation?.span.type).toBe("task");
+ expect(JSON.stringify(agentUnderOperation?.output)).toContain(
+ "OVERLAP_AGENT_OK",
+ );
+ expect(agentUnderNodeA).toBeUndefined();
+ expect(agentUnderNodeB).toBeUndefined();
+ },
+ );
+ }
+
+ test(
+ "does not emit duplicate OpenAI provider auto spans",
+ testConfig,
+ () => {
+ expect(setupError).toBeUndefined();
+ const duplicateProviderSpans = latestEventsPerSpan(events).filter(
+ (event) =>
+ event.span.name === "Chat Completion" ||
+ event.span.name?.startsWith("openai."),
+ );
+
+ expect(duplicateProviderSpans).toHaveLength(0);
+ },
+ );
+
+ test("matches the span tree snapshot", testConfig, async () => {
+ expect(setupError).toBeUndefined();
+ await matchSpanTreeSnapshot(summarize(events), spanSnapshotPath);
+ });
+ });
+}
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/package.json b/e2e/scenarios/strands-agent-sdk-instrumentation/package.json
new file mode 100644
index 000000000..31a9471cf
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/package.json
@@ -0,0 +1,15 @@
+{
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@strands-agents/sdk": "1.6.0",
+ "openai": "^6.7.0"
+ },
+ "braintrustScenario": {
+ "canary": {
+ "dependencies": {
+ "@strands-agents/sdk": "latest"
+ }
+ }
+ }
+}
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/pnpm-lock.yaml b/e2e/scenarios/strands-agent-sdk-instrumentation/pnpm-lock.yaml
new file mode 100644
index 000000000..f620ce017
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/pnpm-lock.yaml
@@ -0,0 +1,1355 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@strands-agents/sdk':
+ specifier: 1.6.0
+ version: 1.6.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(@opentelemetry/api@1.9.1)(@smithy/types@4.15.0)(express@5.2.1)(openai@6.44.0(zod@4.4.3))(zod@4.4.3)
+ openai:
+ specifier: ^6.7.0
+ version: 6.44.0(zod@4.4.3)
+
+packages:
+
+ '@aws-crypto/crc32@5.2.0':
+ resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
+ engines: {node: '>=16.0.0'}
+
+ '@aws-crypto/sha256-browser@5.2.0':
+ resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==}
+
+ '@aws-crypto/sha256-js@5.2.0':
+ resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
+ engines: {node: '>=16.0.0'}
+
+ '@aws-crypto/supports-web-crypto@5.2.0':
+ resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==}
+
+ '@aws-crypto/util@5.2.0':
+ resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
+
+ '@aws-sdk/client-bedrock-runtime@3.1071.0':
+ resolution: {integrity: sha512-1Sa7UTC98Wy+M+zYbbXEqhcL5WUE3VnAmO1Y2RikmYjWx/8nCHudjSj1bVovk6Obfrrsir9Q1AbrmKbgxwI90g==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/core@3.974.22':
+ resolution: {integrity: sha512-YofH63shc6YRdXjz80BJkpJW+Bkn0Cuu2dn4Rv7s9G2Idt58tgtzQEWxrR2xVljlVfIBeUjPuULnSVYLke3sUQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-env@3.972.48':
+ resolution: {integrity: sha512-h6FEC95fbexUd6zxm4PdgS82bTcI2PRtUb2ZwMipb/Xr8bPwtf0G8rBo2jp7NA24Mbx2JA8/WingiYpA9RCCyw==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-http@3.972.50':
+ resolution: {integrity: sha512-lJO3OLpjvz5m/RSBQmsG/CEUGsvCy5ruxKwPQaOCqxqCMuyYT2BZwQUTDZVVwqQ9LrZKuK24JSa6r31hL/tvkg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-ini@3.972.55':
+ resolution: {integrity: sha512-TBoF4buBGYhXjdZAryayY2TrkQj2B2KfE/msG4V53XCt+w0EhEwM2JRjx8p2grJ2C6gtH5++SAwEvGMRdi0yyw==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-login@3.972.54':
+ resolution: {integrity: sha512-hBWI3wZTdTGiuMfmPts6AWbAjFfRniOQnqx68tc2cQvRKWawFbN9wkLOVPWM1FAOyowZU73mC6Fi+rHSHNyLFw==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-node@3.972.57':
+ resolution: {integrity: sha512-u6dClpzNdWf1HGWz4wwhdXi1wiOofCLniM9S4BQQGlLAN9TW7VB+ld5V533GdKrYMaFeBGFqKnj0JCYvynLqwQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-process@3.972.48':
+ resolution: {integrity: sha512-w6VZwojPt12WnEkAUy6Nu4K6sWCbBmR7QX390b0nE6vRvkXbrYr9Lq9VySGkfjiMjpUA87op+J4EgvRmtWIDoQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-sso@3.972.54':
+ resolution: {integrity: sha512-23uZpIpF2SIFDCa1fcWa202tK4gGeyvX6GIIAjiB8WBsvsVRBMnJ/7dCxHzxf7eZT7GToJg837LDIBnZsl/VUg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-web-identity@3.972.54':
+ resolution: {integrity: sha512-0Iv5QttS6wcATlodYKgvQj6B9Db51rx7NU9fqu0PoLeS4BIgdYMc/QK4smwLwpm5RFrs02V/eLyEFp3FklvlNQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/eventstream-handler-node@3.972.22':
+ resolution: {integrity: sha512-tqPJv0dz4+O0hWGm1a6YekcMZyPhDFs/zH73Von7icaVT5n0Jqvm86typ3jRrG+qoUdPhALOnboRLTmnWQTlYQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-eventstream@3.972.18':
+ resolution: {integrity: sha512-OHpk8YoZi3yexPq8aFt1vN1IxA2zLKvsIR5GpWYylX/ve6kQmY7wxHNSFy/D3t2apMZ16rs76Co4dJWcDyIk3A==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-websocket@3.972.30':
+ resolution: {integrity: sha512-kH6N4f/Fzi9r/dYap8EQ+Zk4NOz8pl4AtWKhzAoG2C1/4YkIHok9APp/e+75woreWQq264n+LkrJsJVZ0Q+M1Q==}
+ engines: {node: '>= 14.0.0'}
+
+ '@aws-sdk/nested-clients@3.997.22':
+ resolution: {integrity: sha512-4IwtcYSxEIVw5hcp8ogq0CMbFNZFw7jJUetpfFUhFFeqsa1K8j2Ihg2hnxLyOp3stMZnXda6VzOmPi1AFZQXcg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/signature-v4-multi-region@3.996.35':
+ resolution: {integrity: sha512-6L/VWs+Wch2stHemCGTmUNqKLMzURxQDK5boNG3Jn3kAOp71meDUuS5sbObpEvFxHDq0uWeSLFDNSYsjNt+Dlg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/token-providers@3.1071.0':
+ resolution: {integrity: sha512-4LDW2Qob6LoLFuqYSYZq2AyTE9koSE9+i+n5UZcm10GpmQOK0zRD9L4uYlzItiTKksIWgC/qMFChAi3RvKYtMg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/types@3.973.13':
+ resolution: {integrity: sha512-pEHZqRkAlHfnfAU9tK+WpKv/gBNjGJrHMgA3A0iYRGyswBS2t0pfez+lWlwktb3Bqa0ovh7w/QJTFwp3fDxLNg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/util-locate-window@3.965.8':
+ resolution: {integrity: sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/xml-builder@3.972.30':
+ resolution: {integrity: sha512-StElZPEoBquWwNqw1AcfpzEyZqJvFxouG+mpDNYlcH6ZOrqd2CuIryv+8LV8gNHZUOyKyJF3Dq9vxaXEmDR9TQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws/lambda-invoke-store@0.2.4':
+ resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@hono/node-server@1.19.14':
+ resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
+ engines: {node: '>=18.14.1'}
+ peerDependencies:
+ hono: ^4
+
+ '@modelcontextprotocol/sdk@1.29.0':
+ resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@cfworker/json-schema': ^4.1.1
+ zod: ^3.25 || ^4.0
+ peerDependenciesMeta:
+ '@cfworker/json-schema':
+ optional: true
+
+ '@nodable/entities@2.2.0':
+ resolution: {integrity: sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==}
+
+ '@opentelemetry/api@1.9.1':
+ resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==}
+ engines: {node: '>=8.0.0'}
+
+ '@smithy/core@3.25.1':
+ resolution: {integrity: sha512-zpDbpXBCBsxfLtG2GEUyfgvHvSFrw5CwDZSNzL0v52gx/c3oPlPbm+7W7num8xs6vyiUBn+bvYPHcQDOXZynCQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/credential-provider-imds@4.4.1':
+ resolution: {integrity: sha512-TSAF5NHgxEsllbErYWbK8aLnl5L601NGc5VYJlSPsKnf3YlkhdoBN+geGcaU00oiw2OK3QO5LA3QNXiiWhCidQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/fetch-http-handler@5.5.1':
+ resolution: {integrity: sha512-96JrD1q71anokymx9Iblb+zKmNQYNstlV/25A9ZYIJ2A0rp1r7/GZAIm0bDWSmVvz3DpNOCZuabzsiL+w0UHhw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/is-array-buffer@2.2.0':
+ resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/node-http-handler@4.8.1':
+ resolution: {integrity: sha512-emtXvoky671puri18ETf64AFIQUGIEA093F2drXpBgB0OGnBLjcwNR3CA2mYu62IAqNsS56xa5lnTxAgPq7cjw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/signature-v4@5.5.1':
+ resolution: {integrity: sha512-X9rVls3En0z3NtrmguTmpRM0/NqtWUxBjal6fcAkwtsub+gOdLZ6kD+V7xhUgFMGdG14bHbZ7M5QjaRI1+DatQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/types@4.15.0':
+ resolution: {integrity: sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-buffer-from@2.2.0':
+ resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/util-utf8@2.3.0':
+ resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
+ engines: {node: '>=14.0.0'}
+
+ '@strands-agents/sdk@1.6.0':
+ resolution: {integrity: sha512-tvmDHkgO7oPe8G0c3bmnTiSetb/Iden4DdSh/maRyLqDZj7uLIeS7whwrk654yM7EOYgo6p0/ewEItMbS+7m+A==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@a2a-js/sdk': ^0.3.10
+ '@ai-sdk/provider': ^3.0.0
+ '@anthropic-ai/sdk': ^0.92.0
+ '@aws-sdk/client-bedrock-agent': ^3.943.0
+ '@aws-sdk/client-bedrock-agent-runtime': ^3.943.0
+ '@aws-sdk/client-s3': ^3.943.0
+ '@aws/bedrock-token-generator': ^1.1.0
+ '@cedar-policy/cedar-wasm': ^4.0.0
+ '@cedar-policy/mcp-schema-generator-wasm': ^0.6.0
+ '@google/genai': ^1.40.0
+ '@modelcontextprotocol/sdk': ^1.25.2
+ '@opentelemetry/api': ^1.9.0
+ '@opentelemetry/exporter-metrics-otlp-http': ^0.214.0
+ '@opentelemetry/exporter-trace-otlp-http': ^0.214.0
+ '@opentelemetry/resources': ^2.6.1
+ '@opentelemetry/sdk-metrics': ^2.6.1
+ '@opentelemetry/sdk-trace-base': ^2.6.1
+ '@opentelemetry/sdk-trace-node': ^2.6.1
+ '@smithy/types': ^4.0.0
+ express: ^5.1.0
+ openai: ^6.7.0
+ zod: ^4.1.12
+ peerDependenciesMeta:
+ '@a2a-js/sdk':
+ optional: true
+ '@ai-sdk/provider':
+ optional: true
+ '@anthropic-ai/sdk':
+ optional: true
+ '@aws-sdk/client-bedrock-agent':
+ optional: true
+ '@aws-sdk/client-bedrock-agent-runtime':
+ optional: true
+ '@aws-sdk/client-s3':
+ optional: true
+ '@aws/bedrock-token-generator':
+ optional: true
+ '@cedar-policy/cedar-wasm':
+ optional: true
+ '@cedar-policy/mcp-schema-generator-wasm':
+ optional: true
+ '@google/genai':
+ optional: true
+ '@opentelemetry/exporter-metrics-otlp-http':
+ optional: true
+ '@opentelemetry/exporter-trace-otlp-http':
+ optional: true
+ '@opentelemetry/resources':
+ optional: true
+ '@opentelemetry/sdk-metrics':
+ optional: true
+ '@opentelemetry/sdk-trace-base':
+ optional: true
+ '@opentelemetry/sdk-trace-node':
+ optional: true
+ '@smithy/types':
+ optional: true
+ express:
+ optional: true
+ openai:
+ optional: true
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ accepts@2.0.0:
+ resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
+ engines: {node: '>= 0.6'}
+
+ ajv-formats@3.0.1:
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
+ ajv@8.20.0:
+ resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==}
+
+ anynum@1.0.1:
+ resolution: {integrity: sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==}
+
+ body-parser@2.3.0:
+ resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
+ engines: {node: '>=18'}
+
+ bowser@2.14.1:
+ resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
+
+ bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ content-disposition@1.1.0:
+ resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
+ engines: {node: '>=18'}
+
+ content-type@1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+
+ content-type@2.0.0:
+ resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==}
+ engines: {node: '>=18'}
+
+ cookie-signature@1.2.2:
+ resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
+ engines: {node: '>=6.6.0'}
+
+ cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+
+ cors@2.8.6:
+ resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
+ engines: {node: '>= 0.10'}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+
+ encodeurl@2.0.0:
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+ engines: {node: '>= 0.8'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-object-atoms@1.1.2:
+ resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
+ engines: {node: '>= 0.4'}
+
+ escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
+ etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+
+ eventsource-parser@3.1.0:
+ resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==}
+ engines: {node: '>=18.0.0'}
+
+ eventsource@3.0.7:
+ resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
+ engines: {node: '>=18.0.0'}
+
+ express-rate-limit@8.5.2:
+ resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==}
+ engines: {node: '>= 16'}
+ peerDependencies:
+ express: '>= 4.11'
+
+ express@5.2.1:
+ resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
+ engines: {node: '>= 18'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-uri@3.1.2:
+ resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==}
+
+ fast-xml-builder@1.2.0:
+ resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==}
+
+ fast-xml-parser@5.7.3:
+ resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==}
+ hasBin: true
+
+ finalhandler@2.1.1:
+ resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
+ engines: {node: '>= 18.0.0'}
+
+ forwarded@0.2.0:
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+ engines: {node: '>= 0.6'}
+
+ fresh@2.0.0:
+ resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
+ engines: {node: '>= 0.8'}
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.4:
+ resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
+ engines: {node: '>= 0.4'}
+
+ hono@4.12.26:
+ resolution: {integrity: sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==}
+ engines: {node: '>=16.9.0'}
+
+ http-errors@2.0.1:
+ resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
+ engines: {node: '>= 0.8'}
+
+ iconv-lite@0.7.2:
+ resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
+ engines: {node: '>=0.10.0'}
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ ip-address@10.2.0:
+ resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
+ engines: {node: '>= 12'}
+
+ ipaddr.js@1.9.1:
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+ engines: {node: '>= 0.10'}
+
+ is-promise@4.0.0:
+ resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ jose@6.2.3:
+ resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==}
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json-schema-typed@8.0.2:
+ resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ media-typer@1.1.0:
+ resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
+ engines: {node: '>= 0.8'}
+
+ merge-descriptors@2.0.0:
+ resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
+ engines: {node: '>=18'}
+
+ mime-db@1.54.0:
+ resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@3.0.2:
+ resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
+ engines: {node: '>=18'}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
+ on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ openai@6.44.0:
+ resolution: {integrity: sha512-09/gH+8jH0RgUwsgWHAaxsKGRT5zVZ95IaJUnqAWj6XejIBmnFRwq2WUIF37VtDEsmGrtPmvCs5+yBSeZGWvkA==}
+ peerDependencies:
+ ws: ^8.18.0
+ zod: ^3.25 || ^4.0
+ peerDependenciesMeta:
+ ws:
+ optional: true
+ zod:
+ optional: true
+
+ parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+
+ path-expression-matcher@1.5.0:
+ resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==}
+ engines: {node: '>=14.0.0'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-to-regexp@8.4.2:
+ resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
+
+ pkce-challenge@5.0.1:
+ resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
+ engines: {node: '>=16.20.0'}
+
+ proxy-addr@2.0.7:
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+ engines: {node: '>= 0.10'}
+
+ qs@6.15.2:
+ resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
+ engines: {node: '>=0.6'}
+
+ range-parser@1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+
+ raw-body@3.0.2:
+ resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
+ engines: {node: '>= 0.10'}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ router@2.2.0:
+ resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
+ engines: {node: '>= 18'}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ send@1.2.1:
+ resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
+ engines: {node: '>= 18'}
+
+ serve-static@2.2.1:
+ resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
+ engines: {node: '>= 18'}
+
+ setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ side-channel-list@1.0.1:
+ resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.1:
+ resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
+ engines: {node: '>= 0.4'}
+
+ statuses@2.0.2:
+ resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
+ engines: {node: '>= 0.8'}
+
+ strnum@2.4.1:
+ resolution: {integrity: sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==}
+
+ toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ type-is@2.1.0:
+ resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
+ engines: {node: '>= 18'}
+
+ unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+
+ uuid@14.0.0:
+ resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==}
+ hasBin: true
+
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ xml-naming@0.1.0:
+ resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==}
+ engines: {node: '>=16.0.0'}
+
+ yaml@2.9.0:
+ resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
+ zod-to-json-schema@3.25.2:
+ resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
+ peerDependencies:
+ zod: ^3.25.28 || ^4
+
+ zod@4.4.3:
+ resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
+
+snapshots:
+
+ '@aws-crypto/crc32@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.13
+ tslib: 2.8.1
+
+ '@aws-crypto/sha256-browser@5.2.0':
+ dependencies:
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-crypto/supports-web-crypto': 5.2.0
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.13
+ '@aws-sdk/util-locate-window': 3.965.8
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-crypto/sha256-js@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.13
+ tslib: 2.8.1
+
+ '@aws-crypto/supports-web-crypto@5.2.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@aws-crypto/util@5.2.0':
+ dependencies:
+ '@aws-sdk/types': 3.973.13
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-sdk/client-bedrock-runtime@3.1071.0':
+ dependencies:
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/credential-provider-node': 3.972.57
+ '@aws-sdk/eventstream-handler-node': 3.972.22
+ '@aws-sdk/middleware-eventstream': 3.972.18
+ '@aws-sdk/middleware-websocket': 3.972.30
+ '@aws-sdk/token-providers': 3.1071.0
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/fetch-http-handler': 5.5.1
+ '@smithy/node-http-handler': 4.8.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/core@3.974.22':
+ dependencies:
+ '@aws-sdk/types': 3.973.13
+ '@aws-sdk/xml-builder': 3.972.30
+ '@aws/lambda-invoke-store': 0.2.4
+ '@smithy/core': 3.25.1
+ '@smithy/signature-v4': 5.5.1
+ '@smithy/types': 4.15.0
+ bowser: 2.14.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-env@3.972.48':
+ dependencies:
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-http@3.972.50':
+ dependencies:
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/fetch-http-handler': 5.5.1
+ '@smithy/node-http-handler': 4.8.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-ini@3.972.55':
+ dependencies:
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/credential-provider-env': 3.972.48
+ '@aws-sdk/credential-provider-http': 3.972.50
+ '@aws-sdk/credential-provider-login': 3.972.54
+ '@aws-sdk/credential-provider-process': 3.972.48
+ '@aws-sdk/credential-provider-sso': 3.972.54
+ '@aws-sdk/credential-provider-web-identity': 3.972.54
+ '@aws-sdk/nested-clients': 3.997.22
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/credential-provider-imds': 4.4.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-login@3.972.54':
+ dependencies:
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/nested-clients': 3.997.22
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-node@3.972.57':
+ dependencies:
+ '@aws-sdk/credential-provider-env': 3.972.48
+ '@aws-sdk/credential-provider-http': 3.972.50
+ '@aws-sdk/credential-provider-ini': 3.972.55
+ '@aws-sdk/credential-provider-process': 3.972.48
+ '@aws-sdk/credential-provider-sso': 3.972.54
+ '@aws-sdk/credential-provider-web-identity': 3.972.54
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/credential-provider-imds': 4.4.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-process@3.972.48':
+ dependencies:
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-sso@3.972.54':
+ dependencies:
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/nested-clients': 3.997.22
+ '@aws-sdk/token-providers': 3.1071.0
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-web-identity@3.972.54':
+ dependencies:
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/nested-clients': 3.997.22
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/eventstream-handler-node@3.972.22':
+ dependencies:
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-eventstream@3.972.18':
+ dependencies:
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-websocket@3.972.30':
+ dependencies:
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/fetch-http-handler': 5.5.1
+ '@smithy/signature-v4': 5.5.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/nested-clients@3.997.22':
+ dependencies:
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/signature-v4-multi-region': 3.996.35
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/fetch-http-handler': 5.5.1
+ '@smithy/node-http-handler': 4.8.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/signature-v4-multi-region@3.996.35':
+ dependencies:
+ '@aws-sdk/types': 3.973.13
+ '@smithy/signature-v4': 5.5.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/token-providers@3.1071.0':
+ dependencies:
+ '@aws-sdk/core': 3.974.22
+ '@aws-sdk/nested-clients': 3.997.22
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/types@3.973.13':
+ dependencies:
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/util-locate-window@3.965.8':
+ dependencies:
+ tslib: 2.8.1
+
+ '@aws-sdk/xml-builder@3.972.30':
+ dependencies:
+ '@smithy/types': 4.15.0
+ fast-xml-parser: 5.7.3
+ tslib: 2.8.1
+
+ '@aws/lambda-invoke-store@0.2.4': {}
+
+ '@hono/node-server@1.19.14(hono@4.12.26)':
+ dependencies:
+ hono: 4.12.26
+
+ '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)':
+ dependencies:
+ '@hono/node-server': 1.19.14(hono@4.12.26)
+ ajv: 8.20.0
+ ajv-formats: 3.0.1(ajv@8.20.0)
+ content-type: 1.0.5
+ cors: 2.8.6
+ cross-spawn: 7.0.6
+ eventsource: 3.0.7
+ eventsource-parser: 3.1.0
+ express: 5.2.1
+ express-rate-limit: 8.5.2(express@5.2.1)
+ hono: 4.12.26
+ jose: 6.2.3
+ json-schema-typed: 8.0.2
+ pkce-challenge: 5.0.1
+ raw-body: 3.0.2
+ zod: 4.4.3
+ zod-to-json-schema: 3.25.2(zod@4.4.3)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@nodable/entities@2.2.0': {}
+
+ '@opentelemetry/api@1.9.1': {}
+
+ '@smithy/core@3.25.1':
+ dependencies:
+ '@aws-crypto/crc32': 5.2.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/credential-provider-imds@4.4.1':
+ dependencies:
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/fetch-http-handler@5.5.1':
+ dependencies:
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/is-array-buffer@2.2.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/node-http-handler@4.8.1':
+ dependencies:
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/signature-v4@5.5.1':
+ dependencies:
+ '@smithy/core': 3.25.1
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/types@4.15.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/util-buffer-from@2.2.0':
+ dependencies:
+ '@smithy/is-array-buffer': 2.2.0
+ tslib: 2.8.1
+
+ '@smithy/util-utf8@2.3.0':
+ dependencies:
+ '@smithy/util-buffer-from': 2.2.0
+ tslib: 2.8.1
+
+ '@strands-agents/sdk@1.6.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(@opentelemetry/api@1.9.1)(@smithy/types@4.15.0)(express@5.2.1)(openai@6.44.0(zod@4.4.3))(zod@4.4.3)':
+ dependencies:
+ '@aws-sdk/client-bedrock-runtime': 3.1071.0
+ '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3)
+ '@opentelemetry/api': 1.9.1
+ '@types/json-schema': 7.0.15
+ uuid: 14.0.0
+ yaml: 2.9.0
+ zod: 4.4.3
+ optionalDependencies:
+ '@smithy/types': 4.15.0
+ express: 5.2.1
+ openai: 6.44.0(zod@4.4.3)
+
+ '@types/json-schema@7.0.15': {}
+
+ accepts@2.0.0:
+ dependencies:
+ mime-types: 3.0.2
+ negotiator: 1.0.0
+
+ ajv-formats@3.0.1(ajv@8.20.0):
+ optionalDependencies:
+ ajv: 8.20.0
+
+ ajv@8.20.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.2
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
+ anynum@1.0.1: {}
+
+ body-parser@2.3.0:
+ dependencies:
+ bytes: 3.1.2
+ content-type: 2.0.0
+ debug: 4.4.3
+ http-errors: 2.0.1
+ iconv-lite: 0.7.2
+ on-finished: 2.4.1
+ qs: 6.15.2
+ raw-body: 3.0.2
+ type-is: 2.1.0
+ transitivePeerDependencies:
+ - supports-color
+
+ bowser@2.14.1: {}
+
+ bytes@3.1.2: {}
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
+ content-disposition@1.1.0: {}
+
+ content-type@1.0.5: {}
+
+ content-type@2.0.0: {}
+
+ cookie-signature@1.2.2: {}
+
+ cookie@0.7.2: {}
+
+ cors@2.8.6:
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ depd@2.0.0: {}
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ ee-first@1.1.1: {}
+
+ encodeurl@2.0.0: {}
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-object-atoms@1.1.2:
+ dependencies:
+ es-errors: 1.3.0
+
+ escape-html@1.0.3: {}
+
+ etag@1.8.1: {}
+
+ eventsource-parser@3.1.0: {}
+
+ eventsource@3.0.7:
+ dependencies:
+ eventsource-parser: 3.1.0
+
+ express-rate-limit@8.5.2(express@5.2.1):
+ dependencies:
+ express: 5.2.1
+ ip-address: 10.2.0
+
+ express@5.2.1:
+ dependencies:
+ accepts: 2.0.0
+ body-parser: 2.3.0
+ content-disposition: 1.1.0
+ content-type: 1.0.5
+ cookie: 0.7.2
+ cookie-signature: 1.2.2
+ debug: 4.4.3
+ depd: 2.0.0
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 2.1.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ merge-descriptors: 2.0.0
+ mime-types: 3.0.2
+ on-finished: 2.4.1
+ once: 1.4.0
+ parseurl: 1.3.3
+ proxy-addr: 2.0.7
+ qs: 6.15.2
+ range-parser: 1.2.1
+ router: 2.2.0
+ send: 1.2.1
+ serve-static: 2.2.1
+ statuses: 2.0.2
+ type-is: 2.1.0
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-uri@3.1.2: {}
+
+ fast-xml-builder@1.2.0:
+ dependencies:
+ path-expression-matcher: 1.5.0
+ xml-naming: 0.1.0
+
+ fast-xml-parser@5.7.3:
+ dependencies:
+ '@nodable/entities': 2.2.0
+ fast-xml-builder: 1.2.0
+ path-expression-matcher: 1.5.0
+ strnum: 2.4.1
+
+ finalhandler@2.1.1:
+ dependencies:
+ debug: 4.4.3
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ forwarded@0.2.0: {}
+
+ fresh@2.0.0: {}
+
+ function-bind@1.1.2: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.4
+ math-intrinsics: 1.1.0
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.2
+
+ gopd@1.2.0: {}
+
+ has-symbols@1.1.0: {}
+
+ hasown@2.0.4:
+ dependencies:
+ function-bind: 1.1.2
+
+ hono@4.12.26: {}
+
+ http-errors@2.0.1:
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.2
+ toidentifier: 1.0.1
+
+ iconv-lite@0.7.2:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ inherits@2.0.4: {}
+
+ ip-address@10.2.0: {}
+
+ ipaddr.js@1.9.1: {}
+
+ is-promise@4.0.0: {}
+
+ isexe@2.0.0: {}
+
+ jose@6.2.3: {}
+
+ json-schema-traverse@1.0.0: {}
+
+ json-schema-typed@8.0.2: {}
+
+ math-intrinsics@1.1.0: {}
+
+ media-typer@1.1.0: {}
+
+ merge-descriptors@2.0.0: {}
+
+ mime-db@1.54.0: {}
+
+ mime-types@3.0.2:
+ dependencies:
+ mime-db: 1.54.0
+
+ ms@2.1.3: {}
+
+ negotiator@1.0.0: {}
+
+ object-assign@4.1.1: {}
+
+ object-inspect@1.13.4: {}
+
+ on-finished@2.4.1:
+ dependencies:
+ ee-first: 1.1.1
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ openai@6.44.0(zod@4.4.3):
+ optionalDependencies:
+ zod: 4.4.3
+
+ parseurl@1.3.3: {}
+
+ path-expression-matcher@1.5.0: {}
+
+ path-key@3.1.1: {}
+
+ path-to-regexp@8.4.2: {}
+
+ pkce-challenge@5.0.1: {}
+
+ proxy-addr@2.0.7:
+ dependencies:
+ forwarded: 0.2.0
+ ipaddr.js: 1.9.1
+
+ qs@6.15.2:
+ dependencies:
+ side-channel: 1.1.1
+
+ range-parser@1.2.1: {}
+
+ raw-body@3.0.2:
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.1
+ iconv-lite: 0.7.2
+ unpipe: 1.0.0
+
+ require-from-string@2.0.2: {}
+
+ router@2.2.0:
+ dependencies:
+ debug: 4.4.3
+ depd: 2.0.0
+ is-promise: 4.0.0
+ parseurl: 1.3.3
+ path-to-regexp: 8.4.2
+ transitivePeerDependencies:
+ - supports-color
+
+ safer-buffer@2.1.2: {}
+
+ send@1.2.1:
+ dependencies:
+ debug: 4.4.3
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ mime-types: 3.0.2
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ serve-static@2.2.1:
+ dependencies:
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 1.2.1
+ transitivePeerDependencies:
+ - supports-color
+
+ setprototypeof@1.2.0: {}
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ side-channel-list@1.0.1:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.1
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
+ statuses@2.0.2: {}
+
+ strnum@2.4.1:
+ dependencies:
+ anynum: 1.0.1
+
+ toidentifier@1.0.1: {}
+
+ tslib@2.8.1: {}
+
+ type-is@2.1.0:
+ dependencies:
+ content-type: 2.0.0
+ media-typer: 1.1.0
+ mime-types: 3.0.2
+
+ unpipe@1.0.0: {}
+
+ uuid@14.0.0: {}
+
+ vary@1.1.2: {}
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ wrappy@1.0.2: {}
+
+ xml-naming@0.1.0: {}
+
+ yaml@2.9.0: {}
+
+ zod-to-json-schema@3.25.2(zod@4.4.3):
+ dependencies:
+ zod: 4.4.3
+
+ zod@4.4.3: {}
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.impl.mjs b/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.impl.mjs
new file mode 100644
index 000000000..2d02a1146
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.impl.mjs
@@ -0,0 +1,354 @@
+import { wrapStrandsAgentSDK } from "braintrust";
+import {
+ collectAsync,
+ runOperation,
+ runTracedScenario,
+} from "../../helpers/provider-runtime.mjs";
+
+const MODEL_NAME = "gpt-4o-mini-2024-07-18";
+const ROOT_NAME = "strands-agent-sdk-instrumentation-root";
+const SCENARIO_NAME = "strands-agent-sdk-instrumentation";
+
+function createOpenAIModel(OpenAIModel) {
+ return new OpenAIModel({
+ api: "chat",
+ modelId: MODEL_NAME,
+ temperature: 0,
+ maxTokens: 160,
+ ...(process.env.OPENAI_BASE_URL
+ ? { clientConfig: { baseURL: process.env.OPENAI_BASE_URL } }
+ : {}),
+ });
+}
+
+function resultText(result) {
+ return typeof result?.toString === "function" &&
+ result.toString !== Object.prototype.toString
+ ? result.toString()
+ : JSON.stringify(result);
+}
+
+function expectContains(value, marker, label) {
+ const text = resultText(value);
+ if (!text.includes(marker)) {
+ throw new Error(`${label} did not include ${marker}: ${text}`);
+ }
+}
+
+function createOverlappingParentSDK() {
+ class Agent {
+ constructor(options = {}) {
+ Object.assign(this, options);
+ }
+
+ async *stream(input) {
+ const result = {
+ type: "agentResult",
+ stopReason: "endTurn",
+ lastMessage: {
+ role: "assistant",
+ content: [
+ {
+ text: `OVERLAP_AGENT_OK ${input}`,
+ },
+ ],
+ },
+ metrics: {
+ accumulatedUsage: {
+ inputTokens: 1,
+ outputTokens: 1,
+ totalTokens: 2,
+ },
+ },
+ };
+ yield { type: "agentResultEvent", result };
+ return result;
+ }
+ }
+
+ class Graph {
+ constructor(options = {}) {
+ this.id = options.id;
+ this.nodes = new Map(
+ (options.nodes ?? []).map((node) => [node.id, node]),
+ );
+ }
+
+ async *stream() {
+ yield {
+ type: "beforeNodeCallEvent",
+ nodeId: "overlap-node-a",
+ orchestrator: this,
+ };
+ yield {
+ type: "beforeNodeCallEvent",
+ nodeId: "overlap-node-b",
+ orchestrator: this,
+ };
+ yield {
+ type: "nodeResultEvent",
+ nodeId: "overlap-node-b",
+ nodeType: "agentNode",
+ result: {
+ nodeId: "overlap-node-b",
+ status: "COMPLETED",
+ content: [{ text: "OVERLAP_NODE_B_OK" }],
+ duration: 1,
+ },
+ };
+ yield {
+ type: "afterNodeCallEvent",
+ nodeId: "overlap-node-b",
+ orchestrator: this,
+ };
+ yield {
+ type: "nodeResultEvent",
+ nodeId: "overlap-node-a",
+ nodeType: "agentNode",
+ result: {
+ nodeId: "overlap-node-a",
+ status: "COMPLETED",
+ content: [{ text: "OVERLAP_NODE_A_OK" }],
+ duration: 1,
+ },
+ };
+ yield {
+ type: "afterNodeCallEvent",
+ nodeId: "overlap-node-a",
+ orchestrator: this,
+ };
+
+ const result = {
+ type: "multiAgentResult",
+ status: "COMPLETED",
+ content: [{ text: "OVERLAP_GRAPH_OK" }],
+ duration: 2,
+ };
+ yield { type: "multiAgentResultEvent", result };
+ return result;
+ }
+ }
+
+ class Swarm extends Graph {}
+
+ return { Agent, Graph, Swarm };
+}
+
+async function runOverlappingParentProbe() {
+ const strands = wrapStrandsAgentSDK(createOverlappingParentSDK());
+ const sharedAgent = new strands.Agent({
+ id: "overlap-agent",
+ name: "overlap-agent",
+ });
+ const graph = new strands.Graph({
+ id: "overlap-graph",
+ nodes: [
+ {
+ id: "overlap-node-a",
+ type: "agentNode",
+ agent: sharedAgent,
+ },
+ {
+ id: "overlap-node-b",
+ type: "agentNode",
+ agent: sharedAgent,
+ },
+ ],
+ });
+ const graphIterator = graph.stream("overlap graph input");
+ const graphEvents = [];
+
+ graphEvents.push((await graphIterator.next()).value);
+ graphEvents.push((await graphIterator.next()).value);
+
+ const agentEvents = await collectAsync(
+ sharedAgent.stream("overlap-node-a work"),
+ );
+
+ while (true) {
+ const step = await graphIterator.next();
+ graphEvents.push(step.value);
+ if (step.done) {
+ break;
+ }
+ }
+
+ if (!JSON.stringify(agentEvents).includes("OVERLAP_AGENT_OK")) {
+ throw new Error("overlap agent stream did not include OVERLAP_AGENT_OK");
+ }
+ if (!JSON.stringify(graphEvents).includes("OVERLAP_GRAPH_OK")) {
+ throw new Error("overlap graph stream did not include OVERLAP_GRAPH_OK");
+ }
+}
+
+async function runStrandsAgentSDKInstrumentationScenario(
+ sdk,
+ OpenAIModel,
+ options = {},
+) {
+ const strands = options.decorateSDK ? options.decorateSDK(sdk) : sdk;
+ const { Agent, Graph, Swarm, tool } = strands;
+ const lookupWeather = tool({
+ name: "lookup_weather",
+ description: "Return a deterministic weather report for one city.",
+ inputSchema: {
+ type: "object",
+ properties: {
+ city: { type: "string" },
+ },
+ required: ["city"],
+ },
+ callback: ({ city }) => ({
+ city,
+ forecast: "sunny",
+ marker: "STRANDS_TOOL_OK",
+ }),
+ });
+
+ await runTracedScenario({
+ callback: async () => {
+ await runOperation(
+ "strands-agent-invoke-operation",
+ "agent-invoke",
+ async () => {
+ const agent = new Agent({
+ id: "weather-agent",
+ name: "weather-agent",
+ model: createOpenAIModel(OpenAIModel),
+ printer: false,
+ systemPrompt:
+ "You are a deterministic test agent. Always call lookup_weather exactly once for Vienna, then answer exactly STRANDS_AGENT_TOOL_OK.",
+ tools: [lookupWeather],
+ });
+ const result = await agent.invoke(
+ "Use lookup_weather for Vienna, then answer exactly STRANDS_AGENT_TOOL_OK.",
+ );
+
+ expectContains(result, "STRANDS_AGENT_TOOL_OK", "agent invoke");
+ },
+ );
+
+ await runOperation(
+ "strands-agent-stream-operation",
+ "agent-stream",
+ async () => {
+ const agent = new Agent({
+ id: "stream-agent",
+ name: "stream-agent",
+ model: createOpenAIModel(OpenAIModel),
+ printer: false,
+ systemPrompt: "Reply exactly STRANDS_AGENT_STREAM_OK.",
+ });
+ const events = await collectAsync(
+ agent.stream("Reply exactly STRANDS_AGENT_STREAM_OK."),
+ );
+
+ if (!JSON.stringify(events).includes("STRANDS_AGENT_STREAM_OK")) {
+ throw new Error(
+ "agent stream did not include STRANDS_AGENT_STREAM_OK",
+ );
+ }
+ },
+ );
+
+ await runOperation(
+ "strands-graph-invoke-operation",
+ "graph-invoke",
+ async () => {
+ const researcher = new Agent({
+ id: "graph-researcher",
+ name: "graph-researcher",
+ model: createOpenAIModel(OpenAIModel),
+ printer: false,
+ systemPrompt: "Reply exactly GRAPH_RESEARCH_DONE.",
+ });
+ const writer = new Agent({
+ id: "graph-writer",
+ name: "graph-writer",
+ model: createOpenAIModel(OpenAIModel),
+ printer: false,
+ systemPrompt: "Reply exactly STRANDS_GRAPH_OK.",
+ });
+ const graph = new Graph({
+ id: "weather-graph",
+ maxSteps: 3,
+ nodes: [researcher, writer],
+ edges: [["graph-researcher", "graph-writer"]],
+ });
+ const result = await graph.invoke(
+ "Have the first node finish, then have the writer answer exactly STRANDS_GRAPH_OK.",
+ );
+
+ expectContains(result, "STRANDS_GRAPH_OK", "graph invoke");
+ },
+ );
+
+ if (options.includeOverlapParentProbe) {
+ await runOperation(
+ "strands-overlap-parent-operation",
+ "overlap-parent",
+ runOverlappingParentProbe,
+ );
+ }
+
+ await runOperation(
+ "strands-swarm-invoke-operation",
+ "swarm-invoke",
+ async () => {
+ const router = new Agent({
+ id: "swarm-router",
+ name: "swarm-router",
+ description: "Routes work to the finisher agent.",
+ model: createOpenAIModel(OpenAIModel),
+ printer: false,
+ systemPrompt:
+ "Use the structured output schema. Hand off to swarm-finisher with message finish with STRANDS_SWARM_OK.",
+ });
+ const finisher = new Agent({
+ id: "swarm-finisher",
+ name: "swarm-finisher",
+ description: "Finishes the test response.",
+ model: createOpenAIModel(OpenAIModel),
+ printer: false,
+ systemPrompt:
+ "Use the structured output schema. Do not hand off. Set the final message to exactly STRANDS_SWARM_OK.",
+ });
+ const swarm = new Swarm({
+ id: "weather-swarm",
+ maxSteps: 2,
+ nodes: [router, finisher],
+ start: "swarm-router",
+ });
+ const result = await swarm.invoke(
+ "Route to the finisher, then finish exactly STRANDS_SWARM_OK.",
+ );
+
+ expectContains(result, "STRANDS_SWARM_OK", "swarm invoke");
+ },
+ );
+ },
+ flushCount: 2,
+ flushDelayMs: 250,
+ metadata: {
+ scenario: SCENARIO_NAME,
+ },
+ projectNameBase: "e2e-strands-agent-sdk-instrumentation",
+ rootName: ROOT_NAME,
+ });
+}
+
+export async function runWrappedStrandsAgentSDKInstrumentation(
+ sdk,
+ OpenAIModel,
+) {
+ await runStrandsAgentSDKInstrumentationScenario(sdk, OpenAIModel, {
+ decorateSDK: wrapStrandsAgentSDK,
+ includeOverlapParentProbe: true,
+ });
+}
+
+export async function runAutoStrandsAgentSDKInstrumentation(sdk, OpenAIModel) {
+ await runStrandsAgentSDKInstrumentationScenario(sdk, OpenAIModel);
+}
+
+export { MODEL_NAME, ROOT_NAME, SCENARIO_NAME };
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.mjs b/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.mjs
new file mode 100644
index 000000000..6cddc7644
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.mjs
@@ -0,0 +1,6 @@
+import * as strands from "@strands-agents/sdk";
+import { OpenAIModel } from "@strands-agents/sdk/models/openai";
+import { runMain } from "../../helpers/provider-runtime.mjs";
+import { runAutoStrandsAgentSDKInstrumentation } from "./scenario.impl.mjs";
+
+runMain(() => runAutoStrandsAgentSDKInstrumentation(strands, OpenAIModel));
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.test.ts b/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.test.ts
new file mode 100644
index 000000000..81bdac2c3
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.test.ts
@@ -0,0 +1,66 @@
+import { describe } from "vitest";
+import {
+ prepareScenarioDir,
+ readInstalledPackageVersion,
+ resolveScenarioDir,
+} from "../../helpers/scenario-harness";
+import { defineStrandsAgentSDKInstrumentationAssertions } from "./assertions";
+
+const originalScenarioDir = resolveScenarioDir(import.meta.url);
+const scenarioDir = await prepareScenarioDir({
+ scenarioDir: originalScenarioDir,
+});
+const TIMEOUT_MS = 240_000;
+const strandsAgentSDKScenario = {
+ autoEntry: "scenario.mjs",
+ autoSnapshotName: "strands-agent-sdk-v160-auto-hook",
+ version: await readInstalledPackageVersion(
+ scenarioDir,
+ "@strands-agents/sdk",
+ ),
+ wrapperEntry: "scenario.ts",
+ wrapperSnapshotName: "strands-agent-sdk-v160-wrapped",
+ variantKey: "strands-agent-sdk-v160",
+};
+
+describe("wrapped instrumentation", () => {
+ defineStrandsAgentSDKInstrumentationAssertions({
+ expectOverlapParentProbe: true,
+ name: `Strands Agent SDK ${strandsAgentSDKScenario.version}`,
+ runScenario: async ({ runScenarioDir }) => {
+ await runScenarioDir({
+ entry: strandsAgentSDKScenario.wrapperEntry,
+ runContext: {
+ variantKey: strandsAgentSDKScenario.variantKey,
+ originalScenarioDir,
+ },
+ scenarioDir,
+ timeoutMs: TIMEOUT_MS,
+ });
+ },
+ snapshotName: strandsAgentSDKScenario.wrapperSnapshotName,
+ testFileUrl: import.meta.url,
+ timeoutMs: TIMEOUT_MS,
+ });
+});
+
+describe("auto-hook instrumentation", () => {
+ defineStrandsAgentSDKInstrumentationAssertions({
+ name: `Strands Agent SDK ${strandsAgentSDKScenario.version}`,
+ runScenario: async ({ runNodeScenarioDir }) => {
+ await runNodeScenarioDir({
+ entry: strandsAgentSDKScenario.autoEntry,
+ nodeArgs: ["--import", "braintrust/hook.mjs"],
+ runContext: {
+ variantKey: strandsAgentSDKScenario.variantKey,
+ originalScenarioDir,
+ },
+ scenarioDir,
+ timeoutMs: TIMEOUT_MS,
+ });
+ },
+ snapshotName: strandsAgentSDKScenario.autoSnapshotName,
+ testFileUrl: import.meta.url,
+ timeoutMs: TIMEOUT_MS,
+ });
+});
diff --git a/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.ts b/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.ts
new file mode 100644
index 000000000..be2d50c8c
--- /dev/null
+++ b/e2e/scenarios/strands-agent-sdk-instrumentation/scenario.ts
@@ -0,0 +1,6 @@
+import * as strands from "@strands-agents/sdk";
+import { OpenAIModel } from "@strands-agents/sdk/models/openai";
+import { runMain } from "../../helpers/provider-runtime.mjs";
+import { runWrappedStrandsAgentSDKInstrumentation } from "./scenario.impl.mjs";
+
+runMain(() => runWrappedStrandsAgentSDKInstrumentation(strands, OpenAIModel));
diff --git a/js/src/auto-instrumentations/configs/all.ts b/js/src/auto-instrumentations/configs/all.ts
index ad179de34..da543ee51 100644
--- a/js/src/auto-instrumentations/configs/all.ts
+++ b/js/src/auto-instrumentations/configs/all.ts
@@ -24,6 +24,7 @@ import { openAICodexConfigs } from "./openai-codex";
import { openRouterConfigs } from "./openrouter";
import { openRouterAgentConfigs } from "./openrouter-agent";
import { piCodingAgentConfigs } from "./pi-coding-agent";
+import { strandsAgentSDKConfigs } from "./strands-agent-sdk";
interface InstrumentationConfigGroup {
integrations: readonly (keyof InstrumentationIntegrationsConfig)[];
@@ -81,6 +82,10 @@ const defaultInstrumentationConfigGroups: readonly InstrumentationConfigGroup[]
integrations: ["piCodingAgent"],
configs: piCodingAgentConfigs,
},
+ {
+ integrations: ["strandsAgentSDK"],
+ configs: strandsAgentSDKConfigs,
+ },
{
integrations: ["flue"],
configs: flueConfigs,
diff --git a/js/src/auto-instrumentations/configs/strands-agent-sdk.test.ts b/js/src/auto-instrumentations/configs/strands-agent-sdk.test.ts
new file mode 100644
index 000000000..58dcff867
--- /dev/null
+++ b/js/src/auto-instrumentations/configs/strands-agent-sdk.test.ts
@@ -0,0 +1,78 @@
+import { describe, expect, it } from "vitest";
+import { readDisabledInstrumentationEnvConfig } from "../../instrumentation/config";
+import { getDefaultInstrumentationConfigs } from "./all";
+import { strandsAgentSDKConfigs } from "./strands-agent-sdk";
+
+const strandsChannelNames = [
+ "Agent.stream",
+ "Graph.stream",
+ "Swarm.stream",
+] as const;
+
+describe("strandsAgentSDKConfigs", () => {
+ it("targets the Strands Agent SDK stream entrypoints", () => {
+ expect(strandsAgentSDKConfigs).toHaveLength(3);
+ expect(strandsAgentSDKConfigs.map((config) => config.channelName)).toEqual(
+ strandsChannelNames,
+ );
+ expect(strandsAgentSDKConfigs).toEqual([
+ expect.objectContaining({
+ module: {
+ name: "@strands-agents/sdk",
+ versionRange: ">=1.0.0 <2.0.0",
+ filePath: "dist/src/agent/agent.js",
+ },
+ functionQuery: {
+ className: "Agent",
+ methodName: "stream",
+ kind: "Sync",
+ },
+ }),
+ expect.objectContaining({
+ module: expect.objectContaining({
+ filePath: "dist/src/multiagent/graph.js",
+ }),
+ functionQuery: expect.objectContaining({
+ className: "Graph",
+ methodName: "stream",
+ }),
+ }),
+ expect.objectContaining({
+ module: expect.objectContaining({
+ filePath: "dist/src/multiagent/swarm.js",
+ }),
+ functionQuery: expect.objectContaining({
+ className: "Swarm",
+ methodName: "stream",
+ }),
+ }),
+ ]);
+ });
+
+ it("is included by default and disabled by Strands Agent SDK integration keys", () => {
+ expect(
+ getDefaultInstrumentationConfigs().some((config) =>
+ strandsChannelNames.includes(config.channelName as never),
+ ),
+ ).toBe(true);
+
+ for (const alias of [
+ "strandsAgentSDK",
+ "strandsagentsdk",
+ "strands-agent-sdk",
+ "@strands-agents/sdk",
+ ]) {
+ const disabledConfig =
+ readDisabledInstrumentationEnvConfig(alias).integrations;
+
+ expect(disabledConfig).toMatchObject({ strandsAgentSDK: false });
+ expect(
+ getDefaultInstrumentationConfigs({
+ disabledIntegrationConfig: disabledConfig,
+ }).some((config) =>
+ strandsChannelNames.includes(config.channelName as never),
+ ),
+ ).toBe(false);
+ }
+ });
+});
diff --git a/js/src/auto-instrumentations/configs/strands-agent-sdk.ts b/js/src/auto-instrumentations/configs/strands-agent-sdk.ts
new file mode 100644
index 000000000..7a7f7a5b9
--- /dev/null
+++ b/js/src/auto-instrumentations/configs/strands-agent-sdk.ts
@@ -0,0 +1,46 @@
+import type { InstrumentationConfig } from "@apm-js-collab/code-transformer";
+import { strandsAgentSDKChannels } from "../../instrumentation/plugins/strands-agent-sdk-channels";
+
+const strandsAgentSDKVersionRange = ">=1.0.0 <2.0.0";
+
+export const strandsAgentSDKConfigs: InstrumentationConfig[] = [
+ {
+ channelName: strandsAgentSDKChannels.agentStream.channelName,
+ module: {
+ name: "@strands-agents/sdk",
+ versionRange: strandsAgentSDKVersionRange,
+ filePath: "dist/src/agent/agent.js",
+ },
+ functionQuery: {
+ className: "Agent",
+ methodName: "stream",
+ kind: "Sync",
+ },
+ },
+ {
+ channelName: strandsAgentSDKChannels.graphStream.channelName,
+ module: {
+ name: "@strands-agents/sdk",
+ versionRange: strandsAgentSDKVersionRange,
+ filePath: "dist/src/multiagent/graph.js",
+ },
+ functionQuery: {
+ className: "Graph",
+ methodName: "stream",
+ kind: "Sync",
+ },
+ },
+ {
+ channelName: strandsAgentSDKChannels.swarmStream.channelName,
+ module: {
+ name: "@strands-agents/sdk",
+ versionRange: strandsAgentSDKVersionRange,
+ filePath: "dist/src/multiagent/swarm.js",
+ },
+ functionQuery: {
+ className: "Swarm",
+ methodName: "stream",
+ kind: "Sync",
+ },
+ },
+];
diff --git a/js/src/exports.ts b/js/src/exports.ts
index 54cd03e2a..c6c4572fb 100644
--- a/js/src/exports.ts
+++ b/js/src/exports.ts
@@ -185,6 +185,7 @@ export { wrapClaudeAgentSDK } from "./wrappers/claude-agent-sdk/claude-agent-sdk
export { wrapOpenAICodexSDK } from "./wrappers/openai-codex";
export { wrapCursorSDK } from "./wrappers/cursor-sdk";
export { wrapPiCodingAgentSDK } from "./wrappers/pi-coding-agent";
+export { wrapStrandsAgentSDK } from "./wrappers/strands-agent-sdk";
export { wrapGoogleGenAI } from "./wrappers/google-genai";
export { wrapGoogleADK } from "./wrappers/google-adk";
export { wrapGenkit } from "./wrappers/genkit";
diff --git a/js/src/instrumentation/braintrust-plugin.test.ts b/js/src/instrumentation/braintrust-plugin.test.ts
index 34ac282af..394a6b0f7 100644
--- a/js/src/instrumentation/braintrust-plugin.test.ts
+++ b/js/src/instrumentation/braintrust-plugin.test.ts
@@ -16,6 +16,7 @@ import { GroqPlugin } from "./plugins/groq-plugin";
import { GitHubCopilotPlugin } from "./plugins/github-copilot-plugin";
import { LangChainPlugin } from "./plugins/langchain-plugin";
import { PiCodingAgentPlugin } from "./plugins/pi-coding-agent-plugin";
+import { StrandsAgentSDKPlugin } from "./plugins/strands-agent-sdk-plugin";
function createPluginClassMock() {
return vi.fn(function MockPlugin(this: {
@@ -101,6 +102,10 @@ vi.mock("./plugins/pi-coding-agent-plugin", () => ({
PiCodingAgentPlugin: createPluginClassMock(),
}));
+vi.mock("./plugins/strands-agent-sdk-plugin", () => ({
+ StrandsAgentSDKPlugin: createPluginClassMock(),
+}));
+
describe("BraintrustPlugin", () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -245,6 +250,16 @@ describe("BraintrustPlugin", () => {
expect(mockInstance.enable).toHaveBeenCalledTimes(1);
});
+ it("should create and enable Strands Agent SDK plugin by default", () => {
+ const plugin = new BraintrustPlugin();
+ plugin.enable();
+
+ expect(StrandsAgentSDKPlugin).toHaveBeenCalledTimes(1);
+ const mockInstance = vi.mocked(StrandsAgentSDKPlugin).mock.results[0]
+ .value;
+ expect(mockInstance.enable).toHaveBeenCalledTimes(1);
+ });
+
it("should create all plugins when enabled with no config", () => {
const plugin = new BraintrustPlugin();
plugin.enable();
@@ -263,6 +278,7 @@ describe("BraintrustPlugin", () => {
expect(CoherePlugin).toHaveBeenCalledTimes(1);
expect(GroqPlugin).toHaveBeenCalledTimes(1);
expect(GitHubCopilotPlugin).toHaveBeenCalledTimes(1);
+ expect(StrandsAgentSDKPlugin).toHaveBeenCalledTimes(1);
expect(LangChainPlugin).toHaveBeenCalledTimes(1);
});
@@ -284,6 +300,7 @@ describe("BraintrustPlugin", () => {
expect(CoherePlugin).toHaveBeenCalledTimes(1);
expect(GroqPlugin).toHaveBeenCalledTimes(1);
expect(GitHubCopilotPlugin).toHaveBeenCalledTimes(1);
+ expect(StrandsAgentSDKPlugin).toHaveBeenCalledTimes(1);
expect(LangChainPlugin).toHaveBeenCalledTimes(1);
});
@@ -304,6 +321,7 @@ describe("BraintrustPlugin", () => {
expect(MistralPlugin).toHaveBeenCalledTimes(1);
expect(CoherePlugin).toHaveBeenCalledTimes(1);
expect(GroqPlugin).toHaveBeenCalledTimes(1);
+ expect(StrandsAgentSDKPlugin).toHaveBeenCalledTimes(1);
expect(LangChainPlugin).toHaveBeenCalledTimes(1);
});
});
@@ -563,6 +581,7 @@ describe("BraintrustPlugin", () => {
gitHubCopilot: false,
langchain: false,
piCodingAgent: false,
+ strandsAgentSDK: false,
},
});
plugin.enable();
@@ -583,6 +602,7 @@ describe("BraintrustPlugin", () => {
expect(GitHubCopilotPlugin).not.toHaveBeenCalled();
expect(LangChainPlugin).not.toHaveBeenCalled();
expect(PiCodingAgentPlugin).not.toHaveBeenCalled();
+ expect(StrandsAgentSDKPlugin).not.toHaveBeenCalled();
});
it("should not create Pi Coding Agent plugin when piCodingAgent: false", () => {
@@ -596,6 +616,16 @@ describe("BraintrustPlugin", () => {
expect(AnthropicPlugin).toHaveBeenCalledTimes(1);
});
+ it("should not create Strands Agent SDK plugin when strandsAgentSDK: false", () => {
+ const plugin = new BraintrustPlugin({
+ integrations: { strandsAgentSDK: false },
+ });
+ plugin.enable();
+
+ expect(StrandsAgentSDKPlugin).not.toHaveBeenCalled();
+ expect(OpenAIPlugin).toHaveBeenCalledTimes(1);
+ });
+
it("should allow selective enabling of plugins", () => {
const plugin = new BraintrustPlugin({
integrations: {
@@ -751,6 +781,8 @@ describe("BraintrustPlugin", () => {
const groqMock = vi.mocked(GroqPlugin).mock.results[0].value;
const piCodingAgentMock =
vi.mocked(PiCodingAgentPlugin).mock.results[0].value;
+ const strandsAgentSDKMock = vi.mocked(StrandsAgentSDKPlugin).mock
+ .results[0].value;
const langChainMock = vi.mocked(LangChainPlugin).mock.results[0].value;
expect(openaiMock.enable).toHaveBeenCalledTimes(1);
@@ -767,6 +799,7 @@ describe("BraintrustPlugin", () => {
expect(cohereMock.enable).toHaveBeenCalledTimes(1);
expect(groqMock.enable).toHaveBeenCalledTimes(1);
expect(piCodingAgentMock.enable).toHaveBeenCalledTimes(1);
+ expect(strandsAgentSDKMock.enable).toHaveBeenCalledTimes(1);
expect(langChainMock.enable).toHaveBeenCalledTimes(1);
});
@@ -795,6 +828,8 @@ describe("BraintrustPlugin", () => {
const groqMock = vi.mocked(GroqPlugin).mock.results[0].value;
const piCodingAgentMock =
vi.mocked(PiCodingAgentPlugin).mock.results[0].value;
+ const strandsAgentSDKMock = vi.mocked(StrandsAgentSDKPlugin).mock
+ .results[0].value;
const langChainMock = vi.mocked(LangChainPlugin).mock.results[0].value;
plugin.disable();
@@ -813,6 +848,7 @@ describe("BraintrustPlugin", () => {
expect(cohereMock.disable).toHaveBeenCalledTimes(1);
expect(groqMock.disable).toHaveBeenCalledTimes(1);
expect(piCodingAgentMock.disable).toHaveBeenCalledTimes(1);
+ expect(strandsAgentSDKMock.disable).toHaveBeenCalledTimes(1);
expect(langChainMock.disable).toHaveBeenCalledTimes(1);
});
@@ -861,6 +897,7 @@ describe("BraintrustPlugin", () => {
expect(CoherePlugin).not.toHaveBeenCalled();
expect(GroqPlugin).not.toHaveBeenCalled();
expect(PiCodingAgentPlugin).not.toHaveBeenCalled();
+ expect(StrandsAgentSDKPlugin).not.toHaveBeenCalled();
});
it("should allow re-enabling after disable", () => {
@@ -886,6 +923,7 @@ describe("BraintrustPlugin", () => {
expect(CoherePlugin).toHaveBeenCalledTimes(1);
expect(GroqPlugin).toHaveBeenCalledTimes(1);
expect(PiCodingAgentPlugin).toHaveBeenCalledTimes(1);
+ expect(StrandsAgentSDKPlugin).toHaveBeenCalledTimes(1);
expect(LangChainPlugin).toHaveBeenCalledTimes(1);
});
diff --git a/js/src/instrumentation/braintrust-plugin.ts b/js/src/instrumentation/braintrust-plugin.ts
index 36e7e83f0..87e8687bf 100644
--- a/js/src/instrumentation/braintrust-plugin.ts
+++ b/js/src/instrumentation/braintrust-plugin.ts
@@ -19,19 +19,13 @@ import { GitHubCopilotPlugin } from "./plugins/github-copilot-plugin";
import { FluePlugin } from "./plugins/flue-plugin";
import { LangChainPlugin } from "./plugins/langchain-plugin";
import { PiCodingAgentPlugin } from "./plugins/pi-coding-agent-plugin";
+import { StrandsAgentSDKPlugin } from "./plugins/strands-agent-sdk-plugin";
import type { InstrumentationIntegrationsConfig } from "./config";
export interface BraintrustPluginConfig {
integrations?: InstrumentationIntegrationsConfig;
}
-function getIntegrationConfig(
- integrations: NonNullable,
- key: string,
-): boolean | undefined {
- return (integrations as Record)[key];
-}
-
/**
* Default Braintrust plugin that manages all AI provider instrumentation plugins.
*
@@ -71,6 +65,7 @@ export class BraintrustPlugin extends BasePlugin {
private fluePlugin: FluePlugin | null = null;
private langChainPlugin: LangChainPlugin | null = null;
private piCodingAgentPlugin: PiCodingAgentPlugin | null = null;
+ private strandsAgentSDKPlugin: StrandsAgentSDKPlugin | null = null;
constructor(config: BraintrustPluginConfig = {}) {
super();
@@ -179,7 +174,12 @@ export class BraintrustPlugin extends BasePlugin {
this.piCodingAgentPlugin.enable();
}
- if (getIntegrationConfig(integrations, "flue") !== false) {
+ if (integrations.strandsAgentSDK !== false) {
+ this.strandsAgentSDKPlugin = new StrandsAgentSDKPlugin();
+ this.strandsAgentSDKPlugin.enable();
+ }
+
+ if (integrations.flue !== false) {
this.fluePlugin = new FluePlugin();
this.fluePlugin.enable();
}
@@ -287,6 +287,11 @@ export class BraintrustPlugin extends BasePlugin {
this.piCodingAgentPlugin = null;
}
+ if (this.strandsAgentSDKPlugin) {
+ this.strandsAgentSDKPlugin.disable();
+ this.strandsAgentSDKPlugin = null;
+ }
+
if (this.fluePlugin) {
this.fluePlugin.disable();
this.fluePlugin = null;
diff --git a/js/src/instrumentation/config.ts b/js/src/instrumentation/config.ts
index 299b2b827..54abe9cf4 100644
--- a/js/src/instrumentation/config.ts
+++ b/js/src/instrumentation/config.ts
@@ -22,6 +22,7 @@ export interface InstrumentationIntegrationsConfig {
gitHubCopilot?: boolean;
openaiCodexSDK?: boolean;
piCodingAgent?: boolean;
+ strandsAgentSDK?: boolean;
langchain?: boolean;
langgraph?: boolean;
}
@@ -49,6 +50,10 @@ const envIntegrationAliases: Record<
picodingagent: "piCodingAgent",
picodingagentsdk: "piCodingAgent",
"@earendil-works/pi-coding-agent": "piCodingAgent",
+ strandsAgentSDK: "strandsAgentSDK",
+ strandsagentsdk: "strandsAgentSDK",
+ "strands-agent-sdk": "strandsAgentSDK",
+ "@strands-agents/sdk": "strandsAgentSDK",
anthropic: "anthropic",
aisdk: "aisdk",
"ai-sdk": "aisdk",
@@ -120,6 +125,7 @@ export function getDefaultInstrumentationIntegrations(): Record<
langchain: true,
langgraph: true,
piCodingAgent: true,
+ strandsAgentSDK: true,
};
}
@@ -130,9 +136,12 @@ export function readDisabledInstrumentationEnvConfig(
if (disabledList) {
for (const value of disabledList.split(",")) {
- const sdk = value.trim().toLowerCase();
+ const rawSdk = value.trim();
+ const sdk = rawSdk.toLowerCase();
if (sdk.length > 0) {
- integrations[envIntegrationAliases[sdk] ?? sdk] = false;
+ integrations[
+ envIntegrationAliases[rawSdk] ?? envIntegrationAliases[sdk] ?? sdk
+ ] = false;
}
}
}
diff --git a/js/src/instrumentation/core/index.ts b/js/src/instrumentation/core/index.ts
index c5afe288a..0189111f2 100644
--- a/js/src/instrumentation/core/index.ts
+++ b/js/src/instrumentation/core/index.ts
@@ -10,6 +10,7 @@
*/
export { BasePlugin } from "./plugin";
+export { toLoggedError } from "./logging";
export {
createChannelName,
parseChannelName,
diff --git a/js/src/instrumentation/core/logging.test.ts b/js/src/instrumentation/core/logging.test.ts
new file mode 100644
index 000000000..706d9bd50
--- /dev/null
+++ b/js/src/instrumentation/core/logging.test.ts
@@ -0,0 +1,25 @@
+import { describe, expect, it } from "vitest";
+import { toLoggedError } from "./logging";
+
+describe("toLoggedError", () => {
+ it("returns Error messages", () => {
+ expect(toLoggedError(new Error("failed"))).toBe("failed");
+ });
+
+ it("preserves string throws", () => {
+ expect(toLoggedError("failed")).toBe("failed");
+ });
+
+ it("serializes non-Error thrown values", () => {
+ expect(toLoggedError({ code: "bad_request" })).toBe(
+ '{"code":"bad_request"}',
+ );
+ });
+
+ it("falls back when non-Error thrown values are not JSON serializable", () => {
+ const circular: Record = {};
+ circular.self = circular;
+
+ expect(toLoggedError(circular)).toBe("[object Object]");
+ });
+});
diff --git a/js/src/instrumentation/core/logging.ts b/js/src/instrumentation/core/logging.ts
new file mode 100644
index 000000000..00598016b
--- /dev/null
+++ b/js/src/instrumentation/core/logging.ts
@@ -0,0 +1,19 @@
+export function toLoggedError(error: unknown): string {
+ if (error instanceof Error) {
+ return error.message;
+ }
+ if (typeof error === "string") {
+ return error;
+ }
+
+ try {
+ const serialized = JSON.stringify(error);
+ return serialized === undefined ? String(error) : serialized;
+ } catch {
+ try {
+ return String(error);
+ } catch {
+ return "";
+ }
+ }
+}
diff --git a/js/src/instrumentation/core/stream-patcher.ts b/js/src/instrumentation/core/stream-patcher.ts
index 90f7a5a95..6796f5ccb 100644
--- a/js/src/instrumentation/core/stream-patcher.ts
+++ b/js/src/instrumentation/core/stream-patcher.ts
@@ -48,6 +48,12 @@ export interface StreamPatchOptions {
* Default: collect all chunks.
*/
shouldCollect?: (chunk: TChunk) => boolean;
+
+ /**
+ * Optional wrapper around iterator.next(). Used by integrations that need to
+ * preserve async context while the producer advances.
+ */
+ aroundNext?: (callback: () => PromiseLike) => PromiseLike | T;
}
type AsyncIteratorLike = AsyncIterable &
@@ -155,7 +161,9 @@ export function patchStreamIfNeeded(
stream.next = async (...args: [] | [undefined]) => {
try {
- const result = await originalNext(...args);
+ const result = await runNextWithWrapper(options, () =>
+ originalNext(...args),
+ );
if (result.done) {
if (!completed) {
@@ -276,7 +284,9 @@ export function patchStreamIfNeeded(
// Patch the next() method
iterator.next = async function (...args: [] | [undefined]) {
try {
- const result = await originalNext(...args);
+ const result = await runNextWithWrapper(options, () =>
+ originalNext(...args),
+ );
if (result.done) {
// Stream completed successfully
@@ -396,6 +406,13 @@ export function patchStreamIfNeeded(
}
}
+function runNextWithWrapper(
+ options: StreamPatchOptions,
+ callback: () => PromiseLike,
+): PromiseLike | T {
+ return options.aroundNext ? options.aroundNext(callback) : callback();
+}
+
/**
* Higher-level helper for common pattern: collect chunks and process on completion.
*
diff --git a/js/src/instrumentation/plugins/ai-sdk-plugin.ts b/js/src/instrumentation/plugins/ai-sdk-plugin.ts
index 749b6fb70..114b06c43 100644
--- a/js/src/instrumentation/plugins/ai-sdk-plugin.ts
+++ b/js/src/instrumentation/plugins/ai-sdk-plugin.ts
@@ -1,4 +1,4 @@
-import { BasePlugin } from "../core";
+import { BasePlugin, toLoggedError } from "../core";
import {
traceAsyncChannel,
traceStreamingChannel,
@@ -1361,9 +1361,7 @@ function prepareAISDKChildTracing(
}
span.log({ output: lastValue });
} catch (error) {
- span.log({
- error: error instanceof Error ? error.message : String(error),
- });
+ span.log({ error: toLoggedError(error) });
throw error;
} finally {
span.end();
diff --git a/js/src/instrumentation/plugins/anthropic-plugin.ts b/js/src/instrumentation/plugins/anthropic-plugin.ts
index c16bb50be..a8454f67c 100644
--- a/js/src/instrumentation/plugins/anthropic-plugin.ts
+++ b/js/src/instrumentation/plugins/anthropic-plugin.ts
@@ -1,4 +1,4 @@
-import { BasePlugin } from "../core";
+import { BasePlugin, toLoggedError } from "../core";
import { traceStreamingChannel, unsubscribeAll } from "../core/channel-tracing";
import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher";
import { Attachment, startSpan, withCurrent } from "../../logger";
@@ -277,10 +277,6 @@ function extractAnthropicToolNames(tools: unknown[]): string[] {
return toolNames;
}
-function toErrorMessage(error: unknown): string {
- return error instanceof Error ? error.message : String(error);
-}
-
function getAnthropicToolRunnerInput(args: unknown[]): unknown {
return args.length > 0 ? args[0] : undefined;
}
@@ -339,7 +335,7 @@ function wrapAnthropicToolRunnerTool(
};
const finalizeError = (error: unknown) => {
- span.log({ error: toErrorMessage(error) });
+ span.log({ error: toLoggedError(error) });
throw error;
};
@@ -555,7 +551,7 @@ function finalizeAnthropicToolRunnerError(
}
state.finalized = true;
state.span.log({
- error: error instanceof Error ? error.message : String(error),
+ error: toLoggedError(error),
});
state.span.end();
}
diff --git a/js/src/instrumentation/plugins/claude-agent-sdk-local-tool-spans.ts b/js/src/instrumentation/plugins/claude-agent-sdk-local-tool-spans.ts
index 1631cc75f..28026770b 100644
--- a/js/src/instrumentation/plugins/claude-agent-sdk-local-tool-spans.ts
+++ b/js/src/instrumentation/plugins/claude-agent-sdk-local-tool-spans.ts
@@ -1,5 +1,6 @@
import { startSpan, withCurrent } from "../../logger";
import { SpanTypeAttribute, isPromiseLike } from "../../../util/index";
+import { toLoggedError } from "../core";
import { getClaudeLocalToolParentResolver } from "./claude-agent-sdk-local-tool-context";
export type LocalToolSpanMetadata = {
@@ -13,10 +14,6 @@ const LOCAL_TOOL_HANDLER_WRAPPED = Symbol.for(
"braintrust.claude_agent_sdk.local_tool_handler_wrapped",
);
-function toErrorMessage(error: unknown): string {
- return error instanceof Error ? error.message : String(error);
-}
-
function getToolUseIdFromExtra(extra: unknown): string | undefined {
if (!extra || typeof extra !== "object" || !("_meta" in extra)) {
return undefined;
@@ -85,7 +82,7 @@ export function wrapLocalClaudeToolHandler(
return result;
};
const finalizeError = (error: unknown) => {
- span.log({ error: toErrorMessage(error) });
+ span.log({ error: toLoggedError(error) });
span.end();
throw error;
};
diff --git a/js/src/instrumentation/plugins/cursor-sdk-plugin.ts b/js/src/instrumentation/plugins/cursor-sdk-plugin.ts
index 5f45bf440..b9347d4f6 100644
--- a/js/src/instrumentation/plugins/cursor-sdk-plugin.ts
+++ b/js/src/instrumentation/plugins/cursor-sdk-plugin.ts
@@ -1,4 +1,4 @@
-import { BasePlugin } from "../core";
+import { BasePlugin, toLoggedError } from "../core";
import type { ChannelMessage } from "../core/channel-definitions";
import type { IsoChannelHandlers } from "../../isomorph";
import { debugLogger } from "../../debug-logger";
@@ -542,7 +542,7 @@ async function handleToolUpdate(
}));
finishToolSpan(toolState, {
- error: toolCall?.status === "error" ? stringifyUnknown(result) : undefined,
+ error: toolCall?.status === "error" ? toLoggedError(result) : undefined,
metadata: {
"cursor_sdk.tool.status": toolCall?.status ?? "completed",
},
@@ -654,7 +654,7 @@ async function handleToolMessage(
}));
finishToolSpan(toolState, {
error:
- message.status === "error" ? stringifyUnknown(message.result) : undefined,
+ message.status === "error" ? toLoggedError(message.result) : undefined,
metadata: {
"cursor_sdk.tool.status": message.status,
},
@@ -722,9 +722,7 @@ async function handleConversationStep(
});
finishToolSpan(toolState, {
error:
- toolCall?.status === "error"
- ? stringifyUnknown(toolCall.result)
- : undefined,
+ toolCall?.status === "error" ? toLoggedError(toolCall.result) : undefined,
metadata: {
"cursor_sdk.tool.status": toolCall?.status ?? "completed",
},
@@ -885,7 +883,7 @@ async function finalizeCursorRun(
};
if (error) {
safeLog(state.span, {
- error: error instanceof Error ? error.message : String(error),
+ error: toLoggedError(error),
metadata,
metrics: {
...cleanMetrics(state.metrics),
@@ -1142,20 +1140,6 @@ function getString(
return typeof value === "string" ? value : undefined;
}
-function stringifyUnknown(value: unknown): string {
- if (value instanceof Error) {
- return value.message;
- }
- if (typeof value === "string") {
- return value;
- }
- try {
- return JSON.stringify(value);
- } catch {
- return String(value);
- }
-}
-
function safeLog(span: Span, event: Parameters[0]): void {
try {
span.log(event);
diff --git a/js/src/instrumentation/plugins/flue-plugin.ts b/js/src/instrumentation/plugins/flue-plugin.ts
index 408c203c3..68f257506 100644
--- a/js/src/instrumentation/plugins/flue-plugin.ts
+++ b/js/src/instrumentation/plugins/flue-plugin.ts
@@ -1,4 +1,4 @@
-import { BasePlugin } from "../core";
+import { BasePlugin, toLoggedError } from "../core";
import type { ChannelMessage } from "../core/channel-definitions";
import type { IsoChannelHandlers } from "../../isomorph";
import {
@@ -380,7 +380,7 @@ class FlueObserveBridge {
if (state) {
safeLog(state.span, {
- ...(event.isError ? { error: errorToString(event.error) } : {}),
+ ...(event.isError ? { error: toLoggedError(event.error) } : {}),
metadata: {
...state.metadata,
...extractEventMetadata(event),
@@ -442,7 +442,7 @@ class FlueObserveBridge {
this.finishPendingChildrenForOperation(event, output);
safeLog(state.span, {
- ...(event.isError ? { error: errorToString(event.error) } : {}),
+ ...(event.isError ? { error: toLoggedError(event.error) } : {}),
metadata,
metrics: durationMetrics(event.durationMs),
output,
@@ -510,7 +510,7 @@ class FlueObserveBridge {
};
safeLog(state.span, {
- ...(event.isError ? { error: errorToString(event.error) } : {}),
+ ...(event.isError ? { error: toLoggedError(event.error) } : {}),
metadata,
metrics: {
...durationMetrics(event.durationMs),
@@ -566,7 +566,7 @@ class FlueObserveBridge {
};
safeLog(state.span, {
- ...(event.isError ? { error: errorToString(event.result) } : {}),
+ ...(event.isError ? { error: toLoggedError(event.result) } : {}),
metadata,
metrics: durationMetrics(event.durationMs),
output: event.result,
@@ -609,7 +609,7 @@ class FlueObserveBridge {
}
safeLog(state.span, {
- ...(event.isError ? { error: errorToString(event.result) } : {}),
+ ...(event.isError ? { error: toLoggedError(event.result) } : {}),
metadata: {
...state.metadata,
...extractEventMetadata(event),
@@ -1124,20 +1124,6 @@ function safeEnd(span: Span, endTime: number | undefined): void {
}
}
-function errorToString(error: unknown): string {
- if (error instanceof Error) {
- return error.message;
- }
- if (typeof error === "string") {
- return error;
- }
- try {
- return JSON.stringify(error);
- } catch {
- return String(error);
- }
-}
-
function logInstrumentationError(label: string, error: unknown): void {
// eslint-disable-next-line no-restricted-properties -- preserving intentional console usage.
console.error(`Error in ${label} instrumentation:`, error);
diff --git a/js/src/instrumentation/plugins/genkit-plugin.ts b/js/src/instrumentation/plugins/genkit-plugin.ts
index 82f01c6d9..d58a6192b 100644
--- a/js/src/instrumentation/plugins/genkit-plugin.ts
+++ b/js/src/instrumentation/plugins/genkit-plugin.ts
@@ -1,4 +1,4 @@
-import { BasePlugin } from "../core";
+import { BasePlugin, toLoggedError } from "../core";
import {
traceAsyncChannel,
traceSyncStreamChannel,
@@ -560,7 +560,7 @@ function createDeferredSpanFinalizer(
try {
await callback();
} catch (error) {
- span.log({ error: errorMessage(error) });
+ span.log({ error: toLoggedError(error) });
} finally {
span.end();
}
@@ -759,10 +759,6 @@ function pickNumberMetrics(
);
}
-function errorMessage(error: unknown): string {
- return error instanceof Error ? error.message : String(error);
-}
-
function stringValue(value: unknown): string | undefined {
return typeof value === "string" ? value : undefined;
}
diff --git a/js/src/instrumentation/plugins/openai-codex-plugin.ts b/js/src/instrumentation/plugins/openai-codex-plugin.ts
index 0231c0b8c..402972632 100644
--- a/js/src/instrumentation/plugins/openai-codex-plugin.ts
+++ b/js/src/instrumentation/plugins/openai-codex-plugin.ts
@@ -1,4 +1,4 @@
-import { BasePlugin } from "../core";
+import { BasePlugin, toLoggedError } from "../core";
import type { ChannelMessage } from "../core/channel-definitions";
import type { IsoChannelHandlers } from "../../isomorph";
import { debugLogger } from "../../debug-logger";
@@ -309,9 +309,7 @@ async function finalizeCodexRun(
try {
const error = params.error;
safeLog(state.span, {
- ...(error
- ? { error: error instanceof Error ? error.message : String(error) }
- : {}),
+ ...(error ? { error: toLoggedError(error) } : {}),
metadata: state.metadata,
metrics,
output,
@@ -464,9 +462,7 @@ async function finishActiveLlmSpan(
state.activeLlmSpan = undefined;
const output = buildLlmOutput(active);
safeLog(active.span, {
- ...(error
- ? { error: error instanceof Error ? error.message : String(error) }
- : {}),
+ ...(error ? { error: toLoggedError(error) } : {}),
metadata: active.metadata,
...(output ? { output } : {}),
});
diff --git a/js/src/instrumentation/plugins/openrouter-agent-plugin.ts b/js/src/instrumentation/plugins/openrouter-agent-plugin.ts
index 73e61e8fb..facda63c0 100644
--- a/js/src/instrumentation/plugins/openrouter-agent-plugin.ts
+++ b/js/src/instrumentation/plugins/openrouter-agent-plugin.ts
@@ -1,4 +1,4 @@
-import { BasePlugin } from "../core";
+import { BasePlugin, toLoggedError } from "../core";
import {
traceAsyncChannel,
traceStreamingChannel,
@@ -949,9 +949,7 @@ function patchOpenRouterCallModelResult(args: {
return;
}
ended = true;
- span.log({
- error: normalizeError(error).message,
- });
+ span.log({ error: toLoggedError(error) });
span.end();
};
diff --git a/js/src/instrumentation/plugins/openrouter-plugin.ts b/js/src/instrumentation/plugins/openrouter-plugin.ts
index 6268d8ea3..c89b8f9a7 100644
--- a/js/src/instrumentation/plugins/openrouter-plugin.ts
+++ b/js/src/instrumentation/plugins/openrouter-plugin.ts
@@ -1,4 +1,4 @@
-import { BasePlugin } from "../core";
+import { BasePlugin, toLoggedError } from "../core";
import {
traceAsyncChannel,
traceStreamingChannel,
@@ -1131,9 +1131,7 @@ function patchOpenRouterCallModelResult(args: {
return;
}
ended = true;
- span.log({
- error: normalizeError(error).message,
- });
+ span.log({ error: toLoggedError(error) });
span.end();
};
diff --git a/js/src/instrumentation/plugins/pi-coding-agent-plugin.ts b/js/src/instrumentation/plugins/pi-coding-agent-plugin.ts
index b7e755ce9..aa4ed1fe3 100644
--- a/js/src/instrumentation/plugins/pi-coding-agent-plugin.ts
+++ b/js/src/instrumentation/plugins/pi-coding-agent-plugin.ts
@@ -1,4 +1,4 @@
-import { BasePlugin } from "../core";
+import { BasePlugin, toLoggedError } from "../core";
import type { ChannelMessage } from "../core/channel-definitions";
import iso, {
type IsoAsyncLocalStorage,
@@ -692,7 +692,7 @@ function finishPiToolSpan(
};
try {
safeLog(toolState.span, {
- ...(event.isError ? { error: stringifyUnknown(event.result) } : {}),
+ ...(event.isError ? { error: toLoggedError(event.result) } : {}),
metadata,
output: event.result,
});
@@ -731,7 +731,7 @@ async function finalizePiPromptRun(
};
try {
safeLog(state.span, {
- ...(error ? { error: stringifyUnknown(error) } : {}),
+ ...(error ? { error: toLoggedError(error) } : {}),
metadata,
metrics: {
...cleanMetrics(state.metrics),
@@ -808,7 +808,7 @@ function finishPiLlmSpan(
try {
safeLog(llmState.span, {
...(error || messageError
- ? { error: stringifyUnknown(error ?? messageError) }
+ ? { error: toLoggedError(error ?? messageError) }
: {}),
metadata: {
...llmState.metadata,
@@ -826,7 +826,7 @@ function finishOpenToolSpans(state: PiPromptState, error?: unknown): void {
for (const [, toolState] of state.activeToolSpans) {
try {
safeLog(toolState.span, {
- error: error ? stringifyUnknown(error) : "Pi tool did not complete",
+ error: error ? toLoggedError(error) : "Pi tool did not complete",
});
toolState.span.end();
} finally {
diff --git a/js/src/instrumentation/plugins/strands-agent-sdk-channels.ts b/js/src/instrumentation/plugins/strands-agent-sdk-channels.ts
new file mode 100644
index 000000000..c8246534e
--- /dev/null
+++ b/js/src/instrumentation/plugins/strands-agent-sdk-channels.ts
@@ -0,0 +1,57 @@
+import { channel, defineChannels } from "../core/channel-definitions";
+import type {
+ StrandsAgent,
+ StrandsAgentResult,
+ StrandsAgentStreamEvent,
+ StrandsInvokeArgs,
+ StrandsInvokeOptions,
+ StrandsMultiAgent,
+ StrandsMultiAgentInput,
+ StrandsMultiAgentInvokeOptions,
+ StrandsMultiAgentResult,
+ StrandsMultiAgentStreamEvent,
+} from "../../vendor-sdk-types/strands-agent-sdk";
+
+type StrandsChannelContext = {
+ self?: unknown;
+};
+
+export const strandsAgentSDKChannels = defineChannels("@strands-agents/sdk", {
+ agentStream: channel<
+ [StrandsInvokeArgs, StrandsInvokeOptions | undefined],
+ AsyncGenerator,
+ StrandsChannelContext & { agent?: StrandsAgent },
+ StrandsAgentStreamEvent
+ >({
+ channelName: "Agent.stream",
+ kind: "sync-stream",
+ }),
+
+ graphStream: channel<
+ [StrandsMultiAgentInput, StrandsMultiAgentInvokeOptions | undefined],
+ AsyncGenerator<
+ StrandsMultiAgentStreamEvent,
+ StrandsMultiAgentResult,
+ undefined
+ >,
+ StrandsChannelContext & { orchestrator?: StrandsMultiAgent },
+ StrandsMultiAgentStreamEvent
+ >({
+ channelName: "Graph.stream",
+ kind: "sync-stream",
+ }),
+
+ swarmStream: channel<
+ [StrandsMultiAgentInput, StrandsMultiAgentInvokeOptions | undefined],
+ AsyncGenerator<
+ StrandsMultiAgentStreamEvent,
+ StrandsMultiAgentResult,
+ undefined
+ >,
+ StrandsChannelContext & { orchestrator?: StrandsMultiAgent },
+ StrandsMultiAgentStreamEvent
+ >({
+ channelName: "Swarm.stream",
+ kind: "sync-stream",
+ }),
+});
diff --git a/js/src/instrumentation/plugins/strands-agent-sdk-plugin.test.ts b/js/src/instrumentation/plugins/strands-agent-sdk-plugin.test.ts
new file mode 100644
index 000000000..abc3d1c0e
--- /dev/null
+++ b/js/src/instrumentation/plugins/strands-agent-sdk-plugin.test.ts
@@ -0,0 +1,510 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+const {
+ mockBindStore,
+ mockWithCurrent,
+ mockNewAsyncLocalStorage,
+ mockStartSpan,
+ mockUnbindStore,
+} = vi.hoisted(() => ({
+ mockBindStore: vi.fn(),
+ mockWithCurrent: vi.fn(),
+ mockNewAsyncLocalStorage: vi.fn(() => {
+ let current: unknown;
+ return {
+ enterWith: vi.fn((store: unknown) => {
+ current = store;
+ }),
+ getStore: vi.fn(() => current),
+ run: vi.fn((store: unknown, callback: () => unknown) => {
+ const previous = current;
+ current = store;
+ try {
+ return callback();
+ } finally {
+ current = previous;
+ }
+ }),
+ };
+ }),
+ mockStartSpan: vi.fn(),
+ mockUnbindStore: vi.fn(),
+}));
+
+vi.mock("../../isomorph", () => ({
+ default: {
+ newAsyncLocalStorage: mockNewAsyncLocalStorage,
+ newTracingChannel: vi.fn(),
+ },
+}));
+
+vi.mock("../../logger", () => ({
+ startSpan: (...args: unknown[]) => mockStartSpan(...args),
+ withCurrent: (...args: unknown[]) => mockWithCurrent(...args),
+}));
+
+import iso from "../../isomorph";
+import { isAutoInstrumentationSuppressed } from "../auto-instrumentation-suppression";
+import { StrandsAgentSDKPlugin } from "./strands-agent-sdk-plugin";
+
+const mockNewTracingChannel = iso.newTracingChannel as ReturnType;
+
+describe("StrandsAgentSDKPlugin", () => {
+ let handlersByName: Map;
+ let spans: Array<{
+ args: any;
+ end: ReturnType;
+ log: ReturnType;
+ rootSpanId: string;
+ spanId: string;
+ }>;
+ let currentSpan:
+ | {
+ rootSpanId: string;
+ spanId: string;
+ }
+ | undefined;
+
+ beforeEach(() => {
+ handlersByName = new Map();
+ spans = [];
+ mockNewTracingChannel.mockImplementation((name: string) => ({
+ start: {
+ bindStore: mockBindStore,
+ unbindStore: mockUnbindStore,
+ },
+ subscribe: vi.fn((handlers) => handlersByName.set(name, handlers)),
+ traceSync: vi.fn((fn) => fn()),
+ unsubscribe: vi.fn(),
+ }));
+ currentSpan = undefined;
+ mockWithCurrent.mockImplementation((span: any, callback: () => unknown) => {
+ const previous = currentSpan;
+ currentSpan = span;
+ try {
+ return callback();
+ } finally {
+ currentSpan = previous;
+ }
+ });
+ mockStartSpan.mockImplementation((args: any) => {
+ const parentSpanIds =
+ args.parentSpanIds ??
+ (currentSpan
+ ? {
+ rootSpanId: currentSpan.rootSpanId,
+ spanId: currentSpan.spanId,
+ }
+ : undefined);
+ const effectiveArgs = parentSpanIds ? { ...args, parentSpanIds } : args;
+ const span = {
+ args: effectiveArgs,
+ end: vi.fn(),
+ log: vi.fn(),
+ rootSpanId: parentSpanIds?.rootSpanId ?? `root-${spans.length}`,
+ spanId: `span-${spans.length}`,
+ };
+ if (effectiveArgs.event) {
+ span.log(effectiveArgs.event);
+ }
+ spans.push(span);
+ return span;
+ });
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("subscribes to Strands stream channels and binds suppression", () => {
+ const plugin = new StrandsAgentSDKPlugin();
+ plugin.enable();
+
+ expect(
+ handlersByName.has("orchestrion:@strands-agents/sdk:Agent.stream"),
+ ).toBe(true);
+ expect(
+ handlersByName.has("orchestrion:@strands-agents/sdk:Graph.stream"),
+ ).toBe(true);
+ expect(
+ handlersByName.has("orchestrion:@strands-agents/sdk:Swarm.stream"),
+ ).toBe(true);
+ expect(mockBindStore).toHaveBeenCalledTimes(3);
+
+ plugin.disable();
+
+ expect(mockUnbindStore).toHaveBeenCalledTimes(3);
+ });
+
+ it("records agent model and tool spans from stream events", async () => {
+ const plugin = new StrandsAgentSDKPlugin();
+ plugin.enable();
+
+ const handlers = handlersByName.get(
+ "orchestrion:@strands-agents/sdk:Agent.stream",
+ );
+ class OpenAIModel {
+ modelId = "gpt-4o-mini";
+
+ getConfig() {
+ return { api: "responses", modelId: "gpt-4o-mini" };
+ }
+ }
+ const model = new OpenAIModel();
+ const agent = {
+ id: "agent-1",
+ messages: [{ role: "user", content: [{ text: "hello" }] }],
+ model,
+ name: "helper",
+ stream: vi.fn(),
+ };
+ const suppressionStates: boolean[] = [];
+ const stream = makeAgentStream(
+ [
+ { agent, model, projectedInputTokens: 3, type: "beforeModelCallEvent" },
+ {
+ event: {
+ metrics: { latencyMs: 50, timeToFirstByteMs: 10 },
+ type: "modelMetadataEvent",
+ usage: { inputTokens: 3, outputTokens: 4, totalTokens: 7 },
+ },
+ type: "modelStreamUpdateEvent",
+ },
+ {
+ agent,
+ attemptCount: 1,
+ model,
+ stopData: {
+ message: { role: "assistant", content: [{ text: "call tool" }] },
+ stopReason: "tool_use",
+ },
+ type: "afterModelCallEvent",
+ },
+ {
+ toolUse: {
+ input: { city: "Vienna" },
+ name: "lookup_weather",
+ toolUseId: "tool-1",
+ },
+ type: "beforeToolCallEvent",
+ },
+ {
+ result: {
+ content: [{ text: "sunny" }],
+ status: "success",
+ toolUseId: "tool-1",
+ },
+ toolUse: {
+ input: { city: "Vienna" },
+ name: "lookup_weather",
+ toolUseId: "tool-1",
+ },
+ type: "afterToolCallEvent",
+ },
+ {
+ result: {
+ lastMessage: { role: "assistant", content: [{ text: "sunny" }] },
+ metrics: {
+ accumulatedUsage: {
+ inputTokens: 10,
+ outputTokens: 5,
+ totalTokens: 15,
+ },
+ },
+ stopReason: "end_turn",
+ },
+ type: "agentResultEvent",
+ },
+ ],
+ () => suppressionStates.push(isAutoInstrumentationSuppressed()),
+ );
+ const event = {
+ arguments: ["hello", undefined],
+ moduleVersion: "1.6.0",
+ result: stream,
+ self: agent,
+ };
+
+ handlers.start(event);
+ handlers.end(event);
+ const chunks = [];
+ for await (const chunk of stream) {
+ chunks.push(chunk);
+ }
+
+ expect(chunks).toHaveLength(6);
+ expect(suppressionStates).toEqual([true, true, true, true, true, true]);
+ const rootSpan = spans.find((span) => span.args.name === "Agent: helper");
+ const modelSpan = spans.find(
+ (span) => span.args.name === "Strands model: gpt-4o-mini",
+ );
+ const toolSpan = spans.find(
+ (span) => span.args.name === "tool: lookup_weather",
+ );
+
+ expect(rootSpan?.log).toHaveBeenCalledWith(
+ expect.objectContaining({
+ metadata: expect.objectContaining({
+ "strands.agent.id": "agent-1",
+ "strands.operation": "Agent.stream",
+ model: "gpt-4o-mini",
+ provider: "openai",
+ }),
+ }),
+ );
+ expect(modelSpan?.args.parentSpanIds).toEqual({
+ rootSpanId: rootSpan?.rootSpanId,
+ spanId: rootSpan?.spanId,
+ });
+ expect(modelSpan?.log).toHaveBeenCalledWith(
+ expect.objectContaining({
+ metrics: expect.objectContaining({
+ completion_tokens: 4,
+ prompt_tokens: 3,
+ tokens: 7,
+ }),
+ }),
+ );
+ expect(toolSpan?.log).toHaveBeenCalledWith(
+ expect.objectContaining({
+ output: expect.objectContaining({ status: "success" }),
+ }),
+ );
+ expect(rootSpan?.end).toHaveBeenCalledTimes(1);
+ });
+
+ it("parents nested agent spans under active graph nodes", async () => {
+ const plugin = new StrandsAgentSDKPlugin();
+ plugin.enable();
+
+ const graphHandlers = handlersByName.get(
+ "orchestrion:@strands-agents/sdk:Graph.stream",
+ );
+ const agentHandlers = handlersByName.get(
+ "orchestrion:@strands-agents/sdk:Agent.stream",
+ );
+ const agent = {
+ model: { getConfig: () => ({ modelId: "gpt-4o-mini" }) },
+ name: "worker",
+ stream: vi.fn(),
+ };
+ const graph = {
+ id: "graph-1",
+ nodes: new Map([["worker", { agent, id: "worker", type: "agentNode" }]]),
+ stream: vi.fn(),
+ };
+ const graphStream = makeMultiAgentStream([
+ { nodeId: "worker", orchestrator: graph, type: "beforeNodeCallEvent" },
+ {
+ nodeId: "worker",
+ nodeType: "agentNode",
+ result: {
+ content: [{ text: "node done" }],
+ nodeId: "worker",
+ status: "COMPLETED",
+ usage: { inputTokens: 1, outputTokens: 2, totalTokens: 3 },
+ },
+ type: "nodeResultEvent",
+ },
+ { nodeId: "worker", orchestrator: graph, type: "afterNodeCallEvent" },
+ {
+ result: {
+ content: [{ text: "graph done" }],
+ status: "COMPLETED",
+ usage: { inputTokens: 1, outputTokens: 2, totalTokens: 3 },
+ },
+ type: "multiAgentResultEvent",
+ },
+ ]);
+ const graphEvent = {
+ arguments: ["work", undefined],
+ result: graphStream,
+ self: graph,
+ };
+
+ graphHandlers.start(graphEvent);
+ graphHandlers.end(graphEvent);
+ const iterator = graphStream[Symbol.asyncIterator]();
+ await iterator.next();
+
+ const agentStream = makeAgentStream([
+ {
+ result: {
+ lastMessage: { role: "assistant", content: [{ text: "nested" }] },
+ stopReason: "end_turn",
+ },
+ type: "agentResultEvent",
+ },
+ ]);
+ const agentEvent = {
+ arguments: ["node work", undefined],
+ result: agentStream,
+ self: agent,
+ };
+ agentHandlers.start(agentEvent);
+ agentHandlers.end(agentEvent);
+ for await (const _chunk of agentStream) {
+ // consume nested agent stream
+ }
+ await iterator.next();
+ await iterator.next();
+ await iterator.next();
+ await iterator.next();
+
+ const nodeSpan = spans.find((span) => span.args.name === "node: worker");
+ const agentSpan = spans.find((span) => span.args.name === "Agent: worker");
+
+ expect(agentSpan?.args.parentSpanIds).toEqual({
+ rootSpanId: nodeSpan?.rootSpanId,
+ spanId: nodeSpan?.spanId,
+ });
+ expect(nodeSpan?.end).toHaveBeenCalledTimes(1);
+ });
+
+ it("parents nested graph spans under active graph nodes", async () => {
+ const plugin = new StrandsAgentSDKPlugin();
+ plugin.enable();
+
+ const graphHandlers = handlersByName.get(
+ "orchestrion:@strands-agents/sdk:Graph.stream",
+ );
+ const innerGraph = {
+ id: "inner-graph",
+ nodes: new Map(),
+ stream: vi.fn(),
+ };
+ const outerGraph = {
+ id: "outer-graph",
+ nodes: new Map([
+ ["inner", { id: "inner", orchestrator: innerGraph, type: "graphNode" }],
+ ]),
+ stream: vi.fn(),
+ };
+ const outerStream = makeMultiAgentStream([
+ {
+ nodeId: "inner",
+ orchestrator: outerGraph,
+ type: "beforeNodeCallEvent",
+ },
+ {
+ nodeId: "inner",
+ nodeType: "graphNode",
+ result: {
+ content: [{ text: "inner done" }],
+ nodeId: "inner",
+ status: "COMPLETED",
+ },
+ type: "nodeResultEvent",
+ },
+ { nodeId: "inner", orchestrator: outerGraph, type: "afterNodeCallEvent" },
+ {
+ result: { content: [{ text: "outer done" }], status: "COMPLETED" },
+ type: "multiAgentResultEvent",
+ },
+ ]);
+ const outerEvent = {
+ arguments: ["outer work", undefined],
+ result: outerStream,
+ self: outerGraph,
+ };
+
+ graphHandlers.start(outerEvent);
+ graphHandlers.end(outerEvent);
+ const outerIterator = outerStream[Symbol.asyncIterator]();
+ await outerIterator.next();
+
+ const innerStream = makeMultiAgentStream([
+ {
+ result: { content: [{ text: "nested done" }], status: "COMPLETED" },
+ type: "multiAgentResultEvent",
+ },
+ ]);
+ const innerEvent = {
+ arguments: ["inner work", undefined],
+ result: innerStream,
+ self: innerGraph,
+ };
+
+ graphHandlers.start(innerEvent);
+ graphHandlers.end(innerEvent);
+ for await (const _chunk of innerStream) {
+ // consume nested graph stream
+ }
+ await outerIterator.next();
+ await outerIterator.next();
+ await outerIterator.next();
+ await outerIterator.next();
+
+ const nodeSpan = spans.find((span) => span.args.name === "node: inner");
+ const innerGraphSpan = spans.find(
+ (span) =>
+ span.args.name === "Strands Graph" &&
+ span.args.event?.metadata?.["strands.orchestrator.id"] ===
+ "inner-graph",
+ );
+
+ expect(innerGraphSpan?.args.parentSpanIds).toEqual({
+ rootSpanId: nodeSpan?.rootSpanId,
+ spanId: nodeSpan?.spanId,
+ });
+ expect(nodeSpan?.end).toHaveBeenCalledTimes(1);
+ });
+
+ it("ends open child spans when a stream errors", async () => {
+ const plugin = new StrandsAgentSDKPlugin();
+ plugin.enable();
+
+ const handlers = handlersByName.get(
+ "orchestrion:@strands-agents/sdk:Agent.stream",
+ );
+ const model = { getConfig: () => ({ modelId: "gpt-4o-mini" }) };
+ const stream = (async function* () {
+ yield { model, type: "beforeModelCallEvent" };
+ throw new Error("stream failed");
+ })();
+ const event = {
+ arguments: ["hello", undefined],
+ result: stream,
+ self: { model, stream: vi.fn() },
+ };
+
+ handlers.start(event);
+ handlers.end(event);
+
+ await expect(consume(stream)).rejects.toThrow("stream failed");
+
+ const rootSpan = spans.find((span) => span.args.name === "Strands Agent");
+ const modelSpan = spans.find(
+ (span) => span.args.name === "Strands model: gpt-4o-mini",
+ );
+ expect(modelSpan?.end).toHaveBeenCalledTimes(1);
+ expect(rootSpan?.log).toHaveBeenCalledWith(
+ expect.objectContaining({ error: "stream failed" }),
+ );
+ expect(rootSpan?.end).toHaveBeenCalledTimes(1);
+ });
+});
+
+async function* makeAgentStream(events: unknown[], beforeYield?: () => void) {
+ for (const event of events) {
+ beforeYield?.();
+ yield event as never;
+ }
+ return {
+ lastMessage: { role: "assistant", content: [{ text: "done" }] },
+ stopReason: "end_turn",
+ };
+}
+
+async function* makeMultiAgentStream(events: unknown[]) {
+ for (const event of events) {
+ yield event as never;
+ }
+ return { content: [{ text: "done" }], status: "COMPLETED" };
+}
+
+async function consume(stream: AsyncIterable) {
+ for await (const _chunk of stream) {
+ // consume stream
+ }
+}
diff --git a/js/src/instrumentation/plugins/strands-agent-sdk-plugin.ts b/js/src/instrumentation/plugins/strands-agent-sdk-plugin.ts
new file mode 100644
index 000000000..0907fc972
--- /dev/null
+++ b/js/src/instrumentation/plugins/strands-agent-sdk-plugin.ts
@@ -0,0 +1,1088 @@
+import { BasePlugin, toLoggedError } from "../core";
+import type { ChannelMessage } from "../core/channel-definitions";
+import { isAsyncIterable, patchStreamIfNeeded } from "../core/stream-patcher";
+import type { IsoChannelHandlers } from "../../isomorph";
+import { debugLogger } from "../../debug-logger";
+import { startSpan, withCurrent } from "../../logger";
+import type { Span } from "../../logger";
+import { getCurrentUnixTimestamp } from "../../util";
+import { SpanTypeAttribute, isObject } from "../../../util/index";
+import {
+ bindAutoInstrumentationSuppressionToStart,
+ runWithAutoInstrumentationSuppressed,
+} from "../auto-instrumentation-suppression";
+import { strandsAgentSDKChannels } from "./strands-agent-sdk-channels";
+import type {
+ StrandsAfterModelCallEvent,
+ StrandsAfterNodeCallEvent,
+ StrandsAfterToolCallEvent,
+ StrandsAgent,
+ StrandsAgentResult,
+ StrandsAgentStreamEvent,
+ StrandsBeforeModelCallEvent,
+ StrandsBeforeNodeCallEvent,
+ StrandsBeforeToolCallEvent,
+ StrandsContentBlock,
+ StrandsModel,
+ StrandsModelMetrics,
+ StrandsModelStreamUpdateEvent,
+ StrandsMultiAgent,
+ StrandsMultiAgentHandoffEvent,
+ StrandsMultiAgentResult,
+ StrandsMultiAgentStreamEvent,
+ StrandsNode,
+ StrandsNodeResultEvent,
+ StrandsToolUse,
+ StrandsUsage,
+} from "../../vendor-sdk-types/strands-agent-sdk";
+
+type AgentStreamState = {
+ activeModel?: ModelSpanState;
+ activeTools: Map;
+ finalized: boolean;
+ metadata: Record;
+ span: Span;
+ startTime: number;
+};
+
+type ModelSpanState = {
+ metadata: Record;
+ metrics: Record;
+ span: Span;
+ startTime: number;
+};
+
+type ToolSpanState = {
+ span: Span;
+ startTime: number;
+ toolUse?: StrandsToolUse;
+};
+
+type MultiAgentStreamState = {
+ activeNodes: Map;
+ finalized: boolean;
+ handoffs: Array<{ source?: string; targets?: string[] }>;
+ metadata: Record;
+ operation: "Graph.stream" | "Swarm.stream";
+ orchestrator?: StrandsMultiAgent;
+ span: Span;
+ startTime: number;
+};
+
+type NodeSpanState = {
+ child?: object;
+ span: Span;
+ startTime: number;
+};
+
+type MultiAgentStreamChannel =
+ | typeof strandsAgentSDKChannels.graphStream
+ | typeof strandsAgentSDKChannels.swarmStream;
+
+type ActiveChildParents = WeakMap