diff --git a/.changeset/cloudflare-ai-chat-instrumentation.md b/.changeset/cloudflare-ai-chat-instrumentation.md new file mode 100644 index 000000000..008038f72 --- /dev/null +++ b/.changeset/cloudflare-ai-chat-instrumentation.md @@ -0,0 +1,5 @@ +--- +"braintrust": minor +--- + +feat: Add `@cloudflare/ai-chat` instrumentation diff --git a/e2e/config/pr-comment-scenarios.json b/e2e/config/pr-comment-scenarios.json index a644fd45d..de42bf238 100644 --- a/e2e/config/pr-comment-scenarios.json +++ b/e2e/config/pr-comment-scenarios.json @@ -83,6 +83,21 @@ } ] }, + { + "scenarioDirName": "cloudflare-ai-chat-instrumentation", + "label": "Cloudflare AI Chat Instrumentation", + "metadataScenario": "cloudflare-ai-chat-instrumentation", + "variants": [ + { + "variantKey": "cloudflare-ai-chat-v0", + "label": "v0.9 pinned" + }, + { + "variantKey": "cloudflare-ai-chat-v0-latest", + "label": "v0.9 latest" + } + ] + }, { "scenarioDirName": "openai-agents-instrumentation", "label": "OpenAI Agents Instrumentation", diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0-auto.cassette.json b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0-auto.cassette.json new file mode 100644 index 000000000..7d111227e --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0-auto.cassette.json @@ -0,0 +1,404 @@ +{ + "entries": [ + { + "callIndex": 0, + "id": "59ffe088275bf9a2", + "matchKey": "POST api.openai.com/v1/responses", + "recordedAt": "2026-07-20T14:51:35.158Z", + "request": { + "body": { + "kind": "json", + "value": { + "input": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "input_text" + } + ], + "role": "user" + } + ], + "max_output_tokens": 32, + "model": "gpt-4.1-nano", + "temperature": 0, + "tool_choice": "auto", + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "type": "function" + } + ] + } + }, + "headers": {}, + "method": "POST", + "url": "https://api.openai.com/v1/responses" + }, + "response": { + "body": { + "kind": "json", + "value": { + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1784559095, + "created_at": 1784559094, + "error": null, + "frequency_penalty": 0, + "id": "resp_04f551d05edd0796006a5e35f6997481a399c711c8638fdb40", + "incomplete_details": null, + "instructions": null, + "max_output_tokens": 32, + "max_tool_calls": null, + "metadata": {}, + "model": "gpt-4.1-nano-2025-04-14", + "moderation": null, + "object": "response", + "output": [ + { + "arguments": "{\"city\":\"Vienna\"}", + "call_id": "call_860XAul7n7AYZnjcgUVUIZTK", + "id": "fc_04f551d05edd0796006a5e35f70de881a391c54f9817f9c7bf", + "name": "lookup_weather", + "status": "completed", + "type": "function_call" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0, + "previous_response_id": null, + "prompt_cache_key": "[REDACTED]", + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "status": "completed", + "store": true, + "temperature": 0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "output_schema": null, + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "strict": true, + "type": "function" + } + ], + "top_logprobs": 0, + "top_p": 1, + "truncation": "disabled", + "usage": { + "input_tokens": 80, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 112 + }, + "user": null + } + }, + "headers": { + "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "a1e2c8e4c9be4f16-IAD", + "connection": "keep-alive", + "content-encoding": "br", + "content-type": "application/json", + "date": "Mon, 20 Jul 2026 14:51:35 GMT", + "openai-organization": "braintrust-data", + "openai-processing-ms": "567", + "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-ratelimit-limit-requests": "30000", + "x-ratelimit-limit-tokens": "150000000", + "x-ratelimit-remaining-requests": "29999", + "x-ratelimit-remaining-tokens": "149999702", + "x-ratelimit-reset-requests": "2ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_3e2c1053a33e4ba2ab8cbd8cdec33073" + }, + "status": 200, + "statusText": "OK" + } + }, + { + "callIndex": 1, + "id": "fa7acbee43b3436b", + "matchKey": "POST api.openai.com/v1/responses", + "recordedAt": "2026-07-20T14:51:35.790Z", + "request": { + "body": { + "kind": "json", + "value": { + "input": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "input_text" + } + ], + "role": "user" + }, + { + "id": "fc_04f551d05edd0796006a5e35f70de881a391c54f9817f9c7bf", + "type": "item_reference" + }, + { + "call_id": "call_860XAul7n7AYZnjcgUVUIZTK", + "output": "{\"city\":\"Vienna\",\"condition\":\"sunny\",\"marker\":\"WEATHER_TOOL_EXECUTED\"}", + "type": "function_call_output" + } + ], + "max_output_tokens": 32, + "model": "gpt-4.1-nano", + "temperature": 0, + "tool_choice": "auto", + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "type": "function" + } + ] + } + }, + "headers": {}, + "method": "POST", + "url": "https://api.openai.com/v1/responses" + }, + "response": { + "body": { + "kind": "json", + "value": { + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1784559095, + "created_at": 1784559095, + "error": null, + "frequency_penalty": 0, + "id": "resp_04f551d05edd0796006a5e35f73a7481a38b73e055da234629", + "incomplete_details": null, + "instructions": null, + "max_output_tokens": 32, + "max_tool_calls": null, + "metadata": {}, + "model": "gpt-4.1-nano-2025-04-14", + "moderation": null, + "object": "response", + "output": [ + { + "content": [ + { + "annotations": [], + "logprobs": [], + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "output_text" + } + ], + "id": "msg_04f551d05edd0796006a5e35f7929481a391455c80ed8d4a17", + "role": "assistant", + "status": "completed", + "type": "message" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0, + "previous_response_id": null, + "prompt_cache_key": "[REDACTED]", + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "status": "completed", + "store": true, + "temperature": 0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "output_schema": null, + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "strict": true, + "type": "function" + } + ], + "top_logprobs": 0, + "top_p": 1, + "truncation": "disabled", + "usage": { + "input_tokens": 121, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 133 + }, + "user": null + } + }, + "headers": { + "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "a1e2c8e8e90c4f16-IAD", + "connection": "keep-alive", + "content-encoding": "br", + "content-type": "application/json", + "date": "Mon, 20 Jul 2026 14:51:35 GMT", + "openai-organization": "braintrust-data", + "openai-processing-ms": "570", + "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-ratelimit-limit-requests": "30000", + "x-ratelimit-limit-tokens": "150000000", + "x-ratelimit-remaining-requests": "29999", + "x-ratelimit-remaining-tokens": "149999662", + "x-ratelimit-reset-requests": "2ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_3a466c7e215047499b11d5706bec92c5" + }, + "status": 200, + "statusText": "OK" + } + } + ], + "meta": { + "createdAt": "2026-07-20T14:51:37.897Z" + } +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0-latest-auto.cassette.json b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0-latest-auto.cassette.json new file mode 100644 index 000000000..6134d0b41 --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0-latest-auto.cassette.json @@ -0,0 +1,404 @@ +{ + "entries": [ + { + "callIndex": 0, + "id": "59ffe088275bf9a2", + "matchKey": "POST api.openai.com/v1/responses", + "recordedAt": "2026-07-20T14:51:49.241Z", + "request": { + "body": { + "kind": "json", + "value": { + "input": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "input_text" + } + ], + "role": "user" + } + ], + "max_output_tokens": 32, + "model": "gpt-4.1-nano", + "temperature": 0, + "tool_choice": "auto", + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "type": "function" + } + ] + } + }, + "headers": {}, + "method": "POST", + "url": "https://api.openai.com/v1/responses" + }, + "response": { + "body": { + "kind": "json", + "value": { + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1784559109, + "created_at": 1784559108, + "error": null, + "frequency_penalty": 0, + "id": "resp_0acbba58478098f4006a5e36049c4c8191be1def03e2f5b9a2", + "incomplete_details": null, + "instructions": null, + "max_output_tokens": 32, + "max_tool_calls": null, + "metadata": {}, + "model": "gpt-4.1-nano-2025-04-14", + "moderation": null, + "object": "response", + "output": [ + { + "arguments": "{\"city\":\"Vienna\"}", + "call_id": "call_CFLEqfI5s7Q9TrUizNEftJLU", + "id": "fc_0acbba58478098f4006a5e360515a48191a74e66572c26cad0", + "name": "lookup_weather", + "status": "completed", + "type": "function_call" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0, + "previous_response_id": null, + "prompt_cache_key": "[REDACTED]", + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "status": "completed", + "store": true, + "temperature": 0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "output_schema": null, + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "strict": true, + "type": "function" + } + ], + "top_logprobs": 0, + "top_p": 1, + "truncation": "disabled", + "usage": { + "input_tokens": 80, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 112 + }, + "user": null + } + }, + "headers": { + "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "a1e2c93c8eb910de-IAD", + "connection": "keep-alive", + "content-encoding": "br", + "content-type": "application/json", + "date": "Mon, 20 Jul 2026 14:51:49 GMT", + "openai-organization": "braintrust-data", + "openai-processing-ms": "635", + "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-ratelimit-limit-requests": "30000", + "x-ratelimit-limit-tokens": "150000000", + "x-ratelimit-remaining-requests": "29999", + "x-ratelimit-remaining-tokens": "149999702", + "x-ratelimit-reset-requests": "2ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_80cfe4ffe6d546d2a0e0f8a440b85377" + }, + "status": 200, + "statusText": "OK" + } + }, + { + "callIndex": 1, + "id": "c1382bb733eb2c52", + "matchKey": "POST api.openai.com/v1/responses", + "recordedAt": "2026-07-20T14:51:49.957Z", + "request": { + "body": { + "kind": "json", + "value": { + "input": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "input_text" + } + ], + "role": "user" + }, + { + "id": "fc_0acbba58478098f4006a5e360515a48191a74e66572c26cad0", + "type": "item_reference" + }, + { + "call_id": "call_CFLEqfI5s7Q9TrUizNEftJLU", + "output": "{\"city\":\"Vienna\",\"condition\":\"sunny\",\"marker\":\"WEATHER_TOOL_EXECUTED\"}", + "type": "function_call_output" + } + ], + "max_output_tokens": 32, + "model": "gpt-4.1-nano", + "temperature": 0, + "tool_choice": "auto", + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "type": "function" + } + ] + } + }, + "headers": {}, + "method": "POST", + "url": "https://api.openai.com/v1/responses" + }, + "response": { + "body": { + "kind": "json", + "value": { + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1784559109, + "created_at": 1784559109, + "error": null, + "frequency_penalty": 0, + "id": "resp_0acbba58478098f4006a5e360550a081918e5a17a45beef8dd", + "incomplete_details": null, + "instructions": null, + "max_output_tokens": 32, + "max_tool_calls": null, + "metadata": {}, + "model": "gpt-4.1-nano-2025-04-14", + "moderation": null, + "object": "response", + "output": [ + { + "content": [ + { + "annotations": [], + "logprobs": [], + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "output_text" + } + ], + "id": "msg_0acbba58478098f4006a5e3605bfac8191a1eece030df897e3", + "role": "assistant", + "status": "completed", + "type": "message" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0, + "previous_response_id": null, + "prompt_cache_key": "[REDACTED]", + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "status": "completed", + "store": true, + "temperature": 0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "output_schema": null, + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "strict": true, + "type": "function" + } + ], + "top_logprobs": 0, + "top_p": 1, + "truncation": "disabled", + "usage": { + "input_tokens": 121, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 133 + }, + "user": null + } + }, + "headers": { + "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "a1e2c940ee8110de-IAD", + "connection": "keep-alive", + "content-encoding": "br", + "content-type": "application/json", + "date": "Mon, 20 Jul 2026 14:51:49 GMT", + "openai-organization": "braintrust-data", + "openai-processing-ms": "648", + "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-ratelimit-limit-requests": "30000", + "x-ratelimit-limit-tokens": "150000000", + "x-ratelimit-remaining-requests": "29999", + "x-ratelimit-remaining-tokens": "149999662", + "x-ratelimit-reset-requests": "2ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_53186ed2fc784cab8c3a5d259f1c2af8" + }, + "status": 200, + "statusText": "OK" + } + } + ], + "meta": { + "createdAt": "2026-07-20T14:51:52.068Z" + } +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0-latest.cassette.json b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0-latest.cassette.json new file mode 100644 index 000000000..0eb52ee99 --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0-latest.cassette.json @@ -0,0 +1,404 @@ +{ + "entries": [ + { + "callIndex": 0, + "id": "59ffe088275bf9a2", + "matchKey": "POST api.openai.com/v1/responses", + "recordedAt": "2026-07-20T14:51:42.209Z", + "request": { + "body": { + "kind": "json", + "value": { + "input": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "input_text" + } + ], + "role": "user" + } + ], + "max_output_tokens": 32, + "model": "gpt-4.1-nano", + "temperature": 0, + "tool_choice": "auto", + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "type": "function" + } + ] + } + }, + "headers": {}, + "method": "POST", + "url": "https://api.openai.com/v1/responses" + }, + "response": { + "body": { + "kind": "json", + "value": { + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1784559101, + "created_at": 1784559101, + "error": null, + "frequency_penalty": 0, + "id": "resp_0b3de81cd59d852f006a5e35fd606c81a2a0c4dd439eb49ad4", + "incomplete_details": null, + "instructions": null, + "max_output_tokens": 32, + "max_tool_calls": null, + "metadata": {}, + "model": "gpt-4.1-nano-2025-04-14", + "moderation": null, + "object": "response", + "output": [ + { + "arguments": "{\"city\":\"Vienna\"}", + "call_id": "call_5aUWoaFaaTr1bcWcgG89CL8K", + "id": "fc_0b3de81cd59d852f006a5e35fdedc081a29ad13fc7f718c2af", + "name": "lookup_weather", + "status": "completed", + "type": "function_call" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0, + "previous_response_id": null, + "prompt_cache_key": "[REDACTED]", + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "status": "completed", + "store": true, + "temperature": 0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "output_schema": null, + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "strict": true, + "type": "function" + } + ], + "top_logprobs": 0, + "top_p": 1, + "truncation": "disabled", + "usage": { + "input_tokens": 80, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 112 + }, + "user": null + } + }, + "headers": { + "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "a1e2c90eac9adda5-IAD", + "connection": "keep-alive", + "content-encoding": "br", + "content-type": "application/json", + "date": "Mon, 20 Jul 2026 14:51:42 GMT", + "openai-organization": "braintrust-data", + "openai-processing-ms": "900", + "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-ratelimit-limit-requests": "30000", + "x-ratelimit-limit-tokens": "150000000", + "x-ratelimit-remaining-requests": "29999", + "x-ratelimit-remaining-tokens": "149999702", + "x-ratelimit-reset-requests": "2ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_b02ae31c18d94c13aa04c3d8f74a90c6" + }, + "status": 200, + "statusText": "OK" + } + }, + { + "callIndex": 1, + "id": "827dc22627e1d436", + "matchKey": "POST api.openai.com/v1/responses", + "recordedAt": "2026-07-20T14:51:42.842Z", + "request": { + "body": { + "kind": "json", + "value": { + "input": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "input_text" + } + ], + "role": "user" + }, + { + "id": "fc_0b3de81cd59d852f006a5e35fdedc081a29ad13fc7f718c2af", + "type": "item_reference" + }, + { + "call_id": "call_5aUWoaFaaTr1bcWcgG89CL8K", + "output": "{\"city\":\"Vienna\",\"condition\":\"sunny\",\"marker\":\"WEATHER_TOOL_EXECUTED\"}", + "type": "function_call_output" + } + ], + "max_output_tokens": 32, + "model": "gpt-4.1-nano", + "temperature": 0, + "tool_choice": "auto", + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "type": "function" + } + ] + } + }, + "headers": {}, + "method": "POST", + "url": "https://api.openai.com/v1/responses" + }, + "response": { + "body": { + "kind": "json", + "value": { + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1784559102, + "created_at": 1784559102, + "error": null, + "frequency_penalty": 0, + "id": "resp_0b3de81cd59d852f006a5e35fe4d1c81a29a26b2d6aa8c45cb", + "incomplete_details": null, + "instructions": null, + "max_output_tokens": 32, + "max_tool_calls": null, + "metadata": {}, + "model": "gpt-4.1-nano-2025-04-14", + "moderation": null, + "object": "response", + "output": [ + { + "content": [ + { + "annotations": [], + "logprobs": [], + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "output_text" + } + ], + "id": "msg_0b3de81cd59d852f006a5e35fe9f0881a28dd4013633012ebc", + "role": "assistant", + "status": "completed", + "type": "message" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0, + "previous_response_id": null, + "prompt_cache_key": "[REDACTED]", + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "status": "completed", + "store": true, + "temperature": 0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "output_schema": null, + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "strict": true, + "type": "function" + } + ], + "top_logprobs": 0, + "top_p": 1, + "truncation": "disabled", + "usage": { + "input_tokens": 121, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 133 + }, + "user": null + } + }, + "headers": { + "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "a1e2c914fa05dda5-IAD", + "connection": "keep-alive", + "content-encoding": "br", + "content-type": "application/json", + "date": "Mon, 20 Jul 2026 14:51:42 GMT", + "openai-organization": "braintrust-data", + "openai-processing-ms": "574", + "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-ratelimit-limit-requests": "30000", + "x-ratelimit-limit-tokens": "150000000", + "x-ratelimit-remaining-requests": "29999", + "x-ratelimit-remaining-tokens": "149999662", + "x-ratelimit-reset-requests": "2ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_39d0609c871441cf8f52b1309ae1e5a5" + }, + "status": 200, + "statusText": "OK" + } + } + ], + "meta": { + "createdAt": "2026-07-20T14:49:55.732Z" + } +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0.cassette.json b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0.cassette.json new file mode 100644 index 000000000..ca6709293 --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__cassettes__/cloudflare-ai-chat-v0.cassette.json @@ -0,0 +1,404 @@ +{ + "entries": [ + { + "callIndex": 0, + "id": "59ffe088275bf9a2", + "matchKey": "POST api.openai.com/v1/responses", + "recordedAt": "2026-07-20T14:51:27.927Z", + "request": { + "body": { + "kind": "json", + "value": { + "input": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "input_text" + } + ], + "role": "user" + } + ], + "max_output_tokens": 32, + "model": "gpt-4.1-nano", + "temperature": 0, + "tool_choice": "auto", + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "type": "function" + } + ] + } + }, + "headers": {}, + "method": "POST", + "url": "https://api.openai.com/v1/responses" + }, + "response": { + "body": { + "kind": "json", + "value": { + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1784559087, + "created_at": 1784559086, + "error": null, + "frequency_penalty": 0, + "id": "resp_0da1cfe7fdb74174006a5e35eeca7c819fa1e5b97d371ca9d4", + "incomplete_details": null, + "instructions": null, + "max_output_tokens": 32, + "max_tool_calls": null, + "metadata": {}, + "model": "gpt-4.1-nano-2025-04-14", + "moderation": null, + "object": "response", + "output": [ + { + "arguments": "{\"city\":\"Vienna\"}", + "call_id": "call_3XbLuIKu7IUmS9axTgMRE554", + "id": "fc_0da1cfe7fdb74174006a5e35efbe94819fb8ead9a2d6f2650d", + "name": "lookup_weather", + "status": "completed", + "type": "function_call" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0, + "previous_response_id": null, + "prompt_cache_key": "[REDACTED]", + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "status": "completed", + "store": true, + "temperature": 0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "output_schema": null, + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "strict": true, + "type": "function" + } + ], + "top_logprobs": 0, + "top_p": 1, + "truncation": "disabled", + "usage": { + "input_tokens": 80, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 112 + }, + "user": null + } + }, + "headers": { + "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "a1e2c8b32db8c971-IAD", + "connection": "keep-alive", + "content-encoding": "br", + "content-type": "application/json", + "date": "Mon, 20 Jul 2026 14:51:27 GMT", + "openai-organization": "braintrust-data", + "openai-processing-ms": "1151", + "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-ratelimit-limit-requests": "30000", + "x-ratelimit-limit-tokens": "150000000", + "x-ratelimit-remaining-requests": "29999", + "x-ratelimit-remaining-tokens": "149999702", + "x-ratelimit-reset-requests": "2ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_74145fe11b5743b48ba7170c3eebdde8" + }, + "status": 200, + "statusText": "OK" + } + }, + { + "callIndex": 1, + "id": "4170f3cf5cb1b435", + "matchKey": "POST api.openai.com/v1/responses", + "recordedAt": "2026-07-20T14:51:28.792Z", + "request": { + "body": { + "kind": "json", + "value": { + "input": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "input_text" + } + ], + "role": "user" + }, + { + "id": "fc_0da1cfe7fdb74174006a5e35efbe94819fb8ead9a2d6f2650d", + "type": "item_reference" + }, + { + "call_id": "call_3XbLuIKu7IUmS9axTgMRE554", + "output": "{\"city\":\"Vienna\",\"condition\":\"sunny\",\"marker\":\"WEATHER_TOOL_EXECUTED\"}", + "type": "function_call_output" + } + ], + "max_output_tokens": 32, + "model": "gpt-4.1-nano", + "temperature": 0, + "tool_choice": "auto", + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "type": "function" + } + ] + } + }, + "headers": {}, + "method": "POST", + "url": "https://api.openai.com/v1/responses" + }, + "response": { + "body": { + "kind": "json", + "value": { + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1784559088, + "created_at": 1784559088, + "error": null, + "frequency_penalty": 0, + "id": "resp_0da1cfe7fdb74174006a5e35efff44819fb0371b7aa3deb657", + "incomplete_details": null, + "instructions": null, + "max_output_tokens": 32, + "max_tool_calls": null, + "metadata": {}, + "model": "gpt-4.1-nano-2025-04-14", + "moderation": null, + "object": "response", + "output": [ + { + "content": [ + { + "annotations": [], + "logprobs": [], + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "output_text" + } + ], + "id": "msg_0da1cfe7fdb74174006a5e35f0604c819f93e8ab8956841485", + "role": "assistant", + "status": "completed", + "type": "message" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0, + "previous_response_id": null, + "prompt_cache_key": "[REDACTED]", + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "status": "completed", + "store": true, + "temperature": 0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "name": "lookup_weather", + "output_schema": null, + "parameters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": ["city"], + "type": "object" + }, + "strict": true, + "type": "function" + } + ], + "top_logprobs": 0, + "top_p": 1, + "truncation": "disabled", + "usage": { + "input_tokens": 121, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 133 + }, + "user": null + } + }, + "headers": { + "access-control-expose-headers": "X-Request-ID, CF-Ray, CF-Ray", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "a1e2c8bbb8dec971-IAD", + "connection": "keep-alive", + "content-encoding": "br", + "content-type": "application/json", + "date": "Mon, 20 Jul 2026 14:51:28 GMT", + "openai-organization": "braintrust-data", + "openai-processing-ms": "801", + "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-ratelimit-limit-requests": "30000", + "x-ratelimit-limit-tokens": "150000000", + "x-ratelimit-remaining-requests": "29999", + "x-ratelimit-remaining-tokens": "149999662", + "x-ratelimit-reset-requests": "2ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_49c873da83b3492b92f60780777f191a" + }, + "status": 200, + "statusText": "OK" + } + } + ], + "meta": { + "createdAt": "2026-07-20T14:49:39.078Z" + } +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-auto.span-tree.json b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-auto.span-tree.json new file mode 100644 index 000000000..39829462b --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-auto.span-tree.json @@ -0,0 +1,815 @@ +{ + "span_tree": [ + { + "name": "cloudflare-ai-chat-success-root", + "type": "task", + "children": [ + { + "name": "AIChatAgent.onChatMessage", + "type": "task", + "children": [ + { + "name": "generateText", + "type": "function", + "children": [ + { + "name": "doGenerate", + "type": "llm", + "children": [], + "input": { + "maxOutputTokens": 32, + "prompt": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "temperature": 0, + "toolChoice": { + "type": "auto" + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + }, + "name": "lookup_weather", + "type": "function" + } + ] + }, + "output": { + "content": [ + { + "input": "{\"city\":\"Vienna\"}", + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "finishReason": { + "unified": "tool-calls" + }, + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "inputTokens": { + "cacheRead": 0, + "noCache": 80, + "total": 80 + }, + "outputTokens": { + "reasoning": 0, + "text": 32, + "total": 32 + }, + "raw": { + "input_tokens": 80, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + } + } + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "finish_reason": { + "unified": "tool-calls" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses" + }, + "metrics": { + "completion_reasoning_tokens": 0, + "completion_tokens": 32, + "prompt_cached_tokens": 0, + "prompt_tokens": 80, + "reasoning_tokens": 0, + "tokens": 112 + } + }, + { + "name": "doGenerate", + "type": "llm", + "children": [], + "input": { + "maxOutputTokens": 32, + "prompt": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + }, + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + } + ], + "temperature": 0, + "toolChoice": { + "type": "auto" + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + }, + "name": "lookup_weather", + "type": "function" + } + ] + }, + "output": { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": { + "unified": "stop" + }, + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "inputTokens": { + "cacheRead": 0, + "noCache": 121, + "total": 121 + }, + "outputTokens": { + "reasoning": 0, + "text": 12, + "total": 12 + }, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + } + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "finish_reason": { + "unified": "stop" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses" + }, + "metrics": { + "completion_reasoning_tokens": 0, + "completion_tokens": 12, + "prompt_cached_tokens": 0, + "prompt_tokens": 121, + "reasoning_tokens": 0, + "tokens": 133 + } + }, + { + "name": "lookup_weather", + "type": "tool", + "children": [], + "input": { + "city": "Vienna" + }, + "output": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + } + ], + "input": { + "maxOutputTokens": 32, + "messages": [ + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "model": { + "config": { + "fileIdPrefixes": [ + "file-" + ], + "provider": "openai.responses" + }, + "modelId": "gpt-4.1-nano", + "specificationVersion": "v3", + "supportedUrls": { + "application/pdf": [ + {} + ], + "image/*": [ + {} + ] + } + }, + "system": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "temperature": 0, + "tools": { + "lookup_weather": { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + } + } + } + }, + "output": { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": "stop", + "output": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + }, + { + "content": [ + { + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "steps": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + }, + { + "dynamic": false, + "input": { + "city": "Vienna" + }, + "output": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + }, + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "finishReason": "tool-calls", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 80 + }, + "inputTokens": 80, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 32 + }, + "outputTokens": 32, + "raw": { + "input_tokens": 80, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 112 + }, + "warnings": [] + }, + { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": "stop", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + }, + { + "content": [ + { + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 121 + }, + "inputTokens": 121, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 12 + }, + "outputTokens": 12, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 133 + }, + "warnings": [] + } + ], + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "toolCalls": [], + "toolResults": [], + "totalUsage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 201 + }, + "inputTokens": 201, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 44 + }, + "outputTokens": 44, + "reasoningTokens": 0, + "totalTokens": 245 + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 121 + }, + "inputTokens": 121, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 12 + }, + "outputTokens": 12, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 133 + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses", + "tools": { + "lookup_weather": { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + } + } + } + } + } + ], + "input": [ + { + "id": "", + "parts": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "output": { + "id": "", + "parts": [ + { + "state": "done", + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + } + ], + "metadata": { + "mode": "auto", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + }, + { + "name": "cloudflare-ai-chat-error-root", + "type": "task", + "children": [ + { + "name": "AIChatAgent.onChatMessage", + "type": "task", + "children": [], + "input": [ + { + "id": "", + "parts": [ + { + "text": "Produce a deterministic streaming error.", + "type": "text" + } + ], + "role": "user" + } + ], + "output": { + "id": "", + "parts": [ + { + "state": "streaming", + "text": "partial response", + "type": "text" + } + ], + "role": "assistant" + }, + "error": "CLOUDFLARE_AI_CHAT_STREAM_ERROR" + } + ], + "metadata": { + "mode": "auto", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + } + ] +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-auto.span-tree.txt b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-auto.span-tree.txt new file mode 100644 index 000000000..248aaa22f --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-auto.span-tree.txt @@ -0,0 +1,776 @@ +span_tree: +├── cloudflare-ai-chat-success-root [task] +│ metadata: { +│ "mode": "auto", +│ "scenario": "cloudflare-ai-chat-instrumentation", +│ "testRunId": "" +│ } +│ └── AIChatAgent.onChatMessage [task] +│ input: [ +│ { +│ "id": "", +│ "parts": [ +│ { +│ "text": "Look up the weather in Vienna.", +│ "type": "text" +│ } +│ ], +│ "role": "user" +│ } +│ ] +│ output: { +│ "id": "", +│ "parts": [ +│ { +│ "state": "done", +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ └── generateText [function] +│ input: { +│ "maxOutputTokens": 32, +│ "messages": [ +│ { +│ "content": [ +│ { +│ "text": "Look up the weather in Vienna.", +│ "type": "text" +│ } +│ ], +│ "role": "user" +│ } +│ ], +│ "model": { +│ "config": { +│ "fileIdPrefixes": [ +│ "file-" +│ ], +│ "provider": "openai.responses" +│ }, +│ "modelId": "gpt-4.1-nano", +│ "specificationVersion": "v3", +│ "supportedUrls": { +│ "application/pdf": [ +│ {} +│ ], +│ "image/*": [ +│ {} +│ ] +│ } +│ }, +│ "system": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ "temperature": 0, +│ "tools": { +│ "lookup_weather": { +│ "description": "Return deterministic weather for one city.", +│ "inputSchema": { +│ "$schema": "http://json-schema.org/draft-07/schema#", +│ "additionalProperties": false, +│ "properties": { +│ "city": { +│ "type": "string" +│ } +│ }, +│ "required": [ +│ "city" +│ ], +│ "type": "object" +│ } +│ } +│ } +│ } +│ output: { +│ "content": [ +│ { +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "finishReason": "stop", +│ "output": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ }, +│ { +│ "content": [ +│ { +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "steps": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ }, +│ { +│ "dynamic": false, +│ "input": { +│ "city": "Vienna" +│ }, +│ "output": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ }, +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "finishReason": "tool-calls", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 80 +│ }, +│ "inputTokens": 80, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 32 +│ }, +│ "outputTokens": 32, +│ "raw": { +│ "input_tokens": 80, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 32, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 112 +│ }, +│ "warnings": [] +│ }, +│ { +│ "content": [ +│ { +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "finishReason": "stop", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ }, +│ { +│ "content": [ +│ { +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 121 +│ }, +│ "inputTokens": 121, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 12 +│ }, +│ "outputTokens": 12, +│ "raw": { +│ "input_tokens": 121, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 12, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 133 +│ }, +│ "warnings": [] +│ } +│ ], +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "toolCalls": [], +│ "toolResults": [], +│ "totalUsage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 201 +│ }, +│ "inputTokens": 201, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 44 +│ }, +│ "outputTokens": 44, +│ "reasoningTokens": 0, +│ "totalTokens": 245 +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 121 +│ }, +│ "inputTokens": 121, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 12 +│ }, +│ "outputTokens": 12, +│ "raw": { +│ "input_tokens": 121, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 12, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 133 +│ }, +│ "warnings": [] +│ } +│ metadata: { +│ "braintrust": { +│ "integration_name": "ai-sdk", +│ "sdk_language": "typescript" +│ }, +│ "model": "gpt-4.1-nano", +│ "provider": "openai.responses", +│ "tools": { +│ "lookup_weather": { +│ "description": "Return deterministic weather for one city.", +│ "inputSchema": { +│ "$schema": "http://json-schema.org/draft-07/schema#", +│ "additionalProperties": false, +│ "properties": { +│ "city": { +│ "type": "string" +│ } +│ }, +│ "required": [ +│ "city" +│ ], +│ "type": "object" +│ } +│ } +│ } +│ } +│ ├── doGenerate [llm] +│ │ input: { +│ │ "maxOutputTokens": 32, +│ │ "prompt": [ +│ │ { +│ │ "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ │ "role": "system" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "text": "Look up the weather in Vienna.", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "role": "user" +│ │ } +│ │ ], +│ │ "temperature": 0, +│ │ "toolChoice": { +│ │ "type": "auto" +│ │ }, +│ │ "tools": [ +│ │ { +│ │ "description": "Return deterministic weather for one city.", +│ │ "inputSchema": { +│ │ "$schema": "http://json-schema.org/draft-07/schema#", +│ │ "additionalProperties": false, +│ │ "properties": { +│ │ "city": { +│ │ "type": "string" +│ │ } +│ │ }, +│ │ "required": [ +│ │ "city" +│ │ ], +│ │ "type": "object" +│ │ }, +│ │ "name": "lookup_weather", +│ │ "type": "function" +│ │ } +│ │ ] +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "input": "{\"city\":\"Vienna\"}", +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-call" +│ │ } +│ │ ], +│ │ "finishReason": { +│ │ "unified": "tool-calls" +│ │ }, +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "responseId": "", +│ │ "serviceTier": "default" +│ │ } +│ │ }, +│ │ "response": { +│ │ "body": "", +│ │ "id": "", +│ │ "modelId": "gpt-4.1-nano-2025-04-14", +│ │ "timestamp": "" +│ │ }, +│ │ "usage": { +│ │ "inputTokens": { +│ │ "cacheRead": 0, +│ │ "noCache": 80, +│ │ "total": 80 +│ │ }, +│ │ "outputTokens": { +│ │ "reasoning": 0, +│ │ "text": 32, +│ │ "total": 32 +│ │ }, +│ │ "raw": { +│ │ "input_tokens": 80, +│ │ "input_tokens_details": { +│ │ "cached_tokens": 0 +│ │ }, +│ │ "output_tokens": 32, +│ │ "output_tokens_details": { +│ │ "reasoning_tokens": 0 +│ │ } +│ │ } +│ │ }, +│ │ "warnings": [] +│ │ } +│ │ metadata: { +│ │ "braintrust": { +│ │ "integration_name": "ai-sdk", +│ │ "sdk_language": "typescript" +│ │ }, +│ │ "finish_reason": { +│ │ "unified": "tool-calls" +│ │ }, +│ │ "model": "gpt-4.1-nano", +│ │ "provider": "openai.responses" +│ │ } +│ │ metrics: { +│ │ "completion_reasoning_tokens": 0, +│ │ "completion_tokens": 32, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 80, +│ │ "reasoning_tokens": 0, +│ │ "tokens": 112 +│ │ } +│ ├── doGenerate [llm] +│ │ input: { +│ │ "maxOutputTokens": 32, +│ │ "prompt": [ +│ │ { +│ │ "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ │ "role": "system" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "text": "Look up the weather in Vienna.", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "role": "user" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "input": { +│ │ "city": "Vienna" +│ │ }, +│ │ "providerOptions": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-call" +│ │ } +│ │ ], +│ │ "role": "assistant" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "output": { +│ │ "type": "json", +│ │ "value": { +│ │ "city": "Vienna", +│ │ "condition": "sunny", +│ │ "marker": "WEATHER_TOOL_EXECUTED" +│ │ } +│ │ }, +│ │ "providerOptions": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-result" +│ │ } +│ │ ], +│ │ "role": "tool" +│ │ } +│ │ ], +│ │ "temperature": 0, +│ │ "toolChoice": { +│ │ "type": "auto" +│ │ }, +│ │ "tools": [ +│ │ { +│ │ "description": "Return deterministic weather for one city.", +│ │ "inputSchema": { +│ │ "$schema": "http://json-schema.org/draft-07/schema#", +│ │ "additionalProperties": false, +│ │ "properties": { +│ │ "city": { +│ │ "type": "string" +│ │ } +│ │ }, +│ │ "required": [ +│ │ "city" +│ │ ], +│ │ "type": "object" +│ │ }, +│ │ "name": "lookup_weather", +│ │ "type": "function" +│ │ } +│ │ ] +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "finishReason": { +│ │ "unified": "stop" +│ │ }, +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "responseId": "", +│ │ "serviceTier": "default" +│ │ } +│ │ }, +│ │ "response": { +│ │ "body": "", +│ │ "id": "", +│ │ "modelId": "gpt-4.1-nano-2025-04-14", +│ │ "timestamp": "" +│ │ }, +│ │ "usage": { +│ │ "inputTokens": { +│ │ "cacheRead": 0, +│ │ "noCache": 121, +│ │ "total": 121 +│ │ }, +│ │ "outputTokens": { +│ │ "reasoning": 0, +│ │ "text": 12, +│ │ "total": 12 +│ │ }, +│ │ "raw": { +│ │ "input_tokens": 121, +│ │ "input_tokens_details": { +│ │ "cached_tokens": 0 +│ │ }, +│ │ "output_tokens": 12, +│ │ "output_tokens_details": { +│ │ "reasoning_tokens": 0 +│ │ } +│ │ } +│ │ }, +│ │ "warnings": [] +│ │ } +│ │ metadata: { +│ │ "braintrust": { +│ │ "integration_name": "ai-sdk", +│ │ "sdk_language": "typescript" +│ │ }, +│ │ "finish_reason": { +│ │ "unified": "stop" +│ │ }, +│ │ "model": "gpt-4.1-nano", +│ │ "provider": "openai.responses" +│ │ } +│ │ metrics: { +│ │ "completion_reasoning_tokens": 0, +│ │ "completion_tokens": 12, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 121, +│ │ "reasoning_tokens": 0, +│ │ "tokens": 133 +│ │ } +│ └── lookup_weather [tool] +│ input: { +│ "city": "Vienna" +│ } +│ output: { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +└── cloudflare-ai-chat-error-root [task] + metadata: { + "mode": "auto", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + └── AIChatAgent.onChatMessage [task] + input: [ + { + "id": "", + "parts": [ + { + "text": "Produce a deterministic streaming error.", + "type": "text" + } + ], + "role": "user" + } + ] + output: { + "id": "", + "parts": [ + { + "state": "streaming", + "text": "partial response", + "type": "text" + } + ], + "role": "assistant" + } + error: "CLOUDFLARE_AI_CHAT_STREAM_ERROR" diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-auto.span-tree.json b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-auto.span-tree.json new file mode 100644 index 000000000..39829462b --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-auto.span-tree.json @@ -0,0 +1,815 @@ +{ + "span_tree": [ + { + "name": "cloudflare-ai-chat-success-root", + "type": "task", + "children": [ + { + "name": "AIChatAgent.onChatMessage", + "type": "task", + "children": [ + { + "name": "generateText", + "type": "function", + "children": [ + { + "name": "doGenerate", + "type": "llm", + "children": [], + "input": { + "maxOutputTokens": 32, + "prompt": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "temperature": 0, + "toolChoice": { + "type": "auto" + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + }, + "name": "lookup_weather", + "type": "function" + } + ] + }, + "output": { + "content": [ + { + "input": "{\"city\":\"Vienna\"}", + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "finishReason": { + "unified": "tool-calls" + }, + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "inputTokens": { + "cacheRead": 0, + "noCache": 80, + "total": 80 + }, + "outputTokens": { + "reasoning": 0, + "text": 32, + "total": 32 + }, + "raw": { + "input_tokens": 80, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + } + } + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "finish_reason": { + "unified": "tool-calls" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses" + }, + "metrics": { + "completion_reasoning_tokens": 0, + "completion_tokens": 32, + "prompt_cached_tokens": 0, + "prompt_tokens": 80, + "reasoning_tokens": 0, + "tokens": 112 + } + }, + { + "name": "doGenerate", + "type": "llm", + "children": [], + "input": { + "maxOutputTokens": 32, + "prompt": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + }, + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + } + ], + "temperature": 0, + "toolChoice": { + "type": "auto" + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + }, + "name": "lookup_weather", + "type": "function" + } + ] + }, + "output": { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": { + "unified": "stop" + }, + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "inputTokens": { + "cacheRead": 0, + "noCache": 121, + "total": 121 + }, + "outputTokens": { + "reasoning": 0, + "text": 12, + "total": 12 + }, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + } + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "finish_reason": { + "unified": "stop" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses" + }, + "metrics": { + "completion_reasoning_tokens": 0, + "completion_tokens": 12, + "prompt_cached_tokens": 0, + "prompt_tokens": 121, + "reasoning_tokens": 0, + "tokens": 133 + } + }, + { + "name": "lookup_weather", + "type": "tool", + "children": [], + "input": { + "city": "Vienna" + }, + "output": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + } + ], + "input": { + "maxOutputTokens": 32, + "messages": [ + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "model": { + "config": { + "fileIdPrefixes": [ + "file-" + ], + "provider": "openai.responses" + }, + "modelId": "gpt-4.1-nano", + "specificationVersion": "v3", + "supportedUrls": { + "application/pdf": [ + {} + ], + "image/*": [ + {} + ] + } + }, + "system": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "temperature": 0, + "tools": { + "lookup_weather": { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + } + } + } + }, + "output": { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": "stop", + "output": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + }, + { + "content": [ + { + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "steps": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + }, + { + "dynamic": false, + "input": { + "city": "Vienna" + }, + "output": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + }, + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "finishReason": "tool-calls", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 80 + }, + "inputTokens": 80, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 32 + }, + "outputTokens": 32, + "raw": { + "input_tokens": 80, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 112 + }, + "warnings": [] + }, + { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": "stop", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + }, + { + "content": [ + { + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 121 + }, + "inputTokens": 121, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 12 + }, + "outputTokens": 12, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 133 + }, + "warnings": [] + } + ], + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "toolCalls": [], + "toolResults": [], + "totalUsage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 201 + }, + "inputTokens": 201, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 44 + }, + "outputTokens": 44, + "reasoningTokens": 0, + "totalTokens": 245 + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 121 + }, + "inputTokens": 121, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 12 + }, + "outputTokens": 12, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 133 + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses", + "tools": { + "lookup_weather": { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + } + } + } + } + } + ], + "input": [ + { + "id": "", + "parts": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "output": { + "id": "", + "parts": [ + { + "state": "done", + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + } + ], + "metadata": { + "mode": "auto", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + }, + { + "name": "cloudflare-ai-chat-error-root", + "type": "task", + "children": [ + { + "name": "AIChatAgent.onChatMessage", + "type": "task", + "children": [], + "input": [ + { + "id": "", + "parts": [ + { + "text": "Produce a deterministic streaming error.", + "type": "text" + } + ], + "role": "user" + } + ], + "output": { + "id": "", + "parts": [ + { + "state": "streaming", + "text": "partial response", + "type": "text" + } + ], + "role": "assistant" + }, + "error": "CLOUDFLARE_AI_CHAT_STREAM_ERROR" + } + ], + "metadata": { + "mode": "auto", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + } + ] +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-auto.span-tree.txt b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-auto.span-tree.txt new file mode 100644 index 000000000..248aaa22f --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-auto.span-tree.txt @@ -0,0 +1,776 @@ +span_tree: +├── cloudflare-ai-chat-success-root [task] +│ metadata: { +│ "mode": "auto", +│ "scenario": "cloudflare-ai-chat-instrumentation", +│ "testRunId": "" +│ } +│ └── AIChatAgent.onChatMessage [task] +│ input: [ +│ { +│ "id": "", +│ "parts": [ +│ { +│ "text": "Look up the weather in Vienna.", +│ "type": "text" +│ } +│ ], +│ "role": "user" +│ } +│ ] +│ output: { +│ "id": "", +│ "parts": [ +│ { +│ "state": "done", +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ └── generateText [function] +│ input: { +│ "maxOutputTokens": 32, +│ "messages": [ +│ { +│ "content": [ +│ { +│ "text": "Look up the weather in Vienna.", +│ "type": "text" +│ } +│ ], +│ "role": "user" +│ } +│ ], +│ "model": { +│ "config": { +│ "fileIdPrefixes": [ +│ "file-" +│ ], +│ "provider": "openai.responses" +│ }, +│ "modelId": "gpt-4.1-nano", +│ "specificationVersion": "v3", +│ "supportedUrls": { +│ "application/pdf": [ +│ {} +│ ], +│ "image/*": [ +│ {} +│ ] +│ } +│ }, +│ "system": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ "temperature": 0, +│ "tools": { +│ "lookup_weather": { +│ "description": "Return deterministic weather for one city.", +│ "inputSchema": { +│ "$schema": "http://json-schema.org/draft-07/schema#", +│ "additionalProperties": false, +│ "properties": { +│ "city": { +│ "type": "string" +│ } +│ }, +│ "required": [ +│ "city" +│ ], +│ "type": "object" +│ } +│ } +│ } +│ } +│ output: { +│ "content": [ +│ { +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "finishReason": "stop", +│ "output": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ }, +│ { +│ "content": [ +│ { +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "steps": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ }, +│ { +│ "dynamic": false, +│ "input": { +│ "city": "Vienna" +│ }, +│ "output": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ }, +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "finishReason": "tool-calls", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 80 +│ }, +│ "inputTokens": 80, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 32 +│ }, +│ "outputTokens": 32, +│ "raw": { +│ "input_tokens": 80, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 32, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 112 +│ }, +│ "warnings": [] +│ }, +│ { +│ "content": [ +│ { +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "finishReason": "stop", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ }, +│ { +│ "content": [ +│ { +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 121 +│ }, +│ "inputTokens": 121, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 12 +│ }, +│ "outputTokens": 12, +│ "raw": { +│ "input_tokens": 121, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 12, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 133 +│ }, +│ "warnings": [] +│ } +│ ], +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "toolCalls": [], +│ "toolResults": [], +│ "totalUsage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 201 +│ }, +│ "inputTokens": 201, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 44 +│ }, +│ "outputTokens": 44, +│ "reasoningTokens": 0, +│ "totalTokens": 245 +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 121 +│ }, +│ "inputTokens": 121, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 12 +│ }, +│ "outputTokens": 12, +│ "raw": { +│ "input_tokens": 121, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 12, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 133 +│ }, +│ "warnings": [] +│ } +│ metadata: { +│ "braintrust": { +│ "integration_name": "ai-sdk", +│ "sdk_language": "typescript" +│ }, +│ "model": "gpt-4.1-nano", +│ "provider": "openai.responses", +│ "tools": { +│ "lookup_weather": { +│ "description": "Return deterministic weather for one city.", +│ "inputSchema": { +│ "$schema": "http://json-schema.org/draft-07/schema#", +│ "additionalProperties": false, +│ "properties": { +│ "city": { +│ "type": "string" +│ } +│ }, +│ "required": [ +│ "city" +│ ], +│ "type": "object" +│ } +│ } +│ } +│ } +│ ├── doGenerate [llm] +│ │ input: { +│ │ "maxOutputTokens": 32, +│ │ "prompt": [ +│ │ { +│ │ "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ │ "role": "system" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "text": "Look up the weather in Vienna.", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "role": "user" +│ │ } +│ │ ], +│ │ "temperature": 0, +│ │ "toolChoice": { +│ │ "type": "auto" +│ │ }, +│ │ "tools": [ +│ │ { +│ │ "description": "Return deterministic weather for one city.", +│ │ "inputSchema": { +│ │ "$schema": "http://json-schema.org/draft-07/schema#", +│ │ "additionalProperties": false, +│ │ "properties": { +│ │ "city": { +│ │ "type": "string" +│ │ } +│ │ }, +│ │ "required": [ +│ │ "city" +│ │ ], +│ │ "type": "object" +│ │ }, +│ │ "name": "lookup_weather", +│ │ "type": "function" +│ │ } +│ │ ] +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "input": "{\"city\":\"Vienna\"}", +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-call" +│ │ } +│ │ ], +│ │ "finishReason": { +│ │ "unified": "tool-calls" +│ │ }, +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "responseId": "", +│ │ "serviceTier": "default" +│ │ } +│ │ }, +│ │ "response": { +│ │ "body": "", +│ │ "id": "", +│ │ "modelId": "gpt-4.1-nano-2025-04-14", +│ │ "timestamp": "" +│ │ }, +│ │ "usage": { +│ │ "inputTokens": { +│ │ "cacheRead": 0, +│ │ "noCache": 80, +│ │ "total": 80 +│ │ }, +│ │ "outputTokens": { +│ │ "reasoning": 0, +│ │ "text": 32, +│ │ "total": 32 +│ │ }, +│ │ "raw": { +│ │ "input_tokens": 80, +│ │ "input_tokens_details": { +│ │ "cached_tokens": 0 +│ │ }, +│ │ "output_tokens": 32, +│ │ "output_tokens_details": { +│ │ "reasoning_tokens": 0 +│ │ } +│ │ } +│ │ }, +│ │ "warnings": [] +│ │ } +│ │ metadata: { +│ │ "braintrust": { +│ │ "integration_name": "ai-sdk", +│ │ "sdk_language": "typescript" +│ │ }, +│ │ "finish_reason": { +│ │ "unified": "tool-calls" +│ │ }, +│ │ "model": "gpt-4.1-nano", +│ │ "provider": "openai.responses" +│ │ } +│ │ metrics: { +│ │ "completion_reasoning_tokens": 0, +│ │ "completion_tokens": 32, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 80, +│ │ "reasoning_tokens": 0, +│ │ "tokens": 112 +│ │ } +│ ├── doGenerate [llm] +│ │ input: { +│ │ "maxOutputTokens": 32, +│ │ "prompt": [ +│ │ { +│ │ "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ │ "role": "system" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "text": "Look up the weather in Vienna.", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "role": "user" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "input": { +│ │ "city": "Vienna" +│ │ }, +│ │ "providerOptions": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-call" +│ │ } +│ │ ], +│ │ "role": "assistant" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "output": { +│ │ "type": "json", +│ │ "value": { +│ │ "city": "Vienna", +│ │ "condition": "sunny", +│ │ "marker": "WEATHER_TOOL_EXECUTED" +│ │ } +│ │ }, +│ │ "providerOptions": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-result" +│ │ } +│ │ ], +│ │ "role": "tool" +│ │ } +│ │ ], +│ │ "temperature": 0, +│ │ "toolChoice": { +│ │ "type": "auto" +│ │ }, +│ │ "tools": [ +│ │ { +│ │ "description": "Return deterministic weather for one city.", +│ │ "inputSchema": { +│ │ "$schema": "http://json-schema.org/draft-07/schema#", +│ │ "additionalProperties": false, +│ │ "properties": { +│ │ "city": { +│ │ "type": "string" +│ │ } +│ │ }, +│ │ "required": [ +│ │ "city" +│ │ ], +│ │ "type": "object" +│ │ }, +│ │ "name": "lookup_weather", +│ │ "type": "function" +│ │ } +│ │ ] +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "finishReason": { +│ │ "unified": "stop" +│ │ }, +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "responseId": "", +│ │ "serviceTier": "default" +│ │ } +│ │ }, +│ │ "response": { +│ │ "body": "", +│ │ "id": "", +│ │ "modelId": "gpt-4.1-nano-2025-04-14", +│ │ "timestamp": "" +│ │ }, +│ │ "usage": { +│ │ "inputTokens": { +│ │ "cacheRead": 0, +│ │ "noCache": 121, +│ │ "total": 121 +│ │ }, +│ │ "outputTokens": { +│ │ "reasoning": 0, +│ │ "text": 12, +│ │ "total": 12 +│ │ }, +│ │ "raw": { +│ │ "input_tokens": 121, +│ │ "input_tokens_details": { +│ │ "cached_tokens": 0 +│ │ }, +│ │ "output_tokens": 12, +│ │ "output_tokens_details": { +│ │ "reasoning_tokens": 0 +│ │ } +│ │ } +│ │ }, +│ │ "warnings": [] +│ │ } +│ │ metadata: { +│ │ "braintrust": { +│ │ "integration_name": "ai-sdk", +│ │ "sdk_language": "typescript" +│ │ }, +│ │ "finish_reason": { +│ │ "unified": "stop" +│ │ }, +│ │ "model": "gpt-4.1-nano", +│ │ "provider": "openai.responses" +│ │ } +│ │ metrics: { +│ │ "completion_reasoning_tokens": 0, +│ │ "completion_tokens": 12, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 121, +│ │ "reasoning_tokens": 0, +│ │ "tokens": 133 +│ │ } +│ └── lookup_weather [tool] +│ input: { +│ "city": "Vienna" +│ } +│ output: { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +└── cloudflare-ai-chat-error-root [task] + metadata: { + "mode": "auto", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + └── AIChatAgent.onChatMessage [task] + input: [ + { + "id": "", + "parts": [ + { + "text": "Produce a deterministic streaming error.", + "type": "text" + } + ], + "role": "user" + } + ] + output: { + "id": "", + "parts": [ + { + "state": "streaming", + "text": "partial response", + "type": "text" + } + ], + "role": "assistant" + } + error: "CLOUDFLARE_AI_CHAT_STREAM_ERROR" diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-manual.span-tree.json b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-manual.span-tree.json new file mode 100644 index 000000000..5340dbf01 --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-manual.span-tree.json @@ -0,0 +1,815 @@ +{ + "span_tree": [ + { + "name": "cloudflare-ai-chat-success-root", + "type": "task", + "children": [ + { + "name": "AIChatAgent.onChatMessage", + "type": "task", + "children": [ + { + "name": "generateText", + "type": "function", + "children": [ + { + "name": "doGenerate", + "type": "llm", + "children": [], + "input": { + "maxOutputTokens": 32, + "prompt": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "temperature": 0, + "toolChoice": { + "type": "auto" + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + }, + "name": "lookup_weather", + "type": "function" + } + ] + }, + "output": { + "content": [ + { + "input": "{\"city\":\"Vienna\"}", + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "finishReason": { + "unified": "tool-calls" + }, + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "inputTokens": { + "cacheRead": 0, + "noCache": 80, + "total": 80 + }, + "outputTokens": { + "reasoning": 0, + "text": 32, + "total": 32 + }, + "raw": { + "input_tokens": 80, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + } + } + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "finish_reason": { + "unified": "tool-calls" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses" + }, + "metrics": { + "completion_reasoning_tokens": 0, + "completion_tokens": 32, + "prompt_cached_tokens": 0, + "prompt_tokens": 80, + "reasoning_tokens": 0, + "tokens": 112 + } + }, + { + "name": "doGenerate", + "type": "llm", + "children": [], + "input": { + "maxOutputTokens": 32, + "prompt": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + }, + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + } + ], + "temperature": 0, + "toolChoice": { + "type": "auto" + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + }, + "name": "lookup_weather", + "type": "function" + } + ] + }, + "output": { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": { + "unified": "stop" + }, + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "inputTokens": { + "cacheRead": 0, + "noCache": 121, + "total": 121 + }, + "outputTokens": { + "reasoning": 0, + "text": 12, + "total": 12 + }, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + } + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "finish_reason": { + "unified": "stop" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses" + }, + "metrics": { + "completion_reasoning_tokens": 0, + "completion_tokens": 12, + "prompt_cached_tokens": 0, + "prompt_tokens": 121, + "reasoning_tokens": 0, + "tokens": 133 + } + }, + { + "name": "lookup_weather", + "type": "tool", + "children": [], + "input": { + "city": "Vienna" + }, + "output": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + } + ], + "input": { + "maxOutputTokens": 32, + "messages": [ + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "model": { + "config": { + "fileIdPrefixes": [ + "file-" + ], + "provider": "openai.responses" + }, + "modelId": "gpt-4.1-nano", + "specificationVersion": "v3", + "supportedUrls": { + "application/pdf": [ + {} + ], + "image/*": [ + {} + ] + } + }, + "system": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "temperature": 0, + "tools": { + "lookup_weather": { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + } + } + } + }, + "output": { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": "stop", + "output": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + }, + { + "content": [ + { + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "steps": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + }, + { + "dynamic": false, + "input": { + "city": "Vienna" + }, + "output": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + }, + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "finishReason": "tool-calls", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 80 + }, + "inputTokens": 80, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 32 + }, + "outputTokens": 32, + "raw": { + "input_tokens": 80, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 112 + }, + "warnings": [] + }, + { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": "stop", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + }, + { + "content": [ + { + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 121 + }, + "inputTokens": 121, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 12 + }, + "outputTokens": 12, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 133 + }, + "warnings": [] + } + ], + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "toolCalls": [], + "toolResults": [], + "totalUsage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 201 + }, + "inputTokens": 201, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 44 + }, + "outputTokens": 44, + "reasoningTokens": 0, + "totalTokens": 245 + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 121 + }, + "inputTokens": 121, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 12 + }, + "outputTokens": 12, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 133 + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses", + "tools": { + "lookup_weather": { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + } + } + } + } + } + ], + "input": [ + { + "id": "", + "parts": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "output": { + "id": "", + "parts": [ + { + "state": "done", + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + } + ], + "metadata": { + "mode": "manual", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + }, + { + "name": "cloudflare-ai-chat-error-root", + "type": "task", + "children": [ + { + "name": "AIChatAgent.onChatMessage", + "type": "task", + "children": [], + "input": [ + { + "id": "", + "parts": [ + { + "text": "Produce a deterministic streaming error.", + "type": "text" + } + ], + "role": "user" + } + ], + "output": { + "id": "", + "parts": [ + { + "state": "streaming", + "text": "partial response", + "type": "text" + } + ], + "role": "assistant" + }, + "error": "CLOUDFLARE_AI_CHAT_STREAM_ERROR" + } + ], + "metadata": { + "mode": "manual", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + } + ] +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-manual.span-tree.txt b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-manual.span-tree.txt new file mode 100644 index 000000000..5da7fbbdc --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-latest-manual.span-tree.txt @@ -0,0 +1,776 @@ +span_tree: +├── cloudflare-ai-chat-success-root [task] +│ metadata: { +│ "mode": "manual", +│ "scenario": "cloudflare-ai-chat-instrumentation", +│ "testRunId": "" +│ } +│ └── AIChatAgent.onChatMessage [task] +│ input: [ +│ { +│ "id": "", +│ "parts": [ +│ { +│ "text": "Look up the weather in Vienna.", +│ "type": "text" +│ } +│ ], +│ "role": "user" +│ } +│ ] +│ output: { +│ "id": "", +│ "parts": [ +│ { +│ "state": "done", +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ └── generateText [function] +│ input: { +│ "maxOutputTokens": 32, +│ "messages": [ +│ { +│ "content": [ +│ { +│ "text": "Look up the weather in Vienna.", +│ "type": "text" +│ } +│ ], +│ "role": "user" +│ } +│ ], +│ "model": { +│ "config": { +│ "fileIdPrefixes": [ +│ "file-" +│ ], +│ "provider": "openai.responses" +│ }, +│ "modelId": "gpt-4.1-nano", +│ "specificationVersion": "v3", +│ "supportedUrls": { +│ "application/pdf": [ +│ {} +│ ], +│ "image/*": [ +│ {} +│ ] +│ } +│ }, +│ "system": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ "temperature": 0, +│ "tools": { +│ "lookup_weather": { +│ "description": "Return deterministic weather for one city.", +│ "inputSchema": { +│ "$schema": "http://json-schema.org/draft-07/schema#", +│ "additionalProperties": false, +│ "properties": { +│ "city": { +│ "type": "string" +│ } +│ }, +│ "required": [ +│ "city" +│ ], +│ "type": "object" +│ } +│ } +│ } +│ } +│ output: { +│ "content": [ +│ { +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "finishReason": "stop", +│ "output": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ }, +│ { +│ "content": [ +│ { +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "steps": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ }, +│ { +│ "dynamic": false, +│ "input": { +│ "city": "Vienna" +│ }, +│ "output": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ }, +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "finishReason": "tool-calls", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 80 +│ }, +│ "inputTokens": 80, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 32 +│ }, +│ "outputTokens": 32, +│ "raw": { +│ "input_tokens": 80, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 32, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 112 +│ }, +│ "warnings": [] +│ }, +│ { +│ "content": [ +│ { +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "finishReason": "stop", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ }, +│ { +│ "content": [ +│ { +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 121 +│ }, +│ "inputTokens": 121, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 12 +│ }, +│ "outputTokens": 12, +│ "raw": { +│ "input_tokens": 121, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 12, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 133 +│ }, +│ "warnings": [] +│ } +│ ], +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "toolCalls": [], +│ "toolResults": [], +│ "totalUsage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 201 +│ }, +│ "inputTokens": 201, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 44 +│ }, +│ "outputTokens": 44, +│ "reasoningTokens": 0, +│ "totalTokens": 245 +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 121 +│ }, +│ "inputTokens": 121, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 12 +│ }, +│ "outputTokens": 12, +│ "raw": { +│ "input_tokens": 121, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 12, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 133 +│ }, +│ "warnings": [] +│ } +│ metadata: { +│ "braintrust": { +│ "integration_name": "ai-sdk", +│ "sdk_language": "typescript" +│ }, +│ "model": "gpt-4.1-nano", +│ "provider": "openai.responses", +│ "tools": { +│ "lookup_weather": { +│ "description": "Return deterministic weather for one city.", +│ "inputSchema": { +│ "$schema": "http://json-schema.org/draft-07/schema#", +│ "additionalProperties": false, +│ "properties": { +│ "city": { +│ "type": "string" +│ } +│ }, +│ "required": [ +│ "city" +│ ], +│ "type": "object" +│ } +│ } +│ } +│ } +│ ├── doGenerate [llm] +│ │ input: { +│ │ "maxOutputTokens": 32, +│ │ "prompt": [ +│ │ { +│ │ "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ │ "role": "system" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "text": "Look up the weather in Vienna.", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "role": "user" +│ │ } +│ │ ], +│ │ "temperature": 0, +│ │ "toolChoice": { +│ │ "type": "auto" +│ │ }, +│ │ "tools": [ +│ │ { +│ │ "description": "Return deterministic weather for one city.", +│ │ "inputSchema": { +│ │ "$schema": "http://json-schema.org/draft-07/schema#", +│ │ "additionalProperties": false, +│ │ "properties": { +│ │ "city": { +│ │ "type": "string" +│ │ } +│ │ }, +│ │ "required": [ +│ │ "city" +│ │ ], +│ │ "type": "object" +│ │ }, +│ │ "name": "lookup_weather", +│ │ "type": "function" +│ │ } +│ │ ] +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "input": "{\"city\":\"Vienna\"}", +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-call" +│ │ } +│ │ ], +│ │ "finishReason": { +│ │ "unified": "tool-calls" +│ │ }, +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "responseId": "", +│ │ "serviceTier": "default" +│ │ } +│ │ }, +│ │ "response": { +│ │ "body": "", +│ │ "id": "", +│ │ "modelId": "gpt-4.1-nano-2025-04-14", +│ │ "timestamp": "" +│ │ }, +│ │ "usage": { +│ │ "inputTokens": { +│ │ "cacheRead": 0, +│ │ "noCache": 80, +│ │ "total": 80 +│ │ }, +│ │ "outputTokens": { +│ │ "reasoning": 0, +│ │ "text": 32, +│ │ "total": 32 +│ │ }, +│ │ "raw": { +│ │ "input_tokens": 80, +│ │ "input_tokens_details": { +│ │ "cached_tokens": 0 +│ │ }, +│ │ "output_tokens": 32, +│ │ "output_tokens_details": { +│ │ "reasoning_tokens": 0 +│ │ } +│ │ } +│ │ }, +│ │ "warnings": [] +│ │ } +│ │ metadata: { +│ │ "braintrust": { +│ │ "integration_name": "ai-sdk", +│ │ "sdk_language": "typescript" +│ │ }, +│ │ "finish_reason": { +│ │ "unified": "tool-calls" +│ │ }, +│ │ "model": "gpt-4.1-nano", +│ │ "provider": "openai.responses" +│ │ } +│ │ metrics: { +│ │ "completion_reasoning_tokens": 0, +│ │ "completion_tokens": 32, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 80, +│ │ "reasoning_tokens": 0, +│ │ "tokens": 112 +│ │ } +│ ├── doGenerate [llm] +│ │ input: { +│ │ "maxOutputTokens": 32, +│ │ "prompt": [ +│ │ { +│ │ "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ │ "role": "system" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "text": "Look up the weather in Vienna.", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "role": "user" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "input": { +│ │ "city": "Vienna" +│ │ }, +│ │ "providerOptions": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-call" +│ │ } +│ │ ], +│ │ "role": "assistant" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "output": { +│ │ "type": "json", +│ │ "value": { +│ │ "city": "Vienna", +│ │ "condition": "sunny", +│ │ "marker": "WEATHER_TOOL_EXECUTED" +│ │ } +│ │ }, +│ │ "providerOptions": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-result" +│ │ } +│ │ ], +│ │ "role": "tool" +│ │ } +│ │ ], +│ │ "temperature": 0, +│ │ "toolChoice": { +│ │ "type": "auto" +│ │ }, +│ │ "tools": [ +│ │ { +│ │ "description": "Return deterministic weather for one city.", +│ │ "inputSchema": { +│ │ "$schema": "http://json-schema.org/draft-07/schema#", +│ │ "additionalProperties": false, +│ │ "properties": { +│ │ "city": { +│ │ "type": "string" +│ │ } +│ │ }, +│ │ "required": [ +│ │ "city" +│ │ ], +│ │ "type": "object" +│ │ }, +│ │ "name": "lookup_weather", +│ │ "type": "function" +│ │ } +│ │ ] +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "finishReason": { +│ │ "unified": "stop" +│ │ }, +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "responseId": "", +│ │ "serviceTier": "default" +│ │ } +│ │ }, +│ │ "response": { +│ │ "body": "", +│ │ "id": "", +│ │ "modelId": "gpt-4.1-nano-2025-04-14", +│ │ "timestamp": "" +│ │ }, +│ │ "usage": { +│ │ "inputTokens": { +│ │ "cacheRead": 0, +│ │ "noCache": 121, +│ │ "total": 121 +│ │ }, +│ │ "outputTokens": { +│ │ "reasoning": 0, +│ │ "text": 12, +│ │ "total": 12 +│ │ }, +│ │ "raw": { +│ │ "input_tokens": 121, +│ │ "input_tokens_details": { +│ │ "cached_tokens": 0 +│ │ }, +│ │ "output_tokens": 12, +│ │ "output_tokens_details": { +│ │ "reasoning_tokens": 0 +│ │ } +│ │ } +│ │ }, +│ │ "warnings": [] +│ │ } +│ │ metadata: { +│ │ "braintrust": { +│ │ "integration_name": "ai-sdk", +│ │ "sdk_language": "typescript" +│ │ }, +│ │ "finish_reason": { +│ │ "unified": "stop" +│ │ }, +│ │ "model": "gpt-4.1-nano", +│ │ "provider": "openai.responses" +│ │ } +│ │ metrics: { +│ │ "completion_reasoning_tokens": 0, +│ │ "completion_tokens": 12, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 121, +│ │ "reasoning_tokens": 0, +│ │ "tokens": 133 +│ │ } +│ └── lookup_weather [tool] +│ input: { +│ "city": "Vienna" +│ } +│ output: { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +└── cloudflare-ai-chat-error-root [task] + metadata: { + "mode": "manual", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + └── AIChatAgent.onChatMessage [task] + input: [ + { + "id": "", + "parts": [ + { + "text": "Produce a deterministic streaming error.", + "type": "text" + } + ], + "role": "user" + } + ] + output: { + "id": "", + "parts": [ + { + "state": "streaming", + "text": "partial response", + "type": "text" + } + ], + "role": "assistant" + } + error: "CLOUDFLARE_AI_CHAT_STREAM_ERROR" diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-manual.span-tree.json b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-manual.span-tree.json new file mode 100644 index 000000000..5340dbf01 --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-manual.span-tree.json @@ -0,0 +1,815 @@ +{ + "span_tree": [ + { + "name": "cloudflare-ai-chat-success-root", + "type": "task", + "children": [ + { + "name": "AIChatAgent.onChatMessage", + "type": "task", + "children": [ + { + "name": "generateText", + "type": "function", + "children": [ + { + "name": "doGenerate", + "type": "llm", + "children": [], + "input": { + "maxOutputTokens": 32, + "prompt": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "temperature": 0, + "toolChoice": { + "type": "auto" + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + }, + "name": "lookup_weather", + "type": "function" + } + ] + }, + "output": { + "content": [ + { + "input": "{\"city\":\"Vienna\"}", + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "finishReason": { + "unified": "tool-calls" + }, + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "inputTokens": { + "cacheRead": 0, + "noCache": 80, + "total": 80 + }, + "outputTokens": { + "reasoning": 0, + "text": 32, + "total": 32 + }, + "raw": { + "input_tokens": 80, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + } + } + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "finish_reason": { + "unified": "tool-calls" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses" + }, + "metrics": { + "completion_reasoning_tokens": 0, + "completion_tokens": 32, + "prompt_cached_tokens": 0, + "prompt_tokens": 80, + "reasoning_tokens": 0, + "tokens": 112 + } + }, + { + "name": "doGenerate", + "type": "llm", + "children": [], + "input": { + "maxOutputTokens": 32, + "prompt": [ + { + "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "role": "system" + }, + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + }, + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + } + ], + "temperature": 0, + "toolChoice": { + "type": "auto" + }, + "tools": [ + { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + }, + "name": "lookup_weather", + "type": "function" + } + ] + }, + "output": { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": { + "unified": "stop" + }, + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "inputTokens": { + "cacheRead": 0, + "noCache": 121, + "total": 121 + }, + "outputTokens": { + "reasoning": 0, + "text": 12, + "total": 12 + }, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + } + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "finish_reason": { + "unified": "stop" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses" + }, + "metrics": { + "completion_reasoning_tokens": 0, + "completion_tokens": 12, + "prompt_cached_tokens": 0, + "prompt_tokens": 121, + "reasoning_tokens": 0, + "tokens": 133 + } + }, + { + "name": "lookup_weather", + "type": "tool", + "children": [], + "input": { + "city": "Vienna" + }, + "output": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + } + ], + "input": { + "maxOutputTokens": 32, + "messages": [ + { + "content": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "model": { + "config": { + "fileIdPrefixes": [ + "file-" + ], + "provider": "openai.responses" + }, + "modelId": "gpt-4.1-nano", + "specificationVersion": "v3", + "supportedUrls": { + "application/pdf": [ + {} + ], + "image/*": [ + {} + ] + } + }, + "system": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", + "temperature": 0, + "tools": { + "lookup_weather": { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + } + } + } + }, + "output": { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": "stop", + "output": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + }, + { + "content": [ + { + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "steps": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + }, + { + "dynamic": false, + "input": { + "city": "Vienna" + }, + "output": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + }, + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "finishReason": "tool-calls", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 80 + }, + "inputTokens": 80, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 32 + }, + "outputTokens": 32, + "raw": { + "input_tokens": 80, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 32, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 112 + }, + "warnings": [] + }, + { + "content": [ + { + "providerMetadata": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "finishReason": "stop", + "providerMetadata": { + "openai": { + "responseId": "", + "serviceTier": "default" + } + }, + "response": { + "body": "", + "id": "", + "messages": [ + { + "content": [ + { + "input": { + "city": "Vienna" + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-call" + } + ], + "role": "assistant" + }, + { + "content": [ + { + "output": { + "type": "json", + "value": { + "city": "Vienna", + "condition": "sunny", + "marker": "WEATHER_TOOL_EXECUTED" + } + }, + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "toolCallId": "", + "toolName": "lookup_weather", + "type": "tool-result" + } + ], + "role": "tool" + }, + { + "content": [ + { + "providerOptions": { + "openai": { + "itemId": "" + } + }, + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + ], + "modelId": "gpt-4.1-nano-2025-04-14", + "timestamp": "" + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 121 + }, + "inputTokens": 121, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 12 + }, + "outputTokens": 12, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 133 + }, + "warnings": [] + } + ], + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "toolCalls": [], + "toolResults": [], + "totalUsage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 201 + }, + "inputTokens": 201, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 44 + }, + "outputTokens": 44, + "reasoningTokens": 0, + "totalTokens": 245 + }, + "usage": { + "cachedInputTokens": 0, + "inputTokenDetails": { + "cacheReadTokens": 0, + "noCacheTokens": 121 + }, + "inputTokens": 121, + "outputTokenDetails": { + "reasoningTokens": 0, + "textTokens": 12 + }, + "outputTokens": 12, + "raw": { + "input_tokens": 121, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 12, + "output_tokens_details": { + "reasoning_tokens": 0 + } + }, + "reasoningTokens": 0, + "totalTokens": 133 + }, + "warnings": [] + }, + "metadata": { + "braintrust": { + "integration_name": "ai-sdk", + "sdk_language": "typescript" + }, + "model": "gpt-4.1-nano", + "provider": "openai.responses", + "tools": { + "lookup_weather": { + "description": "Return deterministic weather for one city.", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "city": { + "type": "string" + } + }, + "required": [ + "city" + ], + "type": "object" + } + } + } + } + } + ], + "input": [ + { + "id": "", + "parts": [ + { + "text": "Look up the weather in Vienna.", + "type": "text" + } + ], + "role": "user" + } + ], + "output": { + "id": "", + "parts": [ + { + "state": "done", + "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", + "type": "text" + } + ], + "role": "assistant" + } + } + ], + "metadata": { + "mode": "manual", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + }, + { + "name": "cloudflare-ai-chat-error-root", + "type": "task", + "children": [ + { + "name": "AIChatAgent.onChatMessage", + "type": "task", + "children": [], + "input": [ + { + "id": "", + "parts": [ + { + "text": "Produce a deterministic streaming error.", + "type": "text" + } + ], + "role": "user" + } + ], + "output": { + "id": "", + "parts": [ + { + "state": "streaming", + "text": "partial response", + "type": "text" + } + ], + "role": "assistant" + }, + "error": "CLOUDFLARE_AI_CHAT_STREAM_ERROR" + } + ], + "metadata": { + "mode": "manual", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + } + ] +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-manual.span-tree.txt b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-manual.span-tree.txt new file mode 100644 index 000000000..5da7fbbdc --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/__snapshots__/cloudflare-ai-chat-v0-manual.span-tree.txt @@ -0,0 +1,776 @@ +span_tree: +├── cloudflare-ai-chat-success-root [task] +│ metadata: { +│ "mode": "manual", +│ "scenario": "cloudflare-ai-chat-instrumentation", +│ "testRunId": "" +│ } +│ └── AIChatAgent.onChatMessage [task] +│ input: [ +│ { +│ "id": "", +│ "parts": [ +│ { +│ "text": "Look up the weather in Vienna.", +│ "type": "text" +│ } +│ ], +│ "role": "user" +│ } +│ ] +│ output: { +│ "id": "", +│ "parts": [ +│ { +│ "state": "done", +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ └── generateText [function] +│ input: { +│ "maxOutputTokens": 32, +│ "messages": [ +│ { +│ "content": [ +│ { +│ "text": "Look up the weather in Vienna.", +│ "type": "text" +│ } +│ ], +│ "role": "user" +│ } +│ ], +│ "model": { +│ "config": { +│ "fileIdPrefixes": [ +│ "file-" +│ ], +│ "provider": "openai.responses" +│ }, +│ "modelId": "gpt-4.1-nano", +│ "specificationVersion": "v3", +│ "supportedUrls": { +│ "application/pdf": [ +│ {} +│ ], +│ "image/*": [ +│ {} +│ ] +│ } +│ }, +│ "system": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ "temperature": 0, +│ "tools": { +│ "lookup_weather": { +│ "description": "Return deterministic weather for one city.", +│ "inputSchema": { +│ "$schema": "http://json-schema.org/draft-07/schema#", +│ "additionalProperties": false, +│ "properties": { +│ "city": { +│ "type": "string" +│ } +│ }, +│ "required": [ +│ "city" +│ ], +│ "type": "object" +│ } +│ } +│ } +│ } +│ output: { +│ "content": [ +│ { +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "finishReason": "stop", +│ "output": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ }, +│ { +│ "content": [ +│ { +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "steps": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ }, +│ { +│ "dynamic": false, +│ "input": { +│ "city": "Vienna" +│ }, +│ "output": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ }, +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "finishReason": "tool-calls", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 80 +│ }, +│ "inputTokens": 80, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 32 +│ }, +│ "outputTokens": 32, +│ "raw": { +│ "input_tokens": 80, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 32, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 112 +│ }, +│ "warnings": [] +│ }, +│ { +│ "content": [ +│ { +│ "providerMetadata": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "finishReason": "stop", +│ "providerMetadata": { +│ "openai": { +│ "responseId": "", +│ "serviceTier": "default" +│ } +│ }, +│ "response": { +│ "body": "", +│ "id": "", +│ "messages": [ +│ { +│ "content": [ +│ { +│ "input": { +│ "city": "Vienna" +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-call" +│ } +│ ], +│ "role": "assistant" +│ }, +│ { +│ "content": [ +│ { +│ "output": { +│ "type": "json", +│ "value": { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +│ }, +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "toolCallId": "", +│ "toolName": "lookup_weather", +│ "type": "tool-result" +│ } +│ ], +│ "role": "tool" +│ }, +│ { +│ "content": [ +│ { +│ "providerOptions": { +│ "openai": { +│ "itemId": "" +│ } +│ }, +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "type": "text" +│ } +│ ], +│ "role": "assistant" +│ } +│ ], +│ "modelId": "gpt-4.1-nano-2025-04-14", +│ "timestamp": "" +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 121 +│ }, +│ "inputTokens": 121, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 12 +│ }, +│ "outputTokens": 12, +│ "raw": { +│ "input_tokens": 121, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 12, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 133 +│ }, +│ "warnings": [] +│ } +│ ], +│ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ "toolCalls": [], +│ "toolResults": [], +│ "totalUsage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 201 +│ }, +│ "inputTokens": 201, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 44 +│ }, +│ "outputTokens": 44, +│ "reasoningTokens": 0, +│ "totalTokens": 245 +│ }, +│ "usage": { +│ "cachedInputTokens": 0, +│ "inputTokenDetails": { +│ "cacheReadTokens": 0, +│ "noCacheTokens": 121 +│ }, +│ "inputTokens": 121, +│ "outputTokenDetails": { +│ "reasoningTokens": 0, +│ "textTokens": 12 +│ }, +│ "outputTokens": 12, +│ "raw": { +│ "input_tokens": 121, +│ "input_tokens_details": { +│ "cached_tokens": 0 +│ }, +│ "output_tokens": 12, +│ "output_tokens_details": { +│ "reasoning_tokens": 0 +│ } +│ }, +│ "reasoningTokens": 0, +│ "totalTokens": 133 +│ }, +│ "warnings": [] +│ } +│ metadata: { +│ "braintrust": { +│ "integration_name": "ai-sdk", +│ "sdk_language": "typescript" +│ }, +│ "model": "gpt-4.1-nano", +│ "provider": "openai.responses", +│ "tools": { +│ "lookup_weather": { +│ "description": "Return deterministic weather for one city.", +│ "inputSchema": { +│ "$schema": "http://json-schema.org/draft-07/schema#", +│ "additionalProperties": false, +│ "properties": { +│ "city": { +│ "type": "string" +│ } +│ }, +│ "required": [ +│ "city" +│ ], +│ "type": "object" +│ } +│ } +│ } +│ } +│ ├── doGenerate [llm] +│ │ input: { +│ │ "maxOutputTokens": 32, +│ │ "prompt": [ +│ │ { +│ │ "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ │ "role": "system" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "text": "Look up the weather in Vienna.", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "role": "user" +│ │ } +│ │ ], +│ │ "temperature": 0, +│ │ "toolChoice": { +│ │ "type": "auto" +│ │ }, +│ │ "tools": [ +│ │ { +│ │ "description": "Return deterministic weather for one city.", +│ │ "inputSchema": { +│ │ "$schema": "http://json-schema.org/draft-07/schema#", +│ │ "additionalProperties": false, +│ │ "properties": { +│ │ "city": { +│ │ "type": "string" +│ │ } +│ │ }, +│ │ "required": [ +│ │ "city" +│ │ ], +│ │ "type": "object" +│ │ }, +│ │ "name": "lookup_weather", +│ │ "type": "function" +│ │ } +│ │ ] +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "input": "{\"city\":\"Vienna\"}", +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-call" +│ │ } +│ │ ], +│ │ "finishReason": { +│ │ "unified": "tool-calls" +│ │ }, +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "responseId": "", +│ │ "serviceTier": "default" +│ │ } +│ │ }, +│ │ "response": { +│ │ "body": "", +│ │ "id": "", +│ │ "modelId": "gpt-4.1-nano-2025-04-14", +│ │ "timestamp": "" +│ │ }, +│ │ "usage": { +│ │ "inputTokens": { +│ │ "cacheRead": 0, +│ │ "noCache": 80, +│ │ "total": 80 +│ │ }, +│ │ "outputTokens": { +│ │ "reasoning": 0, +│ │ "text": 32, +│ │ "total": 32 +│ │ }, +│ │ "raw": { +│ │ "input_tokens": 80, +│ │ "input_tokens_details": { +│ │ "cached_tokens": 0 +│ │ }, +│ │ "output_tokens": 32, +│ │ "output_tokens_details": { +│ │ "reasoning_tokens": 0 +│ │ } +│ │ } +│ │ }, +│ │ "warnings": [] +│ │ } +│ │ metadata: { +│ │ "braintrust": { +│ │ "integration_name": "ai-sdk", +│ │ "sdk_language": "typescript" +│ │ }, +│ │ "finish_reason": { +│ │ "unified": "tool-calls" +│ │ }, +│ │ "model": "gpt-4.1-nano", +│ │ "provider": "openai.responses" +│ │ } +│ │ metrics: { +│ │ "completion_reasoning_tokens": 0, +│ │ "completion_tokens": 32, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 80, +│ │ "reasoning_tokens": 0, +│ │ "tokens": 112 +│ │ } +│ ├── doGenerate [llm] +│ │ input: { +│ │ "maxOutputTokens": 32, +│ │ "prompt": [ +│ │ { +│ │ "content": "Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly CLOUDFLARE_AI_CHAT_TOOL_OK and no other text.", +│ │ "role": "system" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "text": "Look up the weather in Vienna.", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "role": "user" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "input": { +│ │ "city": "Vienna" +│ │ }, +│ │ "providerOptions": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-call" +│ │ } +│ │ ], +│ │ "role": "assistant" +│ │ }, +│ │ { +│ │ "content": [ +│ │ { +│ │ "output": { +│ │ "type": "json", +│ │ "value": { +│ │ "city": "Vienna", +│ │ "condition": "sunny", +│ │ "marker": "WEATHER_TOOL_EXECUTED" +│ │ } +│ │ }, +│ │ "providerOptions": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "toolCallId": "", +│ │ "toolName": "lookup_weather", +│ │ "type": "tool-result" +│ │ } +│ │ ], +│ │ "role": "tool" +│ │ } +│ │ ], +│ │ "temperature": 0, +│ │ "toolChoice": { +│ │ "type": "auto" +│ │ }, +│ │ "tools": [ +│ │ { +│ │ "description": "Return deterministic weather for one city.", +│ │ "inputSchema": { +│ │ "$schema": "http://json-schema.org/draft-07/schema#", +│ │ "additionalProperties": false, +│ │ "properties": { +│ │ "city": { +│ │ "type": "string" +│ │ } +│ │ }, +│ │ "required": [ +│ │ "city" +│ │ ], +│ │ "type": "object" +│ │ }, +│ │ "name": "lookup_weather", +│ │ "type": "function" +│ │ } +│ │ ] +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "itemId": "" +│ │ } +│ │ }, +│ │ "text": "CLOUDFLARE_AI_CHAT_TOOL_OK", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "finishReason": { +│ │ "unified": "stop" +│ │ }, +│ │ "providerMetadata": { +│ │ "openai": { +│ │ "responseId": "", +│ │ "serviceTier": "default" +│ │ } +│ │ }, +│ │ "response": { +│ │ "body": "", +│ │ "id": "", +│ │ "modelId": "gpt-4.1-nano-2025-04-14", +│ │ "timestamp": "" +│ │ }, +│ │ "usage": { +│ │ "inputTokens": { +│ │ "cacheRead": 0, +│ │ "noCache": 121, +│ │ "total": 121 +│ │ }, +│ │ "outputTokens": { +│ │ "reasoning": 0, +│ │ "text": 12, +│ │ "total": 12 +│ │ }, +│ │ "raw": { +│ │ "input_tokens": 121, +│ │ "input_tokens_details": { +│ │ "cached_tokens": 0 +│ │ }, +│ │ "output_tokens": 12, +│ │ "output_tokens_details": { +│ │ "reasoning_tokens": 0 +│ │ } +│ │ } +│ │ }, +│ │ "warnings": [] +│ │ } +│ │ metadata: { +│ │ "braintrust": { +│ │ "integration_name": "ai-sdk", +│ │ "sdk_language": "typescript" +│ │ }, +│ │ "finish_reason": { +│ │ "unified": "stop" +│ │ }, +│ │ "model": "gpt-4.1-nano", +│ │ "provider": "openai.responses" +│ │ } +│ │ metrics: { +│ │ "completion_reasoning_tokens": 0, +│ │ "completion_tokens": 12, +│ │ "prompt_cached_tokens": 0, +│ │ "prompt_tokens": 121, +│ │ "reasoning_tokens": 0, +│ │ "tokens": 133 +│ │ } +│ └── lookup_weather [tool] +│ input: { +│ "city": "Vienna" +│ } +│ output: { +│ "city": "Vienna", +│ "condition": "sunny", +│ "marker": "WEATHER_TOOL_EXECUTED" +│ } +└── cloudflare-ai-chat-error-root [task] + metadata: { + "mode": "manual", + "scenario": "cloudflare-ai-chat-instrumentation", + "testRunId": "" + } + └── AIChatAgent.onChatMessage [task] + input: [ + { + "id": "", + "parts": [ + { + "text": "Produce a deterministic streaming error.", + "type": "text" + } + ], + "role": "user" + } + ] + output: { + "id": "", + "parts": [ + { + "state": "streaming", + "text": "partial response", + "type": "text" + } + ], + "role": "assistant" + } + error: "CLOUDFLARE_AI_CHAT_STREAM_ERROR" diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/assertions.ts b/e2e/scenarios/cloudflare-ai-chat-instrumentation/assertions.ts new file mode 100644 index 000000000..93e078bbf --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/assertions.ts @@ -0,0 +1,152 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import type { CapturedLogEvent } from "../../helpers/mock-braintrust-server"; +import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; +import { + effectiveScenarioTimeoutMs, + withScenarioHarness, + type ScenarioRunContext, +} from "../../helpers/scenario-harness"; +import { matchSpanTreeSnapshot, spanTreeFields } from "../../helpers/span-tree"; +import { findAllSpans, findLatestSpan } from "../../helpers/trace-selectors"; + +const ERROR_ROOT_NAME = "cloudflare-ai-chat-error-root"; +const SUCCESS_ROOT_NAME = "cloudflare-ai-chat-success-root"; +const TASK_NAME = "AIChatAgent.onChatMessage"; + +type RunScenario = (harness: { + runNodeScenarioDir: (options: { + entry: string; + env: Record; + runContext?: ScenarioRunContext; + scenarioDir: string; + timeoutMs: number; + }) => Promise; +}) => Promise; + +export function defineCloudflareAIChatAssertions(options: { + name: string; + runScenario: RunScenario; + snapshotName: string; + testFileUrl: string; + timeoutMs: number; +}): void { + const snapshotPath = 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 complete task, model, and tool spans", testConfig, () => { + expect(setupError).toBeUndefined(); + const root = findLatestSpan(events, SUCCESS_ROOT_NAME); + const task = findAllSpans(events, TASK_NAME).find((candidate) => + sameTrace(candidate, root), + ); + const generate = spansInRoot(events, root, "generateText")[0]; + const llmSpans = spansInRoot(events, root, "doGenerate"); + const toolSpan = spansInRoot(events, root, "lookup_weather")[0]; + + expect(root?.metadata).toMatchObject({ + scenario: "cloudflare-ai-chat-instrumentation", + }); + expect(task).toBeDefined(); + expect(task?.span.type).toBe("task"); + expect(task?.input).toEqual([ + { + id: "user-success", + parts: [{ text: "Look up the weather in Vienna.", type: "text" }], + role: "user", + }, + ]); + expect(JSON.stringify(task?.output)).toContain( + "CLOUDFLARE_AI_CHAT_TOOL_OK", + ); + expect(generate?.span.parentIds).toContain(task?.span.id); + expect(llmSpans).toHaveLength(2); + expect(llmSpans).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + metadata: expect.objectContaining({ + model: "gpt-4.1-nano", + provider: expect.stringContaining("openai"), + }), + metrics: expect.objectContaining({ tokens: expect.any(Number) }), + }), + ]), + ); + expect(toolSpan).toMatchObject({ + input: { city: "Vienna" }, + output: expect.objectContaining({ marker: "WEATHER_TOOL_EXECUTED" }), + span: { type: "tool" }, + }); + }); + + test("captures and closes streaming failures", testConfig, () => { + expect(setupError).toBeUndefined(); + const root = findLatestSpan(events, ERROR_ROOT_NAME); + const task = findAllSpans(events, TASK_NAME).find((candidate) => + sameTrace(candidate, root), + ); + + expect(root).toBeDefined(); + expect(task?.span.ended).toBe(true); + expect(JSON.stringify(task?.output)).toContain("partial response"); + expect(String(task?.row.error)).toContain( + "CLOUDFLARE_AI_CHAT_STREAM_ERROR", + ); + }); + + test("matches the stable trace contract", testConfig, async () => { + expect(setupError).toBeUndefined(); + const roots = [ + findLatestSpan(events, SUCCESS_ROOT_NAME), + findLatestSpan(events, ERROR_ROOT_NAME), + ].filter((event): event is CapturedLogEvent => event !== undefined); + const rootIds = new Set(roots.map((root) => root.span.rootId)); + const selected = [ + ...roots, + ...findAllSpans(events, TASK_NAME), + ...findAllSpans(events, "generateText"), + ...findAllSpans(events, "doGenerate"), + ...findAllSpans(events, "lookup_weather"), + ].filter((event) => rootIds.has(event.span.rootId)); + + await matchSpanTreeSnapshot( + selected.map((event) => ({ event, fields: spanTreeFields(event) })), + snapshotPath, + { snapshotExpect: expect }, + ); + }); + }); +} + +function spansInRoot( + events: CapturedLogEvent[], + root: CapturedLogEvent | undefined, + name: string, +): CapturedLogEvent[] { + return findAllSpans(events, name).filter((event) => sameTrace(event, root)); +} + +function sameTrace( + event: CapturedLogEvent, + root: CapturedLogEvent | undefined, +): boolean { + return event.span.rootId === root?.span.rootId; +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/package.json b/e2e/scenarios/cloudflare-ai-chat-instrumentation/package.json new file mode 100644 index 000000000..1481a7d67 --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/package.json @@ -0,0 +1,29 @@ +{ + "name": "cloudflare-ai-chat-instrumentation-scenario", + "private": true, + "type": "module", + "dependencies": { + "@ai-sdk/openai": "3.0.7", + "@ai-sdk/react": "3.0.204", + "@cloudflare/vite-plugin": "^1.42.1", + "agents": "0.17.4", + "ai": "6.0.37", + "cloudflare-ai-chat-v0": "npm:@cloudflare/ai-chat@0.9.0", + "cloudflare-ai-chat-v0-latest": "npm:@cloudflare/ai-chat@0.9.3", + "dc-browser": "1.0.4", + "react": "19.2.4", + "vite": "^8.0.16", + "wrangler": "^4.103.0", + "zod": "4.3.6" + }, + "braintrustScenario": { + "bump": { + "dependencies": { + "cloudflare-ai-chat-v0-latest": { + "package": "@cloudflare/ai-chat", + "range": "0.9" + } + } + } + } +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/pnpm-lock.yaml b/e2e/scenarios/cloudflare-ai-chat-instrumentation/pnpm-lock.yaml new file mode 100644 index 000000000..9fc3f692d --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/pnpm-lock.yaml @@ -0,0 +1,3096 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ai-sdk/openai': + specifier: 3.0.7 + version: 3.0.7(zod@4.3.6) + '@ai-sdk/react': + specifier: 3.0.204 + version: 3.0.204(react@19.2.4)(zod@4.3.6) + '@cloudflare/vite-plugin': + specifier: ^1.42.1 + version: 1.45.1(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))(wrangler@4.112.0) + agents: + specifier: 0.17.4 + version: 0.17.4(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(@babel/core@8.0.1)(@babel/runtime@7.29.7)(ai@6.0.37(zod@4.3.6))(react@19.2.4)(rolldown@1.1.5)(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))(zod@4.3.6) + ai: + specifier: 6.0.37 + version: 6.0.37(zod@4.3.6) + cloudflare-ai-chat-v0: + specifier: npm:@cloudflare/ai-chat@0.9.0 + version: '@cloudflare/ai-chat@0.9.0(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(agents@0.17.4(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(@babel/core@8.0.1)(@babel/runtime@7.29.7)(ai@6.0.37(zod@4.3.6))(react@19.2.4)(rolldown@1.1.5)(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))(zod@4.3.6))(ai@6.0.37(zod@4.3.6))(react@19.2.4)(zod@4.3.6)' + cloudflare-ai-chat-v0-latest: + specifier: npm:@cloudflare/ai-chat@0.9.3 + version: '@cloudflare/ai-chat@0.9.3(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(agents@0.17.4(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(@babel/core@8.0.1)(@babel/runtime@7.29.7)(ai@6.0.37(zod@4.3.6))(react@19.2.4)(rolldown@1.1.5)(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))(zod@4.3.6))(ai@6.0.37(zod@4.3.6))(react@19.2.4)(zod@4.3.6)' + dc-browser: + specifier: 1.0.4 + version: 1.0.4 + react: + specifier: 19.2.4 + version: 19.2.4 + vite: + specifier: ^8.0.16 + version: 8.1.5(esbuild@0.28.1)(yaml@2.9.0) + wrangler: + specifier: ^4.103.0 + version: 4.112.0 + zod: + specifier: 4.3.6 + version: 4.3.6 + +packages: + + '@ai-sdk/gateway@3.0.128': + resolution: {integrity: sha512-t9Dyqk5thSmIzsNvVTQtZZiO3xqKGkk1hX3+GNpYmro+GuEdW+E6mKFHihb9Y1vCEt3rEyd4pbGUcin4D57FfQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/gateway@3.0.15': + resolution: {integrity: sha512-OsWcXMfkF9U38YhU7926rYt4IAtJuZlnM1e8STN8hHb4qbiTaORBSXcFaJuOEZ1kYOf3DqqP7CxbmM543ePzIg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/openai@3.0.7': + resolution: {integrity: sha512-CBoYn1U59Lop8yBL9KuVjHCKc/B06q9Qo0SasRwHoyMEq+X4I8LQZu3a8Ck1jwwcZTTxfyiExB70LtIRSynBDA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.28': + resolution: {integrity: sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.4': + resolution: {integrity: sha512-VxhX0B/dWGbpNHxrKCWUAJKXIXV015J4e7qYjdIU9lLWeptk0KMLGcqkB4wFxff5Njqur8dt8wRi1MN9lZtDqg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.7': + resolution: {integrity: sha512-ItzTdBxRLieGz1GHPwl9X3+HKfwTfFd9MdIa91aXRnOjUVRw68ENjAGKm3FcXGsBLkXDLaFWgjbTVdXe2igs2w==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@3.0.10': + resolution: {integrity: sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==} + engines: {node: '>=18'} + + '@ai-sdk/provider@3.0.2': + resolution: {integrity: sha512-HrEmNt/BH/hkQ7zpi2o6N3k1ZR1QTb7z85WYhYygiTxOQuaml4CMtHCWRbric5WPU+RNsYI7r1EpyVQMKO1pYw==} + engines: {node: '>=18'} + + '@ai-sdk/provider@3.0.3': + resolution: {integrity: sha512-qGPYdoAuECaUXPrrz0BPX1SacZQuJ6zky0aakxpW89QW1hrY0eF4gcFm/3L9Pk8C5Fwe+RvBf2z7ZjDhaPjnlg==} + engines: {node: '>=18'} + + '@ai-sdk/react@3.0.204': + resolution: {integrity: sha512-4I5fq9n/YBCEPfbvYEzc5lFNMxoF2hZRXVrUAsjA6NL7LY8AJo3V3PD2TH9rTKIY08uolnyJSnnQc7YuFXXUIA==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 + + '@babel/code-frame@8.0.0': + resolution: {integrity: sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/compat-data@8.0.0': + resolution: {integrity: sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/core@8.0.1': + resolution: {integrity: sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/generator@8.0.0': + resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-annotate-as-pure@8.0.0': + resolution: {integrity: sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-compilation-targets@8.0.0': + resolution: {integrity: sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-create-class-features-plugin@8.0.1': + resolution: {integrity: sha512-++t3ZktzlLmASAxIlxeXQK9Z2YwUafYGYcvGBFevqOqt16HozVHStUoQvWD09fzAZOb/uJGpUTBuGK41AJAuOA==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-globals@8.0.0': + resolution: {integrity: sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-member-expression-to-functions@8.0.0': + resolution: {integrity: sha512-xkXrMbtk87Gk7+oKBVmBc6EORg/Qwx++AHESldmHkpvG8wgccdhJJFwrzqlF382Fk8wfXhJHWE/g/43QvEGNPQ==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-optimise-call-expression@8.0.0': + resolution: {integrity: sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-plugin-utils@8.0.1': + resolution: {integrity: sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-replace-supers@8.0.1': + resolution: {integrity: sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@8.0.0': + resolution: {integrity: sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-string-parser@8.0.0': + resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-validator-identifier@8.0.4': + resolution: {integrity: sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-validator-option@8.0.0': + resolution: {integrity: sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helpers@8.0.0': + resolution: {integrity: sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/parser@8.0.4': + resolution: {integrity: sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + + '@babel/plugin-proposal-decorators@8.0.2': + resolution: {integrity: sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-syntax-decorators@8.0.1': + resolution: {integrity: sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/runtime-corejs3@7.29.7': + resolution: {integrity: sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@8.0.0': + resolution: {integrity: sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/traverse@8.0.4': + resolution: {integrity: sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/types@8.0.4': + resolution: {integrity: sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@cfworker/json-schema@4.1.1': + resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + + '@cloudflare/ai-chat@0.9.0': + resolution: {integrity: sha512-EgohVDjH4+RPEizwTHdrJfZhESSTEOlfLZf0jPpy5EcDg3DiOFPH1yR545RFBeLZjMCFzHv+JQDVDzQUkNCsbA==} + peerDependencies: + '@ai-sdk/react': ^3.0.204 + agents: '>=0.17.0 <1.0.0' + ai: ^6.0.0 + react: ^19.0.0 + zod: ^4.0.0 + + '@cloudflare/ai-chat@0.9.3': + resolution: {integrity: sha512-IjOngV/V+Fmlzc4wIl9to2/dKOAB1T5iCl1p623ArLEXPD4d9T5fBQkXWkRlMlVqQvTANZyWAKjwZWHwNdO9/g==} + peerDependencies: + '@ai-sdk/react': ^3.0.204 + agents: '>=0.17.1 <1.0.0' + ai: ^6.0.0 + react: ^19.0.0 + zod: ^4.0.0 + + '@cloudflare/codemode@0.4.3': + resolution: {integrity: sha512-S6LIqj/NnmFRFxm3j0tPEGMF8HQF5DpV7sQg/W+U48YmnznTKOBcbS8eiV7SxBpIITLuMBcL4KpTDm1RDL20pA==} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.0 + '@tanstack/ai': '>=0.8.0 <1.0.0' + ai: ^6.0.0 + zod: ^4.0.0 + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true + '@tanstack/ai': + optional: true + ai: + optional: true + zod: + optional: true + + '@cloudflare/kv-asset-handler@0.5.0': + resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} + engines: {node: '>=22.0.0'} + + '@cloudflare/unenv-preset@2.16.1': + resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: '>1.20260305.0 <2.0.0-0' + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/vite-plugin@1.45.1': + resolution: {integrity: sha512-C+iDpO9pVH7IqrjdYtUV+obcTAdpiNk0OSinGEZZyd2ZWyGVjGk7iJ2p3xpMBpZuTa1I4hfAECNp0yN/8f3PIQ==} + hasBin: true + peerDependencies: + vite: ^6.1.0 || ^7.0.0 || ^8.0.0 + wrangler: ^4.112.0 + + '@cloudflare/workerd-darwin-64@1.20260714.1': + resolution: {integrity: sha512-ZWXqAN8G7Cx9hMRQuk+59ziJhR3j1F4iO+Qs8aHdfKZ3Dq5Yi/57xvkJTgCGBnW1YU/L78r8f6HEy51bwbTpNw==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260714.1': + resolution: {integrity: sha512-tueWxWC3wyCbMG6zRAxsMXX0YLgrRWbiAPYFQ2uJ7dUH8G+5E7UTWaQS9B1HdJ0bpKFW1NWxhs1o2noKVFSUYg==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20260714.1': + resolution: {integrity: sha512-1VChTZRb0l0F7R4e1G5RtLKV4oFi6x+rQgxh2+yu887j3l/3TLgatuv1L8/5zhc9gKEhATTxOh0e52Rtd9dDWQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260714.1': + resolution: {integrity: sha512-rMm3G+NirG2UdgHIRDdF1asNC6FqgIzZzkRG+VDhhDGcVxAQwvrMT1E38BivEvHr3G04MB4AfhcOczX0+GtRkQ==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20260714.1': + resolution: {integrity: sha512-cGqnU3Hg2YZS/k3SAqrMp1DjpdsyFde72tWltdl6ZT9+SFz/Zrk/8gyTU1TcxC4YApXeNVH5TyU5cOGPgUJ0pg==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@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 + + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@oxc-project/types@0.139.0': + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + + '@rolldown/binding-android-arm64@1.1.5': + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.5': + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.5': + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.5': + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.5': + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.5': + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.5': + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.5': + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.5': + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.5': + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/plugin-babel@0.2.3': + resolution: {integrity: sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==} + engines: {node: '>=22.12.0 || ^24.0.0'} + peerDependencies: + '@babel/core': ^7.29.0 || ^8.0.0-rc.1 + '@babel/plugin-transform-runtime': ^7.29.0 || ^8.0.0-rc.1 + '@babel/runtime': ^7.27.0 || ^8.0.0-rc.1 + rolldown: ^1.0.0-rc.5 + vite: ^8.0.0 + peerDependenciesMeta: + '@babel/plugin-transform-runtime': + optional: true + '@babel/runtime': + optional: true + vite: + optional: true + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} + engines: {node: '>=18'} + + '@speed-highlight/core@1.2.17': + resolution: {integrity: sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/gensync@1.0.5': + resolution: {integrity: sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==} + + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@vercel/oidc@3.1.0': + resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} + engines: {node: '>= 20'} + + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + engines: {node: '>= 20'} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agents@0.17.4: + resolution: {integrity: sha512-K6YRbpD3VcwdTOPBlDgI4dILAwkhXo5cdxTlVF0IvUwQEKfMPawmH8E/QMXTN8CPGHqVYgYFACxTyk6nKlK+vg==} + hasBin: true + peerDependencies: + '@ai-sdk/react': ^3.0.204 + '@tanstack/ai': '>=0.10.2 <1.0.0' + '@x402/core': ^2.0.0 + '@x402/evm': ^2.0.0 + ai: ^6.0.0 + chat: ^4.29.0 + just-bash: ^3.0.0 + react: ^19.0.0 + vite: '>=6.0.0 <9.0.0' + zod: ^4.0.0 + peerDependenciesMeta: + '@ai-sdk/react': + optional: true + '@tanstack/ai': + optional: true + '@x402/core': + optional: true + '@x402/evm': + optional: true + ai: + optional: true + chat: + optional: true + just-bash: + optional: true + vite: + optional: true + + ai@6.0.202: + resolution: {integrity: sha512-14O7uyHVa5lXyt1RzduRqM1zwOnLBN+EaE+btWiP2R7GLHd5oh+Z9uaebR785V/Y2hXlxvuQGsw2Gj6iHpkxsg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ai@6.0.37: + resolution: {integrity: sha512-GUMBYx0TXKxXRcWy6DY3ryHJZ7cATxc99WPT7WCD8hGCYkdhFVItKPTtINeTlK+FlUomDqzjPGtiDcVftcw//w==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + 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==} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + baseline-browser-mapping@2.10.43: + resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + browserslist@4.28.6: + resolution: {integrity: sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + 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'} + + caniuse-lite@1.0.30001806: + resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + + 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'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + 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'} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + core-js-pure@3.49.0: + resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cron-schedule@6.0.0: + resolution: {integrity: sha512-BoZaseYGXOo5j5HUwTaegIog3JJbuH4BbrY9A1ArLjXpy+RWb3mV28F/9Gv1dDA7E2L8kngWva4NWisnLTyfgQ==} + engines: {node: '>=20'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + dc-browser@1.0.4: + resolution: {integrity: sha512-7oEtnzNlcE+hr4OvO3GR6Gndgw8BhW+wKOEwMqSleyY7N29jbAxzyW5BaJl7qBCw+6OIxfMWtY0T+6dxq8RWLw==} + + 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'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=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==} + + electron-to-chromium@1.5.393: + resolution: {integrity: sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + 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'} + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + 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'} + + event-target-polyfill@0.0.4: + resolution: {integrity: sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==} + + 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.6.0: + resolution: {integrity: sha512-XKJXDsASUOo0LLtFwW5hCcQGH0N4WQc/Rn8/Pvoia+TJFOkkFPvrtW9lZOeeNcxQJspvOIERMwiRLsVFlhHEkA==} + 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.4: + resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: 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'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + 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.31: + resolution: {integrity: sha512-zJIHFrl6bq3RDd2YusFNCDlM8qUprxKswyi/OPzPyzKDdyBXDqWx8bZlZ7R+saTdSTatUmb3O7K4SspGPaEOQg==} + 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.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + 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==} + + js-base64@3.9.1: + resolution: {integrity: sha512-U73qptcvf/HIOauFOmqT3a0mDUp0MYlfd15oqoe9kqZt5XhiXVb+HG09sLvI9PQ9tZIBFS4nlErai8zbWazP0g==} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + 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.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimetext@3.0.28: + resolution: {integrity: sha512-eQXpbNrtxLCjUtiVbR/qR09dbPgZ2o+KR1uA7QKqGhbn8QV7HIL16mXXsobBL4/8TqoYh1us31kfz+dNfCev9g==} + + miniflare@4.20260714.0: + resolution: {integrity: sha512-MYlTCLdWCPqvrYY2uLwOjXwmglXuiHE3TGGkbOW4BwjUPa1r07E0iuHwrNDIs/sxK21r+o90Jx58AV2KeNdJZw==} + engines: {node: '>=22.0.0'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@5.1.16: + resolution: {integrity: sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==} + engines: {node: ^18 || >=20} + hasBin: true + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-releases@2.0.51: + resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + engines: {node: '>=18'} + + 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'} + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + 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==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + partyserver@0.5.8: + resolution: {integrity: sha512-htgSwiBcBu9zIYLrsxBAOvdkjukHvncbTk0nDrJgfruvZ08rxtEN1Ab4T7j9osykP80Bq3zA2oWFd3ngc4Z9uw==} + peerDependencies: + '@cloudflare/workers-types': ^4.20260424.1 + + partysocket@1.3.0: + resolution: {integrity: sha512-1zToNyolZFK/7nuAw/K2bZrNzFqaZyRoCEkS+9vG6WSC5ikrN6qWRe96q6ImU51uptz2r+dAwSkwhJVdQi4LiA==} + peerDependencies: + react: '>=17' + peerDependenciesMeta: + react: + optional: true + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + postcss@8.5.20: + resolution: {integrity: sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==} + engines: {node: ^10 || ^12 || >=14} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rolldown@1.1.5: + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + 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==} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + 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'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + swr@2.4.2: + resolution: {integrity: sha512-ej644Y2bvkIajfR32KGeSSdBXQW+ScjGjkybZgSE7kFpk9eGnV44XY9FJylXi+W75pavSX1PVNB57W5EbhGIYw==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + 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'} + + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite@8.1.5: + resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + workerd@1.20260714.1: + resolution: {integrity: sha512-oIbQzfdyl9UQUnG6XLegcSq0Mgt/7WKDbFOoqGgOWCS+/fhyGB460uKEgdAQQ9RHCO/ttcNCX/KiMIQzdoeu3Q==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.112.0: + resolution: {integrity: sha512-5H+XUD0TySCv1LuktFHDIEOkboH2nTfQs+35L+USt3MtntjDTMVIJprLgQcL2WBjulOyjxpd1vyTiSTJVW5MjQ==} + engines: {node: '>=22.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^5.20260714.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@ai-sdk/gateway@3.0.128(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.10 + '@ai-sdk/provider-utils': 4.0.28(zod@4.3.6) + '@vercel/oidc': 3.2.0 + zod: 4.3.6 + + '@ai-sdk/gateway@3.0.15(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.3 + '@ai-sdk/provider-utils': 4.0.7(zod@4.3.6) + '@vercel/oidc': 3.1.0 + zod: 4.3.6 + + '@ai-sdk/openai@3.0.7(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.2 + '@ai-sdk/provider-utils': 4.0.4(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/provider-utils@4.0.28(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.10 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.1.0 + zod: 4.3.6 + + '@ai-sdk/provider-utils@4.0.4(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.2 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.1.0 + zod: 4.3.6 + + '@ai-sdk/provider-utils@4.0.7(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.3 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.1.0 + zod: 4.3.6 + + '@ai-sdk/provider@3.0.10': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/provider@3.0.2': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/provider@3.0.3': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6)': + dependencies: + '@ai-sdk/provider-utils': 4.0.28(zod@4.3.6) + ai: 6.0.202(zod@4.3.6) + react: 19.2.4 + swr: 2.4.2(react@19.2.4) + throttleit: 2.1.0 + transitivePeerDependencies: + - zod + + '@babel/code-frame@8.0.0': + dependencies: + '@babel/helper-validator-identifier': 8.0.4 + js-tokens: 10.0.0 + + '@babel/compat-data@8.0.0': {} + + '@babel/core@8.0.1': + dependencies: + '@babel/code-frame': 8.0.0 + '@babel/generator': 8.0.0 + '@babel/helper-compilation-targets': 8.0.0 + '@babel/helpers': 8.0.0 + '@babel/parser': 8.0.4 + '@babel/template': 8.0.0 + '@babel/traverse': 8.0.4 + '@babel/types': 8.0.4 + '@types/gensync': 1.0.5 + convert-source-map: 2.0.0 + empathic: 2.0.1 + gensync: 1.0.0-beta.2 + import-meta-resolve: 4.2.0 + json5: 2.2.3 + obug: 2.1.4 + semver: 7.8.5 + + '@babel/generator@8.0.0': + dependencies: + '@babel/parser': 8.0.4 + '@babel/types': 8.0.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@8.0.0': + dependencies: + '@babel/types': 8.0.4 + + '@babel/helper-compilation-targets@8.0.0': + dependencies: + '@babel/compat-data': 8.0.0 + '@babel/helper-validator-option': 8.0.0 + browserslist: 4.28.6 + lru-cache: 11.5.2 + semver: 7.8.5 + + '@babel/helper-create-class-features-plugin@8.0.1(@babel/core@8.0.1)': + dependencies: + '@babel/core': 8.0.1 + '@babel/helper-annotate-as-pure': 8.0.0 + '@babel/helper-member-expression-to-functions': 8.0.0 + '@babel/helper-optimise-call-expression': 8.0.0 + '@babel/helper-replace-supers': 8.0.1(@babel/core@8.0.1) + '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 + '@babel/traverse': 8.0.4 + semver: 7.8.5 + + '@babel/helper-globals@8.0.0': {} + + '@babel/helper-member-expression-to-functions@8.0.0': + dependencies: + '@babel/traverse': 8.0.4 + '@babel/types': 8.0.4 + + '@babel/helper-optimise-call-expression@8.0.0': + dependencies: + '@babel/types': 8.0.4 + + '@babel/helper-plugin-utils@8.0.1(@babel/core@8.0.1)': + dependencies: + '@babel/core': 8.0.1 + + '@babel/helper-replace-supers@8.0.1(@babel/core@8.0.1)': + dependencies: + '@babel/core': 8.0.1 + '@babel/helper-member-expression-to-functions': 8.0.0 + '@babel/helper-optimise-call-expression': 8.0.0 + '@babel/traverse': 8.0.4 + + '@babel/helper-skip-transparent-expression-wrappers@8.0.0': + dependencies: + '@babel/traverse': 8.0.4 + '@babel/types': 8.0.4 + + '@babel/helper-string-parser@8.0.0': {} + + '@babel/helper-validator-identifier@8.0.4': {} + + '@babel/helper-validator-option@8.0.0': {} + + '@babel/helpers@8.0.0': + dependencies: + '@babel/template': 8.0.0 + '@babel/types': 8.0.4 + + '@babel/parser@8.0.4': + dependencies: + '@babel/types': 8.0.4 + + '@babel/plugin-proposal-decorators@8.0.2(@babel/core@8.0.1)': + dependencies: + '@babel/core': 8.0.1 + '@babel/helper-create-class-features-plugin': 8.0.1(@babel/core@8.0.1) + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/plugin-syntax-decorators': 8.0.1(@babel/core@8.0.1) + + '@babel/plugin-syntax-decorators@8.0.1(@babel/core@8.0.1)': + dependencies: + '@babel/core': 8.0.1 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + + '@babel/runtime-corejs3@7.29.7': + dependencies: + core-js-pure: 3.49.0 + + '@babel/runtime@7.29.7': {} + + '@babel/template@8.0.0': + dependencies: + '@babel/code-frame': 8.0.0 + '@babel/parser': 8.0.4 + '@babel/types': 8.0.4 + + '@babel/traverse@8.0.4': + dependencies: + '@babel/code-frame': 8.0.0 + '@babel/generator': 8.0.0 + '@babel/helper-globals': 8.0.0 + '@babel/parser': 8.0.4 + '@babel/template': 8.0.0 + '@babel/types': 8.0.4 + obug: 2.1.4 + + '@babel/types@8.0.4': + dependencies: + '@babel/helper-string-parser': 8.0.0 + '@babel/helper-validator-identifier': 8.0.4 + + '@cfworker/json-schema@4.1.1': {} + + '@cloudflare/ai-chat@0.9.0(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(agents@0.17.4(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(@babel/core@8.0.1)(@babel/runtime@7.29.7)(ai@6.0.37(zod@4.3.6))(react@19.2.4)(rolldown@1.1.5)(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))(zod@4.3.6))(ai@6.0.37(zod@4.3.6))(react@19.2.4)(zod@4.3.6)': + dependencies: + '@ai-sdk/react': 3.0.204(react@19.2.4)(zod@4.3.6) + agents: 0.17.4(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(@babel/core@8.0.1)(@babel/runtime@7.29.7)(ai@6.0.37(zod@4.3.6))(react@19.2.4)(rolldown@1.1.5)(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))(zod@4.3.6) + ai: 6.0.37(zod@4.3.6) + nanoid: 5.1.16 + react: 19.2.4 + zod: 4.3.6 + + '@cloudflare/ai-chat@0.9.3(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(agents@0.17.4(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(@babel/core@8.0.1)(@babel/runtime@7.29.7)(ai@6.0.37(zod@4.3.6))(react@19.2.4)(rolldown@1.1.5)(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))(zod@4.3.6))(ai@6.0.37(zod@4.3.6))(react@19.2.4)(zod@4.3.6)': + dependencies: + '@ai-sdk/react': 3.0.204(react@19.2.4)(zod@4.3.6) + agents: 0.17.4(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(@babel/core@8.0.1)(@babel/runtime@7.29.7)(ai@6.0.37(zod@4.3.6))(react@19.2.4)(rolldown@1.1.5)(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))(zod@4.3.6) + ai: 6.0.37(zod@4.3.6) + nanoid: 5.1.16 + react: 19.2.4 + zod: 4.3.6 + + '@cloudflare/codemode@0.4.3(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(ai@6.0.37(zod@4.3.6))(zod@4.3.6)': + dependencies: + '@types/json-schema': 7.0.15 + acorn: 8.17.0 + optionalDependencies: + '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + ai: 6.0.37(zod@4.3.6) + zod: 4.3.6 + + '@cloudflare/kv-asset-handler@0.5.0': {} + + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260714.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260714.1 + + '@cloudflare/vite-plugin@1.45.1(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))(wrangler@4.112.0)': + dependencies: + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260714.1) + miniflare: 4.20260714.0 + unenv: 2.0.0-rc.24 + vite: 8.1.5(esbuild@0.28.1)(yaml@2.9.0) + workerd: 1.20260714.1 + wrangler: 4.112.0 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@cloudflare/workerd-darwin-64@1.20260714.1': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260714.1': + optional: true + + '@cloudflare/workerd-linux-64@1.20260714.1': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260714.1': + optional: true + + '@cloudflare/workerd-windows-64@1.20260714.1': + optional: true + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@hono/node-server@1.19.14(hono@4.12.31)': + dependencies: + hono: 4.12.31 + + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.11.2 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.31) + 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.6.0(express@5.2.1) + hono: 4.12.31 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.2(zod@4.3.6) + optionalDependencies: + '@cfworker/json-schema': 4.1.1 + transitivePeerDependencies: + - supports-color + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/api@1.9.1': {} + + '@oxc-project/types@0.139.0': {} + + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + + '@rolldown/binding-android-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-x64@1.1.5': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.5': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.5': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.5': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.5': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.5': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.5': + optional: true + + '@rolldown/plugin-babel@0.2.3(@babel/core@8.0.1)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))': + dependencies: + '@babel/core': 8.0.1 + picomatch: 4.0.5 + rolldown: 1.1.5 + optionalDependencies: + '@babel/runtime': 7.29.7 + vite: 8.1.5(esbuild@0.28.1)(yaml@2.9.0) + + '@rolldown/pluginutils@1.0.1': {} + + '@sindresorhus/is@7.2.0': {} + + '@speed-highlight/core@1.2.17': {} + + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/gensync@1.0.5': {} + + '@types/jsesc@2.5.1': {} + + '@types/json-schema@7.0.15': {} + + '@vercel/oidc@3.1.0': {} + + '@vercel/oidc@3.2.0': {} + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn@8.17.0: {} + + agents@0.17.4(@ai-sdk/react@3.0.204(react@19.2.4)(zod@4.3.6))(@babel/core@8.0.1)(@babel/runtime@7.29.7)(ai@6.0.37(zod@4.3.6))(react@19.2.4)(rolldown@1.1.5)(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0))(zod@4.3.6): + dependencies: + '@babel/plugin-proposal-decorators': 8.0.2(@babel/core@8.0.1) + '@cfworker/json-schema': 4.1.1 + '@cloudflare/codemode': 0.4.3(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(ai@6.0.37(zod@4.3.6))(zod@4.3.6) + '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + '@rolldown/plugin-babel': 0.2.3(@babel/core@8.0.1)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0)) + cron-schedule: 6.0.0 + esbuild: 0.28.1 + mimetext: 3.0.28 + nanoid: 5.1.16 + partyserver: 0.5.8 + partysocket: 1.3.0(react@19.2.4) + react: 19.2.4 + yaml: 2.9.0 + yargs: 18.0.0 + zod: 4.3.6 + optionalDependencies: + '@ai-sdk/react': 3.0.204(react@19.2.4)(zod@4.3.6) + ai: 6.0.37(zod@4.3.6) + vite: 8.1.5(esbuild@0.28.1)(yaml@2.9.0) + transitivePeerDependencies: + - '@babel/core' + - '@babel/plugin-transform-runtime' + - '@babel/runtime' + - '@cloudflare/workers-types' + - rolldown + - supports-color + + ai@6.0.202(zod@4.3.6): + dependencies: + '@ai-sdk/gateway': 3.0.128(zod@4.3.6) + '@ai-sdk/provider': 3.0.10 + '@ai-sdk/provider-utils': 4.0.28(zod@4.3.6) + '@opentelemetry/api': 1.9.1 + zod: 4.3.6 + + ai@6.0.37(zod@4.3.6): + dependencies: + '@ai-sdk/gateway': 3.0.15(zod@4.3.6) + '@ai-sdk/provider': 3.0.3 + '@ai-sdk/provider-utils': 4.0.7(zod@4.3.6) + '@opentelemetry/api': 1.9.0 + zod: 4.3.6 + + 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.4 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@6.2.2: {} + + ansi-styles@6.2.3: {} + + baseline-browser-mapping@2.10.43: {} + + blake3-wasm@2.1.5: {} + + 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.3 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + browserslist@4.28.6: + dependencies: + baseline-browser-mapping: 2.10.43 + caniuse-lite: 1.0.30001806 + electron-to-chromium: 1.5.393 + node-releases: 2.0.51 + update-browserslist-db: 1.2.3(browserslist@4.28.6) + + 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 + + caniuse-lite@1.0.30001806: {} + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cookie@1.1.1: {} + + core-js-pure@3.49.0: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cron-schedule@6.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + dc-browser@1.0.4: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + depd@2.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + 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: {} + + electron-to-chromium@1.5.393: {} + + emoji-regex@10.6.0: {} + + empathic@2.0.1: {} + + encodeurl@2.0.0: {} + + error-stack-parser-es@1.0.5: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + event-target-polyfill@0.0.4: {} + + eventsource-parser@3.1.0: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.0 + + express-rate-limit@8.6.0(express@5.2.1): + dependencies: + debug: 4.4.3 + express: 5.2.1 + ip-address: 10.2.0 + transitivePeerDependencies: + - supports-color + + 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.3 + range-parser: 1.3.0 + 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.4: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + 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: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.6.0: {} + + 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.31: {} + + 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.3: + dependencies: + safer-buffer: 2.1.2 + + import-meta-resolve@4.2.0: {} + + 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: {} + + js-base64@3.9.1: {} + + js-tokens@10.0.0: {} + + jsesc@3.1.0: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + json-schema@0.4.0: {} + + json5@2.2.3: {} + + kleur@4.1.5: {} + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lru-cache@11.5.2: {} + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimetext@3.0.28: + dependencies: + '@babel/runtime': 7.29.7 + '@babel/runtime-corejs3': 7.29.7 + js-base64: 3.9.1 + mime-types: 2.1.35 + + miniflare@4.20260714.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.28.0 + workerd: 1.20260714.1 + ws: 8.21.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ms@2.1.3: {} + + nanoid@3.3.16: {} + + nanoid@5.1.16: {} + + negotiator@1.0.0: {} + + node-releases@2.0.51: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + obug@2.1.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + partyserver@0.5.8: + dependencies: + nanoid: 5.1.16 + + partysocket@1.3.0(react@19.2.4): + dependencies: + event-target-polyfill: 0.0.4 + optionalDependencies: + react: 19.2.4 + + path-key@3.1.1: {} + + path-to-regexp@6.3.0: {} + + path-to-regexp@8.4.2: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.5: {} + + pkce-challenge@5.0.1: {} + + postcss@8.5.20: + dependencies: + nanoid: 3.3.16 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + react@19.2.4: {} + + require-from-string@2.0.2: {} + + rolldown@1.1.5: + dependencies: + '@oxc-project/types': 0.139.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.5 + '@rolldown/binding-darwin-arm64': 1.1.5 + '@rolldown/binding-darwin-x64': 1.1.5 + '@rolldown/binding-freebsd-x64': 1.1.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 + '@rolldown/binding-linux-arm64-gnu': 1.1.5 + '@rolldown/binding-linux-arm64-musl': 1.1.5 + '@rolldown/binding-linux-ppc64-gnu': 1.1.5 + '@rolldown/binding-linux-s390x-gnu': 1.1.5 + '@rolldown/binding-linux-x64-gnu': 1.1.5 + '@rolldown/binding-linux-x64-musl': 1.1.5 + '@rolldown/binding-openharmony-arm64': 1.1.5 + '@rolldown/binding-wasm32-wasi': 1.1.5 + '@rolldown/binding-win32-arm64-msvc': 1.1.5 + '@rolldown/binding-win32-x64-msvc': 1.1.5 + + 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: {} + + semver@7.8.5: {} + + 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.3.0 + 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: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + + 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 + + source-map-js@1.2.1: {} + + statuses@2.0.2: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + supports-color@10.2.2: {} + + swr@2.4.2(react@19.2.4): + dependencies: + dequal: 2.0.3 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + + throttleit@2.1.0: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + toidentifier@1.0.1: {} + + tslib@2.8.1: + optional: true + + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + + undici@7.28.0: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.6): + dependencies: + browserslist: 4.28.6 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-sync-external-store@1.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + + vary@1.1.2: {} + + vite@8.1.5(esbuild@0.28.1)(yaml@2.9.0): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.20 + rolldown: 1.1.5 + tinyglobby: 0.2.17 + optionalDependencies: + esbuild: 0.28.1 + fsevents: 2.3.3 + yaml: 2.9.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + workerd@1.20260714.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260714.1 + '@cloudflare/workerd-darwin-arm64': 1.20260714.1 + '@cloudflare/workerd-linux-64': 1.20260714.1 + '@cloudflare/workerd-linux-arm64': 1.20260714.1 + '@cloudflare/workerd-windows-64': 1.20260714.1 + + wrangler@4.112.0: + dependencies: + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260714.1) + blake3-wasm: 2.1.5 + esbuild: 0.28.1 + miniflare: 4.20260714.0 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260714.1 + optionalDependencies: + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + ws@8.21.0: {} + + y18n@5.0.8: {} + + yaml@2.9.0: {} + + yargs-parser@22.0.0: {} + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.17 + cookie: 1.1.1 + youch-core: 0.3.3 + + zod-to-json-schema@3.25.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/scenario.mjs b/e2e/scenarios/cloudflare-ai-chat-instrumentation/scenario.mjs new file mode 100644 index 000000000..9befa4610 --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/scenario.mjs @@ -0,0 +1,148 @@ +import { spawn } from "node:child_process"; +import net from "node:net"; + +const scenarioDir = new URL(".", import.meta.url).pathname; +const port = await reservePort(); +const baseUrl = `http://127.0.0.1:${port}`; +const build = await runCommand([ + "exec", + "vite", + "build", + "--config", + "vite.config.ts", +]); +if (build.exitCode !== 0) { + throw new Error(`Vite build failed (${build.exitCode}):\n${build.output}`); +} +const vite = spawn( + "pnpm", + [ + "exec", + "vite", + "preview", + "--config", + "vite.config.ts", + "--host", + "127.0.0.1", + "--port", + String(port), + "--strictPort", + ], + { + cwd: scenarioDir, + detached: process.platform !== "win32", + env: process.env, + stdio: ["ignore", "pipe", "pipe"], + }, +); + +let output = build.output; +vite.stdout.on("data", (chunk) => (output += chunk.toString())); +vite.stderr.on("data", (chunk) => (output += chunk.toString())); + +try { + await waitForServer(); + const success = await run("success"); + if (!JSON.stringify(success).includes("CLOUDFLARE_AI_CHAT_TOOL_OK")) { + throw new Error( + `Successful chat result was incomplete: ${JSON.stringify(success)}`, + ); + } + + const failure = await run("error"); + if (!JSON.stringify(failure).includes("CLOUDFLARE_AI_CHAT_STREAM_ERROR")) { + throw new Error( + `Error chat result was incomplete: ${JSON.stringify(failure)}`, + ); + } +} finally { + await stopVite(); +} + +async function run(kind) { + const response = await fetch(`${baseUrl}/run?kind=${kind}`, { + signal: AbortSignal.timeout(60_000), + }); + const body = await response.text(); + if (!response.ok) { + throw new Error( + `Worker ${kind} request failed (${response.status}): ${body}\n${output}`, + ); + } + return JSON.parse(body); +} + +async function waitForServer() { + for (let attempt = 0; attempt < 80; attempt += 1) { + if (vite.exitCode !== null) { + throw new Error( + `Vite exited before startup (${vite.exitCode}):\n${output}`, + ); + } + try { + const response = await fetch(`${baseUrl}/health`, { + signal: AbortSignal.timeout(1_000), + }); + if (response.ok) { + return; + } + } catch {} + await new Promise((resolve) => setTimeout(resolve, 250)); + } + throw new Error(`Timed out waiting for Vite:\n${output}`); +} + +async function stopVite() { + if (vite.exitCode !== null) { + return; + } + try { + if (process.platform !== "win32" && vite.pid) { + process.kill(-vite.pid, "SIGTERM"); + } else { + vite.kill("SIGTERM"); + } + } catch {} + await Promise.race([ + new Promise((resolve) => vite.once("exit", resolve)), + new Promise((resolve) => setTimeout(resolve, 2_000)), + ]); + if (vite.exitCode === null) { + try { + if (process.platform !== "win32" && vite.pid) { + process.kill(-vite.pid, "SIGKILL"); + } else { + vite.kill("SIGKILL"); + } + } catch {} + } +} + +async function reservePort() { + const server = net.createServer(); + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + const selected = typeof address === "object" && address ? address.port : 0; + await new Promise((resolve) => server.close(resolve)); + return selected; +} + +async function runCommand(args) { + return await new Promise((resolve, reject) => { + const child = spawn("pnpm", args, { + cwd: scenarioDir, + env: process.env, + stdio: ["ignore", "pipe", "pipe"], + }); + let commandOutput = ""; + child.stdout.on("data", (chunk) => (commandOutput += chunk.toString())); + child.stderr.on("data", (chunk) => (commandOutput += chunk.toString())); + child.once("error", reject); + child.once("exit", (code) => + resolve({ exitCode: code ?? 0, output: commandOutput }), + ); + }); +} diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/scenario.test.ts b/e2e/scenarios/cloudflare-ai-chat-instrumentation/scenario.test.ts new file mode 100644 index 000000000..4ad3156d1 --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/scenario.test.ts @@ -0,0 +1,67 @@ +import { describe } from "vitest"; +import { + prepareScenarioDir, + readInstalledPackageVersion, + resolveScenarioDir, +} from "../../helpers/scenario-harness"; +import { defineCloudflareAIChatAssertions } from "./assertions"; + +const originalScenarioDir = resolveScenarioDir(import.meta.url); +const scenarioDir = await prepareScenarioDir({ + scenarioDir: originalScenarioDir, +}); +const TIMEOUT_MS = 180_000; +const scenarios = await Promise.all( + [ + { + dependencyName: "cloudflare-ai-chat-v0", + variantKey: "cloudflare-ai-chat-v0", + }, + { + dependencyName: "cloudflare-ai-chat-v0-latest", + variantKey: "cloudflare-ai-chat-v0-latest", + }, + ].map(async (scenario) => ({ + ...scenario, + version: await readInstalledPackageVersion( + scenarioDir, + scenario.dependencyName, + ), + })), +); + +describe.sequential("Cloudflare AI Chat variants", () => { + for (const scenario of scenarios) { + describe.sequential(`@cloudflare/ai-chat ${scenario.version}`, () => { + for (const mode of ["manual", "auto"] as const) { + defineCloudflareAIChatAssertions({ + name: `${mode} instrumentation`, + runScenario: async ({ runNodeScenarioDir }) => { + await runNodeScenarioDir({ + entry: "scenario.mjs", + env: { + CLOUDFLARE_AI_CHAT_INSTRUMENTATION_MODE: mode, + CLOUDFLARE_AI_CHAT_PACKAGE_NAME: scenario.dependencyName, + }, + runContext: { + cassette: { + variantKey: + mode === "manual" + ? scenario.variantKey + : `${scenario.variantKey}-auto`, + }, + originalScenarioDir, + variantKey: scenario.variantKey, + }, + scenarioDir, + timeoutMs: TIMEOUT_MS, + }); + }, + snapshotName: `${scenario.variantKey}-${mode}`, + testFileUrl: import.meta.url, + timeoutMs: TIMEOUT_MS, + }); + } + }); + } +}); diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/vite.config.ts b/e2e/scenarios/cloudflare-ai-chat-instrumentation/vite.config.ts new file mode 100644 index 000000000..0c08bd723 --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/vite.config.ts @@ -0,0 +1,41 @@ +import { createRequire } from "node:module"; +import { cloudflare } from "@cloudflare/vite-plugin"; +import { braintrustVitePlugin } from "braintrust/vite"; +import { defineConfig } from "vite"; + +const require = createRequire(import.meta.url); +const mode = process.env.CLOUDFLARE_AI_CHAT_INSTRUMENTATION_MODE ?? "manual"; +const packageName = + process.env.CLOUDFLARE_AI_CHAT_PACKAGE_NAME ?? "cloudflare-ai-chat-v0-latest"; + +export default defineConfig({ + cacheDir: `.vite-${packageName}-${mode}`, + define: { + __BRAINTRUST_API_KEY__: JSON.stringify(process.env.BRAINTRUST_API_KEY), + __BRAINTRUST_API_URL__: JSON.stringify(process.env.BRAINTRUST_API_URL), + __BRAINTRUST_APP_URL__: JSON.stringify(process.env.BRAINTRUST_APP_URL), + __BRAINTRUST_PROJECT_NAME__: JSON.stringify( + process.env.BRAINTRUST_E2E_PROJECT_NAME, + ), + __BRAINTRUST_TEST_RUN_ID__: JSON.stringify( + process.env.BRAINTRUST_E2E_RUN_ID, + ), + __CLOUDFLARE_AI_CHAT_MODE__: JSON.stringify(mode), + __OPENAI_API_KEY__: JSON.stringify(process.env.OPENAI_API_KEY), + __OPENAI_BASE_URL__: JSON.stringify(process.env.OPENAI_BASE_URL), + }, + plugins: [ + ...(mode === "auto" + ? [braintrustVitePlugin({ useDiagnosticChannelCompatShim: true })] + : []), + cloudflare({ configPath: "./wrangler.toml" }), + ], + resolve: { + alias: [ + { + find: /^@cloudflare\/ai-chat$/, + replacement: require.resolve(packageName), + }, + ], + }, +}); diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/worker.ts b/e2e/scenarios/cloudflare-ai-chat-instrumentation/worker.ts new file mode 100644 index 000000000..6d9eee970 --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/worker.ts @@ -0,0 +1,184 @@ +import * as aiNamespace from "ai"; +import * as aiChatNamespace from "@cloudflare/ai-chat"; +import { createOpenAI } from "@ai-sdk/openai"; +import { initLogger, wrapAISDK, wrapCloudflareAIChat } from "braintrust"; +import { z } from "zod"; + +declare const __BRAINTRUST_API_KEY__: string; +declare const __BRAINTRUST_API_URL__: string; +declare const __BRAINTRUST_APP_URL__: string; +declare const __BRAINTRUST_PROJECT_NAME__: string; +declare const __BRAINTRUST_TEST_RUN_ID__: string; +declare const __CLOUDFLARE_AI_CHAT_MODE__: "auto" | "manual"; +declare const __OPENAI_API_KEY__: string; +declare const __OPENAI_BASE_URL__: string; + +const SCENARIO_NAME = "cloudflare-ai-chat-instrumentation"; +const SUCCESS_ROOT_NAME = "cloudflare-ai-chat-success-root"; +const ERROR_ROOT_NAME = "cloudflare-ai-chat-error-root"; +const ERROR_MARKER = "CLOUDFLARE_AI_CHAT_STREAM_ERROR"; +const SUCCESS_MARKER = "CLOUDFLARE_AI_CHAT_TOOL_OK"; +const MODEL = "gpt-4.1-nano"; + +Object.assign(process.env, { + BRAINTRUST_API_KEY: __BRAINTRUST_API_KEY__, + BRAINTRUST_API_URL: __BRAINTRUST_API_URL__, + BRAINTRUST_APP_URL: __BRAINTRUST_APP_URL__, + BRAINTRUST_E2E_PROJECT_NAME: __BRAINTRUST_PROJECT_NAME__, + BRAINTRUST_E2E_RUN_ID: __BRAINTRUST_TEST_RUN_ID__, +}); + +const ai = + __CLOUDFLARE_AI_CHAT_MODE__ === "manual" + ? wrapAISDK(aiNamespace) + : aiNamespace; +const aiChat = + __CLOUDFLARE_AI_CHAT_MODE__ === "manual" + ? wrapCloudflareAIChat(aiChatNamespace) + : aiChatNamespace; + +export class ChatAgent extends aiChat.AIChatAgent { + private scenarioKind: "error" | "success" = "success"; + + async onChatMessage( + _onFinish: unknown, + _options?: { body?: { kind?: string } }, + ): Promise { + if (this.scenarioKind === "error") { + const stream = ai.createUIMessageStream({ + execute({ writer }) { + writer.write({ type: "text-start", id: "error-text" }); + writer.write({ + type: "text-delta", + id: "error-text", + delta: "partial response", + }); + throw new Error(ERROR_MARKER); + }, + generateId: () => "assistant-error", + onError: () => ERROR_MARKER, + originalMessages: this.messages, + }); + return ai.createUIMessageStreamResponse({ stream }); + } + + const openai = createOpenAI({ + apiKey: __OPENAI_API_KEY__, + baseURL: __OPENAI_BASE_URL__, + }); + const result = await ai.generateText({ + maxOutputTokens: 32, + messages: await ai.convertToModelMessages(this.messages), + model: openai(MODEL), + stopWhen: ai.stepCountIs(2), + system: `Call lookup_weather exactly once with city Vienna. After the tool returns, reply with exactly ${SUCCESS_MARKER} and no other text.`, + temperature: 0, + tools: { + lookup_weather: ai.tool({ + description: "Return deterministic weather for one city.", + execute: async ({ city }) => ({ + city, + condition: "sunny", + marker: "WEATHER_TOOL_EXECUTED", + }), + inputSchema: z.object({ city: z.string() }), + }), + }, + }); + const stream = ai.createUIMessageStream({ + execute({ writer }) { + writer.write({ type: "text-start", id: "success-text" }); + writer.write({ + type: "text-delta", + id: "success-text", + delta: result.text, + }); + writer.write({ type: "text-end", id: "success-text" }); + }, + generateId: () => "assistant-success", + originalMessages: this.messages, + }); + return ai.createUIMessageStreamResponse({ stream }); + } + + onChatResponse(_result: unknown): void {} + + async runInstrumentationScenario(kind: "error" | "success") { + const logger = initLogger({ projectName: __BRAINTRUST_PROJECT_NAME__ }); + let result: Record; + try { + this.scenarioKind = kind; + let turnResult: + | { error?: unknown; requestId: string; status: string } + | undefined; + await logger.traced( + async () => { + turnResult = await this.saveMessages([ + { + id: `user-${kind}`, + parts: [ + { + text: + kind === "success" + ? "Look up the weather in Vienna." + : "Produce a deterministic streaming error.", + type: "text", + }, + ], + role: "user", + }, + ]); + }, + { + event: { + metadata: { + mode: __CLOUDFLARE_AI_CHAT_MODE__, + scenario: SCENARIO_NAME, + testRunId: __BRAINTRUST_TEST_RUN_ID__, + }, + }, + name: kind === "success" ? SUCCESS_ROOT_NAME : ERROR_ROOT_NAME, + }, + ); + result = { + error: + turnResult?.error instanceof Error + ? turnResult.error.message + : turnResult?.error, + kind, + messages: this.messages, + ok: turnResult?.status === "completed", + status: turnResult?.status, + }; + } catch (error) { + result = { + error: error instanceof Error ? error.message : String(error), + kind, + ok: false, + }; + } finally { + await logger.flush(); + } + return result; + } +} + +export default { + async fetch(request: Request, env: { CHAT_AGENT: any }): Promise { + const url = new URL(request.url); + if (url.pathname === "/health") { + return new Response("ok"); + } + if (url.pathname !== "/run") { + return new Response("not found", { status: 404 }); + } + + const kind = url.searchParams.get("kind") === "error" ? "error" : "success"; + const id = env.CHAT_AGENT.idFromName( + `${__CLOUDFLARE_AI_CHAT_MODE__}-${kind}-${__BRAINTRUST_TEST_RUN_ID__}`, + ); + const result = + await env.CHAT_AGENT.get(id).runInstrumentationScenario(kind); + return Response.json(result); + }, +}; diff --git a/e2e/scenarios/cloudflare-ai-chat-instrumentation/wrangler.toml b/e2e/scenarios/cloudflare-ai-chat-instrumentation/wrangler.toml new file mode 100644 index 000000000..5254fc9ed --- /dev/null +++ b/e2e/scenarios/cloudflare-ai-chat-instrumentation/wrangler.toml @@ -0,0 +1,12 @@ +name = "cloudflare-ai-chat-instrumentation" +main = "worker.ts" +compatibility_date = "2026-06-01" +compatibility_flags = ["nodejs_compat"] + +[[durable_objects.bindings]] +name = "CHAT_AGENT" +class_name = "ChatAgent" + +[[migrations]] +tag = "v1" +new_sqlite_classes = ["ChatAgent"] diff --git a/js/src/auto-instrumentations/bundler/plugin.test.ts b/js/src/auto-instrumentations/bundler/plugin.test.ts new file mode 100644 index 000000000..695a243cd --- /dev/null +++ b/js/src/auto-instrumentations/bundler/plugin.test.ts @@ -0,0 +1,40 @@ +import { createRequire } from "node:module"; +import { describe, expect, it } from "vitest"; +import { unplugin } from "./plugin"; + +const require = createRequire(import.meta.url); + +describe("bundler instrumentation plugin", () => { + it("matches dependency paths with Vite cache queries", async () => { + const rawPlugin = unplugin.raw( + { + instrumentations: [ + { + channelName: "querySuffixProbe", + functionQuery: { + functionName: "querySuffixProbe", + kind: "Async", + }, + module: { + filePath: "dist/index.js", + name: "ai", + versionRange: ">=6 <7", + }, + }, + ], + }, + { framework: "vite" }, + ); + const plugin = Array.isArray(rawPlugin) ? rawPlugin[0] : rawPlugin; + const transform = plugin.transform as ( + code: string, + id: string, + ) => Promise<{ code: string } | null> | { code: string } | null; + const result = await transform( + "export async function querySuffixProbe() { return 1; }", + `${require.resolve("ai")}?v=01234567`, + ); + + expect(result?.code).toContain("orchestrion:ai:querySuffixProbe"); + }); +}); diff --git a/js/src/auto-instrumentations/bundler/plugin.ts b/js/src/auto-instrumentations/bundler/plugin.ts index dda1b7490..a1b3ca6ee 100644 --- a/js/src/auto-instrumentations/bundler/plugin.ts +++ b/js/src/auto-instrumentations/bundler/plugin.ts @@ -91,9 +91,16 @@ export const unplugin = createUnplugin( return null; } + // Vite and Rollup may append cache-busting queries or fragments to + // real filesystem module IDs. They are not part of the package path + // and prevent exact instrumentation file paths from matching. + const cleanId = id.replace(/[?#].*$/, ""); + // Convert file:// URLs to regular paths at entry point // Node.js ESM loader hooks provide file:// URLs, but downstream code expects paths - const filePath = id.startsWith("file:") ? fileURLToPath(id) : id; + const filePath = cleanId.startsWith("file:") + ? fileURLToPath(cleanId) + : cleanId; // Determine if this is an ES module using multiple methods for accurate detection const ext = extname(filePath); diff --git a/js/src/auto-instrumentations/configs/all.ts b/js/src/auto-instrumentations/configs/all.ts index b2d778960..e582b4eaf 100644 --- a/js/src/auto-instrumentations/configs/all.ts +++ b/js/src/auto-instrumentations/configs/all.ts @@ -8,6 +8,7 @@ import { aiSDKConfigs } from "./ai-sdk"; import { anthropicConfigs } from "./anthropic"; import { bedrockRuntimeConfigs } from "./bedrock-runtime"; import { claudeAgentSDKConfigs } from "./claude-agent-sdk"; +import { cloudflareAIChatConfigs } from "./cloudflare-ai-chat"; import { cohereConfigs } from "./cohere"; import { cursorSDKConfigs } from "./cursor-sdk"; import { flueConfigs } from "./flue"; @@ -53,6 +54,10 @@ const defaultInstrumentationConfigGroups: readonly InstrumentationConfigGroup[] integrations: ["claudeAgentSDK"], configs: claudeAgentSDKConfigs, }, + { + integrations: ["cloudflareAIChat"], + configs: cloudflareAIChatConfigs, + }, { integrations: ["cursor", "cursorSDK"], configs: cursorSDKConfigs }, { integrations: ["openAIAgents"], diff --git a/js/src/auto-instrumentations/configs/cloudflare-ai-chat.ts b/js/src/auto-instrumentations/configs/cloudflare-ai-chat.ts new file mode 100644 index 000000000..02f081612 --- /dev/null +++ b/js/src/auto-instrumentations/configs/cloudflare-ai-chat.ts @@ -0,0 +1,24 @@ +import type { InstrumentationConfig } from "../orchestrion-js"; +import { cloudflareAIChatChannels } from "../../instrumentation/plugins/cloudflare-ai-chat-channels"; + +const cloudflareAIChatVersionRange = ">=0.9.0 <0.10.0"; + +export const cloudflareAIChatConfigs: InstrumentationConfig[] = [ + { + // AIChatAgent subclasses replace onChatMessage, so there is no stable + // public implementation for the transformer to target. This runner is + // the narrowest shared boundary around every chat turn; keep the version + // range tight because it is an internal method. + channelName: cloudflareAIChatChannels.runExclusiveChatTurn.channelName, + module: { + name: "@cloudflare/ai-chat", + versionRange: cloudflareAIChatVersionRange, + filePath: "dist/index.js", + }, + functionQuery: { + className: "AIChatAgent", + methodName: "_runExclusiveChatTurn", + kind: "Async", + }, + }, +]; diff --git a/js/src/auto-instrumentations/index.ts b/js/src/auto-instrumentations/index.ts index ea0533ac6..9e4601cab 100644 --- a/js/src/auto-instrumentations/index.ts +++ b/js/src/auto-instrumentations/index.ts @@ -42,6 +42,7 @@ export { openRouterAgentConfigs } from "./configs/openrouter-agent"; export { openRouterConfigs } from "./configs/openrouter"; export { mistralConfigs } from "./configs/mistral"; export { googleADKConfigs } from "./configs/google-adk"; +export { cloudflareAIChatConfigs } from "./configs/cloudflare-ai-chat"; export { cohereConfigs } from "./configs/cohere"; export { groqConfigs } from "./configs/groq"; export { genkitConfigs } from "./configs/genkit"; diff --git a/js/src/auto-instrumentations/orchestrion-js/transforms.ts b/js/src/auto-instrumentations/orchestrion-js/transforms.ts index 92a1adfaa..365f6a704 100644 --- a/js/src/auto-instrumentations/orchestrion-js/transforms.ts +++ b/js/src/auto-instrumentations/orchestrion-js/transforms.ts @@ -166,7 +166,7 @@ function traceInstanceMethod( const classBody = node.body; - if (classBody.body.some(({ key }: AnyNode) => key.name === methodName)) { + if (classBody.body.some(({ key }: AnyNode) => key?.name === methodName)) { return; } diff --git a/js/src/exports.ts b/js/src/exports.ts index 36a38946a..c8af2a7bc 100644 --- a/js/src/exports.ts +++ b/js/src/exports.ts @@ -211,6 +211,7 @@ 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 { wrapCloudflareAIChat } from "./wrappers/cloudflare-ai-chat"; 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 fdce9dc99..dafcb6eb4 100644 --- a/js/src/instrumentation/braintrust-plugin.test.ts +++ b/js/src/instrumentation/braintrust-plugin.test.ts @@ -18,6 +18,7 @@ import { LangChainPlugin } from "./plugins/langchain-plugin"; import { LangSmithPlugin } from "./plugins/langsmith-plugin"; import { PiCodingAgentPlugin } from "./plugins/pi-coding-agent-plugin"; import { StrandsAgentSDKPlugin } from "./plugins/strands-agent-sdk-plugin"; +import { CloudflareAIChatPlugin } from "./plugins/cloudflare-ai-chat-plugin"; function createPluginClassMock() { return vi.fn(function MockPlugin(this: { @@ -111,6 +112,10 @@ vi.mock("./plugins/strands-agent-sdk-plugin", () => ({ StrandsAgentSDKPlugin: createPluginClassMock(), })); +vi.mock("./plugins/cloudflare-ai-chat-plugin", () => ({ + CloudflareAIChatPlugin: createPluginClassMock(), +})); + describe("BraintrustPlugin", () => { beforeEach(() => { vi.clearAllMocks(); @@ -265,6 +270,16 @@ describe("BraintrustPlugin", () => { expect(mockInstance.enable).toHaveBeenCalledTimes(1); }); + it("should create and enable Cloudflare AI Chat plugin by default", () => { + const plugin = new BraintrustPlugin(); + plugin.enable(); + + expect(CloudflareAIChatPlugin).toHaveBeenCalledTimes(1); + const mockInstance = vi.mocked(CloudflareAIChatPlugin).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(); @@ -615,6 +630,7 @@ describe("BraintrustPlugin", () => { langsmith: false, piCodingAgent: false, strandsAgentSDK: false, + cloudflareAIChat: false, }, }); plugin.enable(); @@ -637,6 +653,7 @@ describe("BraintrustPlugin", () => { expect(LangSmithPlugin).not.toHaveBeenCalled(); expect(PiCodingAgentPlugin).not.toHaveBeenCalled(); expect(StrandsAgentSDKPlugin).not.toHaveBeenCalled(); + expect(CloudflareAIChatPlugin).not.toHaveBeenCalled(); }); it("should not create Pi Coding Agent plugin when piCodingAgent: false", () => { @@ -660,6 +677,16 @@ describe("BraintrustPlugin", () => { expect(OpenAIPlugin).toHaveBeenCalledTimes(1); }); + it("should not create Cloudflare AI Chat plugin when cloudflareAIChat: false", () => { + const plugin = new BraintrustPlugin({ + integrations: { cloudflareAIChat: false }, + }); + plugin.enable(); + + expect(CloudflareAIChatPlugin).not.toHaveBeenCalled(); + expect(OpenAIPlugin).toHaveBeenCalledTimes(1); + }); + it("should allow selective enabling of plugins", () => { const plugin = new BraintrustPlugin({ integrations: { diff --git a/js/src/instrumentation/braintrust-plugin.ts b/js/src/instrumentation/braintrust-plugin.ts index d5435f2b0..6d23523d9 100644 --- a/js/src/instrumentation/braintrust-plugin.ts +++ b/js/src/instrumentation/braintrust-plugin.ts @@ -22,6 +22,7 @@ import { LangChainPlugin } from "./plugins/langchain-plugin"; import { LangSmithPlugin } from "./plugins/langsmith-plugin"; import { PiCodingAgentPlugin } from "./plugins/pi-coding-agent-plugin"; import { StrandsAgentSDKPlugin } from "./plugins/strands-agent-sdk-plugin"; +import { CloudflareAIChatPlugin } from "./plugins/cloudflare-ai-chat-plugin"; import type { InstrumentationIntegrationsConfig } from "./config"; export interface BraintrustPluginConfig { @@ -70,6 +71,7 @@ export class BraintrustPlugin extends BasePlugin { private langSmithPlugin: LangSmithPlugin | null = null; private piCodingAgentPlugin: PiCodingAgentPlugin | null = null; private strandsAgentSDKPlugin: StrandsAgentSDKPlugin | null = null; + private cloudflareAIChatPlugin: CloudflareAIChatPlugin | null = null; constructor(config: BraintrustPluginConfig = {}) { super(); @@ -192,6 +194,11 @@ export class BraintrustPlugin extends BasePlugin { this.strandsAgentSDKPlugin.enable(); } + if (integrations.cloudflareAIChat !== false) { + this.cloudflareAIChatPlugin = new CloudflareAIChatPlugin(); + this.cloudflareAIChatPlugin.enable(); + } + if (integrations.flue !== false) { this.fluePlugin = new FluePlugin(); this.fluePlugin.enable(); @@ -317,6 +324,11 @@ export class BraintrustPlugin extends BasePlugin { this.strandsAgentSDKPlugin = null; } + if (this.cloudflareAIChatPlugin) { + this.cloudflareAIChatPlugin.disable(); + this.cloudflareAIChatPlugin = 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 8a2d43373..dc246eec7 100644 --- a/js/src/instrumentation/config.ts +++ b/js/src/instrumentation/config.ts @@ -8,6 +8,7 @@ export interface InstrumentationIntegrationsConfig { googleADK?: boolean; huggingface?: boolean; claudeAgentSDK?: boolean; + cloudflareAIChat?: boolean; cursor?: boolean; cursorSDK?: boolean; flue?: boolean; @@ -65,6 +66,9 @@ const envIntegrationAliases: Record< vercel: "vercel", claudeagentsdk: "claudeAgentSDK", "claude-agent-sdk": "claudeAgentSDK", + cloudflareaichat: "cloudflareAIChat", + "cloudflare-ai-chat": "cloudflareAIChat", + "@cloudflare/ai-chat": "cloudflareAIChat", cursor: "cursor", "cursor-sdk": "cursorSDK", cursorsdk: "cursorSDK", @@ -121,6 +125,7 @@ export function getDefaultInstrumentationIntegrations(): Record< googleADK: true, huggingface: true, claudeAgentSDK: true, + cloudflareAIChat: true, cursor: true, cursorSDK: true, flue: true, diff --git a/js/src/instrumentation/plugins/cloudflare-ai-chat-channels.ts b/js/src/instrumentation/plugins/cloudflare-ai-chat-channels.ts new file mode 100644 index 000000000..7e31a7f6f --- /dev/null +++ b/js/src/instrumentation/plugins/cloudflare-ai-chat-channels.ts @@ -0,0 +1,30 @@ +import { channel, defineChannels } from "../core/channel-definitions"; +import type { + CloudflareAIChatResponseResult, + CloudflareAIChatTurnCallback, + CloudflareAIChatTurnOptions, +} from "../../vendor-sdk-types/cloudflare-ai-chat"; + +type CloudflareAIChatChannelContext = { + self?: unknown; +}; + +export const cloudflareAIChatChannels = defineChannels("@cloudflare/ai-chat", { + runExclusiveChatTurn: channel< + [string, CloudflareAIChatTurnCallback, CloudflareAIChatTurnOptions?], + unknown, + CloudflareAIChatChannelContext + >({ + channelName: "AIChatAgent._runExclusiveChatTurn", + kind: "async", + }), + + onChatResponse: channel< + [CloudflareAIChatResponseResult], + unknown, + CloudflareAIChatChannelContext + >({ + channelName: "AIChatAgent.onChatResponse", + kind: "sync-stream", + }), +}); diff --git a/js/src/instrumentation/plugins/cloudflare-ai-chat-instrumentation.ts b/js/src/instrumentation/plugins/cloudflare-ai-chat-instrumentation.ts new file mode 100644 index 000000000..0a109f3e6 --- /dev/null +++ b/js/src/instrumentation/plugins/cloudflare-ai-chat-instrumentation.ts @@ -0,0 +1,101 @@ +import { debugLogger } from "../../debug-logger"; +import type { + CloudflareAIChatAgent, + CloudflareAIChatResponseResult, +} from "../../vendor-sdk-types/cloudflare-ai-chat"; +import { cloudflareAIChatChannels } from "./cloudflare-ai-chat-channels"; + +const wrappedTurnRunners = new WeakSet(); +const wrappedResponseHooks = new WeakSet(); + +export function instrumentCloudflareAIChatAgent( + agent: CloudflareAIChatAgent, +): CloudflareAIChatAgent { + try { + const original = Reflect.get(agent, "_runExclusiveChatTurn"); + if (typeof original !== "function" || wrappedTurnRunners.has(original)) { + return agent; + } + + const wrapped = async function ( + this: CloudflareAIChatAgent, + ...args: Parameters + ): Promise { + return cloudflareAIChatChannels.runExclusiveChatTurn.tracePromise( + () => Reflect.apply(original, this, args), + { + arguments: args, + self: this, + }, + ); + }; + wrappedTurnRunners.add(wrapped); + + defineWrappedMethod(agent, "_runExclusiveChatTurn", original, wrapped); + } catch (error) { + debugLogger.debug("Failed to wrap @cloudflare/ai-chat turn runner:", error); + } + return agent; +} + +export function instrumentCloudflareAIChatResponseHook( + agent: CloudflareAIChatAgent, +): void { + try { + const original = Reflect.get(agent, "onChatResponse"); + if (typeof original !== "function" || wrappedResponseHooks.has(original)) { + return; + } + + const wrapped = function ( + this: CloudflareAIChatAgent, + result: CloudflareAIChatResponseResult, + ): unknown { + const args: [CloudflareAIChatResponseResult] = [result]; + return cloudflareAIChatChannels.onChatResponse.traceSync( + () => Reflect.apply(original, this, args), + { + arguments: args, + self: this, + }, + ); + }; + wrappedResponseHooks.add(wrapped); + + defineWrappedMethod(agent, "onChatResponse", original, wrapped); + } catch (error) { + debugLogger.debug( + "Failed to wrap @cloudflare/ai-chat response hook:", + error, + ); + } +} + +function defineWrappedMethod( + target: object, + property: string, + original: CallableFunction, + wrapped: CallableFunction, +): void { + const ownDescriptor = Object.getOwnPropertyDescriptor(target, property); + Object.defineProperty(target, property, { + configurable: ownDescriptor?.configurable ?? true, + enumerable: ownDescriptor?.enumerable ?? false, + value: wrapped, + writable: + ownDescriptor && "writable" in ownDescriptor + ? ownDescriptor.writable + : true, + }); + + // Preserve the original function's name where the runtime permits it. This + // is observability-only and must never make wrapping fail. + try { + Object.defineProperty(wrapped, "name", { + configurable: true, + value: original.name, + }); + } catch { + // Ignore non-configurable function metadata. + } +} diff --git a/js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.test.ts b/js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.test.ts new file mode 100644 index 000000000..46e2d5fa9 --- /dev/null +++ b/js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.test.ts @@ -0,0 +1,253 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const { + mockInternalGetGlobalState, + mockStartSpan, + mockWithCurrent, + mockCurrentSpanStoreSymbol, +} = vi.hoisted(() => ({ + mockCurrentSpanStoreSymbol: Symbol.for("braintrust.currentSpanStore"), + mockInternalGetGlobalState: vi.fn(() => undefined), + mockStartSpan: vi.fn(), + mockWithCurrent: vi.fn((_span: unknown, callback: () => unknown) => + callback(), + ), +})); + +vi.mock("../../isomorph", () => ({ + default: { newTracingChannel: vi.fn() }, +})); + +vi.mock("../../logger", () => ({ + BRAINTRUST_CURRENT_SPAN_STORE: mockCurrentSpanStoreSymbol, + _internalGetGlobalState: () => mockInternalGetGlobalState(), + startSpan: (...args: unknown[]) => (mockStartSpan as any)(...args), + withCurrent: (...args: unknown[]) => (mockWithCurrent as any)(...args), +})); + +import iso from "../../isomorph"; +import { CloudflareAIChatPlugin } from "./cloudflare-ai-chat-plugin"; + +const mockNewTracingChannel = iso.newTracingChannel as ReturnType; + +describe("CloudflareAIChatPlugin", () => { + let plugin: CloudflareAIChatPlugin; + let channels: Map>; + + beforeEach(() => { + channels = new Map(); + mockNewTracingChannel.mockImplementation((name: string) => { + const existing = channels.get(name); + if (existing) { + return existing; + } + const channel = createMockChannel(); + channels.set(name, channel); + return channel; + }); + mockStartSpan.mockImplementation(() => ({ + end: vi.fn(), + log: vi.fn(), + })); + mockInternalGetGlobalState.mockReturnValue(undefined); + plugin = new CloudflareAIChatPlugin(); + }); + + afterEach(() => { + plugin.disable(); + vi.clearAllMocks(); + }); + + it("captures the full successful turn and binds queued work", async () => { + plugin.enable(); + const turnHandlers = turnChannel().handlers(); + const callback = vi.fn(async () => "callback-result"); + const agent = { + messages: [ + { + id: "user-1", + metadata: { ignored: true }, + parts: [{ text: "hello", type: "text" }], + role: "user", + }, + ], + onChatResponse: vi.fn(), + }; + const event = { + arguments: ["request-1", callback, undefined], + self: agent, + } as any; + + turnHandlers.start?.(event, "start"); + await event.arguments[1](); + agent.onChatResponse({ + message: { + id: "assistant-1", + metadata: { ignored: true }, + parts: [{ text: "world", type: "text" }], + role: "assistant", + }, + requestId: "request-1", + status: "completed", + }); + turnHandlers.asyncEnd?.(event, "asyncEnd"); + + const span = mockStartSpan.mock.results[0].value; + expect(mockStartSpan).toHaveBeenCalledWith({ + name: "AIChatAgent.onChatMessage", + spanAttributes: { type: "task" }, + }); + expect(span.log).toHaveBeenCalledWith({ + input: [ + { + id: "user-1", + parts: [{ text: "hello", type: "text" }], + role: "user", + }, + ], + }); + expect(span.log).toHaveBeenCalledWith({ + input: [ + { + id: "user-1", + parts: [{ text: "hello", type: "text" }], + role: "user", + }, + ], + output: { + id: "assistant-1", + parts: [{ text: "world", type: "text" }], + role: "assistant", + }, + }); + expect(mockWithCurrent).toHaveBeenCalledWith(span, expect.any(Function)); + expect(callback).toHaveBeenCalledTimes(1); + expect(span.end).toHaveBeenCalledTimes(1); + }); + + it("correlates response errors and preserves partial output", () => { + plugin.enable(); + const turnHandlers = turnChannel().handlers(); + const responseHandlers = responseChannel().handlers(); + const agent = { messages: [], onChatResponse() {} }; + const event = { + arguments: ["request-error", async () => undefined, undefined], + self: agent, + } as any; + turnHandlers.start?.(event, "start"); + + responseHandlers.start?.( + { + arguments: [ + { + error: "stream failed", + message: { parts: [{ text: "partial" }], role: "assistant" }, + requestId: "request-error", + status: "error", + }, + ], + self: agent, + } as any, + "start", + ); + turnHandlers.asyncEnd?.(event, "asyncEnd"); + + const span = mockStartSpan.mock.results[0].value; + expect(span.log).toHaveBeenCalledWith({ + error: "stream failed", + input: [], + output: { parts: [{ text: "partial" }], role: "assistant" }, + }); + expect(span.end).toHaveBeenCalledTimes(1); + }); + + it("deduplicates nested manual and automatic turn events", () => { + plugin.enable(); + const handlers = turnChannel().handlers(); + const agent = { messages: [], onChatResponse() {} }; + const outer = { + arguments: ["request-1", async () => undefined, undefined], + self: agent, + } as any; + const inner = { + arguments: ["request-1", async () => undefined, undefined], + self: agent, + } as any; + + handlers.start?.(outer, "start"); + handlers.start?.(inner, "start"); + handlers.asyncEnd?.(inner, "asyncEnd"); + + const span = mockStartSpan.mock.results[0].value; + expect(mockStartSpan).toHaveBeenCalledTimes(1); + expect(span.end).not.toHaveBeenCalled(); + + handlers.asyncEnd?.(outer, "asyncEnd"); + expect(span.end).toHaveBeenCalledTimes(1); + }); + + it("logs original errors and closes outstanding spans on disable", () => { + plugin.enable(); + const handlers = turnChannel().handlers(); + const failure = new Error("turn failed"); + const failedEvent = { + arguments: ["request-1", async () => undefined, undefined], + self: { messages: [], onChatResponse() {} }, + } as any; + handlers.start?.(failedEvent, "start"); + failedEvent.error = failure; + handlers.error?.(failedEvent, "error"); + + const failedSpan = mockStartSpan.mock.results[0].value; + expect(failedSpan.log).toHaveBeenCalledWith({ error: failure }); + expect(failedSpan.end).toHaveBeenCalledTimes(1); + + const pendingEvent = { + arguments: ["request-2", async () => undefined, undefined], + self: { messages: [], onChatResponse() {} }, + } as any; + handlers.start?.(pendingEvent, "start"); + const pendingSpan = mockStartSpan.mock.results[1].value; + plugin.disable(); + expect(pendingSpan.end).toHaveBeenCalledTimes(1); + }); + + function turnChannel() { + return channels.get( + "orchestrion:@cloudflare/ai-chat:AIChatAgent._runExclusiveChatTurn", + )!; + } + + function responseChannel() { + return channels.get( + "orchestrion:@cloudflare/ai-chat:AIChatAgent.onChatResponse", + )!; + } +}); + +function createMockChannel() { + const subscribed: any[] = []; + return { + handlers: () => subscribed[0], + hasSubscribers: false, + start: { + bindStore: vi.fn(), + unbindStore: vi.fn(), + }, + subscribe: vi.fn((handlers) => subscribed.push(handlers)), + traceSync: vi.fn((callback, event) => { + subscribed[0]?.start?.(event, "start"); + try { + const result = callback(); + event.result = result; + subscribed[0]?.end?.(event, "end"); + return result; + } catch (error) { + event.error = error; + subscribed[0]?.error?.(event, "error"); + throw error; + } + }), + unsubscribe: vi.fn(), + }; +} diff --git a/js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.ts b/js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.ts new file mode 100644 index 000000000..b0b19e538 --- /dev/null +++ b/js/src/instrumentation/plugins/cloudflare-ai-chat-plugin.ts @@ -0,0 +1,432 @@ +import { BasePlugin } from "../core"; +import type { ChannelMessage } from "../core/channel-definitions"; +import type { IsoChannelHandlers, IsoTracingChannel } from "../../isomorph"; +import { + BRAINTRUST_CURRENT_SPAN_STORE, + _internalGetGlobalState, + startSpan, + withCurrent, +} from "../../logger"; +import type { CurrentSpanStore, Span } from "../../logger"; +import { debugLogger } from "../../debug-logger"; +import { SpanTypeAttribute } from "../../../util/index"; +import type { + CloudflareAIChatAgent, + CloudflareAIChatMessage, + CloudflareAIChatTurnCallback, +} from "../../vendor-sdk-types/cloudflare-ai-chat"; +import { cloudflareAIChatChannels } from "./cloudflare-ai-chat-channels"; +import { instrumentCloudflareAIChatResponseHook } from "./cloudflare-ai-chat-instrumentation"; + +type TurnChannel = typeof cloudflareAIChatChannels.runExclusiveChatTurn; +type ResponseChannel = typeof cloudflareAIChatChannels.onChatResponse; + +type TurnState = { + agent?: object; + depth: number; + ended: boolean; + key: string | symbol; + pendingError?: unknown; + span: Span; +}; + +const BLOCKED_KEYS = new Set(["__proto__", "constructor", "prototype"]); + +export class CloudflareAIChatPlugin extends BasePlugin { + private readonly activeStates = new Set(); + private readonly activeTurns = new WeakMap< + object, + Map + >(); + private readonly eventStates = new WeakMap(); + + protected onEnable(): void { + this.subscribeToResponseHook(); + this.subscribeToTurnRunner(); + } + + protected onDisable(): void { + for (const unsubscribe of this.unsubscribers) { + unsubscribe(); + } + this.unsubscribers = []; + + for (const state of this.activeStates) { + this.finalizeState(state); + } + this.activeStates.clear(); + } + + private subscribeToTurnRunner(): void { + const tracingChannel = + cloudflareAIChatChannels.runExclusiveChatTurn.tracingChannel() as IsoTracingChannel< + ChannelMessage + >; + + const unbindCurrentSpanStore = this.bindCurrentSpanStore(tracingChannel); + const handlers: IsoChannelHandlers> = { + start: (event) => { + this.ensureEventState(event); + }, + asyncEnd: (event) => { + this.finishEvent(event); + }, + error: (event) => { + this.finishEvent(event, event.error); + }, + }; + + tracingChannel.subscribe(handlers); + this.unsubscribers.push(() => { + unbindCurrentSpanStore?.(); + tracingChannel.unsubscribe(handlers); + }); + } + + private subscribeToResponseHook(): void { + const tracingChannel = + cloudflareAIChatChannels.onChatResponse.tracingChannel() as IsoTracingChannel< + ChannelMessage + >; + const handlers: IsoChannelHandlers> = { + start: (event) => { + try { + const agent = asObject(event.self); + const result = event.arguments[0]; + const state = agent + ? this.findResponseState( + agent, + stringValue(ownValue(result, "requestId")), + ) + : undefined; + if (!state || state.ended) { + return; + } + + const output = serializeMessage(ownValue(result, "message")); + const status = stringValue(ownValue(result, "status")); + const error = ownValue(result, "error"); + const outputId = output?.id; + const input = serializeMessages( + readProperty(agent, "messages"), + )?.filter( + (message) => + typeof outputId !== "string" || message.id !== outputId, + ); + state.span.log({ + ...(input !== undefined ? { input } : {}), + ...(output !== undefined ? { output } : {}), + ...(status === "error" && error !== undefined ? { error } : {}), + }); + } catch (error) { + debugLogger.debug( + "Failed to process @cloudflare/ai-chat response hook:", + error, + ); + } + }, + }; + + tracingChannel.subscribe(handlers); + this.unsubscribers.push(() => tracingChannel.unsubscribe(handlers)); + } + + private bindCurrentSpanStore( + tracingChannel: IsoTracingChannel>, + ): (() => void) | undefined { + const globalState = _internalGetGlobalState(); + const contextManager = globalState?.contextManager; + const startChannel = tracingChannel.start; + const currentSpanStore = contextManager + ? ( + contextManager as { + [BRAINTRUST_CURRENT_SPAN_STORE]?: CurrentSpanStore; + } + )[BRAINTRUST_CURRENT_SPAN_STORE] + : undefined; + + if (!startChannel || !currentSpanStore || !contextManager) { + return undefined; + } + + startChannel.bindStore(currentSpanStore, (event) => { + const state = this.ensureEventState(event); + return state + ? contextManager.wrapSpanForStore(state.span) + : currentSpanStore.getStore(); + }); + + return () => startChannel.unbindStore(currentSpanStore); + } + + private ensureEventState( + event: ChannelMessage, + ): TurnState | undefined { + const eventKey = event as object; + const known = this.eventStates.get(eventKey); + if (known) { + return known; + } + + try { + const agent = asObject(event.self); + const requestId = stringValue(event.arguments[0]); + const key = requestId ?? Symbol("cloudflare-ai-chat-turn"); + const activeForAgent = agent ? this.getActiveTurns(agent) : undefined; + const existing = activeForAgent?.get(key); + const state = + existing && !existing.ended + ? existing + : this.createTurnState(agent, key); + + if (existing && !existing.ended) { + state.depth += 1; + } + this.eventStates.set(eventKey, state); + this.bindTurnCallback(event, agent, state.span); + + if (agent) { + instrumentCloudflareAIChatResponseHook(agent as CloudflareAIChatAgent); + } + return state; + } catch (error) { + debugLogger.debug( + "Failed to start @cloudflare/ai-chat instrumentation:", + error, + ); + return undefined; + } + } + + private createTurnState( + agent: object | undefined, + key: string | symbol, + ): TurnState { + const span = startSpan({ + name: "AIChatAgent.onChatMessage", + spanAttributes: { type: SpanTypeAttribute.TASK }, + }); + const state: TurnState = { + agent, + depth: 1, + ended: false, + key, + span, + }; + this.activeStates.add(state); + if (agent) { + this.getActiveTurns(agent).set(key, state); + } + + return state; + } + + private bindTurnCallback( + event: ChannelMessage, + agent: object | undefined, + span: Span, + ): void { + const callback = event.arguments[1]; + if (typeof callback !== "function") { + return; + } + + try { + event.arguments[1] = function ( + this: unknown, + ...args: unknown[] + ): unknown { + return withCurrent(span, () => { + try { + const input = serializeMessages(readProperty(agent, "messages")); + if (input !== undefined) { + span.log({ input }); + } + } catch (error) { + debugLogger.debug( + "Failed to capture @cloudflare/ai-chat messages:", + error, + ); + } + return Reflect.apply(callback, this, args); + }); + } as CloudflareAIChatTurnCallback; + } catch (error) { + debugLogger.debug( + "Failed to bind @cloudflare/ai-chat turn callback context:", + error, + ); + } + } + + private finishEvent( + event: ChannelMessage, + error?: unknown, + ): void { + const eventKey = event as object; + const state = this.eventStates.get(eventKey); + if (!state || state.ended) { + return; + } + this.eventStates.delete(eventKey); + if (error !== undefined && state.pendingError === undefined) { + state.pendingError = error; + } + state.depth -= 1; + if (state.depth === 0) { + this.finalizeState(state); + } + } + + private finalizeState(state: TurnState): void { + if (state.ended) { + return; + } + state.ended = true; + try { + if (state.pendingError !== undefined) { + state.span.log({ error: state.pendingError }); + } + } finally { + state.span.end(); + this.activeStates.delete(state); + if (state.agent) { + const activeForAgent = this.activeTurns.get(state.agent); + if (activeForAgent?.get(state.key) === state) { + activeForAgent.delete(state.key); + } + } + } + } + + private getActiveTurns(agent: object): Map { + let turns = this.activeTurns.get(agent); + if (!turns) { + turns = new Map(); + this.activeTurns.set(agent, turns); + } + return turns; + } + + private findResponseState( + agent: object, + requestId: string | undefined, + ): TurnState | undefined { + const turns = this.activeTurns.get(agent); + if (!turns) { + return undefined; + } + if (requestId !== undefined) { + return turns.get(requestId); + } + const active = [...turns.values()].filter((state) => !state.ended); + return active.length === 1 ? active[0] : undefined; + } +} + +function asObject(value: unknown): object | undefined { + return value !== null && + (typeof value === "object" || typeof value === "function") + ? value + : undefined; +} + +function readProperty(value: unknown, key: PropertyKey): unknown { + const object = asObject(value); + return object ? Reflect.get(object, key) : undefined; +} + +function ownValue(value: unknown, key: PropertyKey): unknown { + const object = asObject(value); + if (!object) { + return undefined; + } + const descriptor = Object.getOwnPropertyDescriptor(object, key); + return descriptor && "value" in descriptor ? descriptor.value : undefined; +} + +function stringValue(value: unknown): string | undefined { + return typeof value === "string" ? value : undefined; +} + +function serializeMessages( + value: unknown, +): Record[] | undefined { + if (!Array.isArray(value)) { + return undefined; + } + return value.flatMap((message) => { + const serialized = serializeMessage(message); + return serialized === undefined ? [] : [serialized]; + }); +} + +function serializeMessage(value: unknown): Record | undefined { + const message = asObject(value) as CloudflareAIChatMessage | undefined; + if (!message) { + return undefined; + } + + const serialized: Array<[string, unknown]> = []; + const id = ownValue(message, "id"); + const role = ownValue(message, "role"); + const parts = ownValue(message, "parts"); + if (typeof id === "string") { + serialized.push(["id", id]); + } + if (typeof role === "string") { + serialized.push(["role", role]); + } + const sanitizedParts = sanitizeLoggedValue(parts); + if (sanitizedParts !== undefined) { + serialized.push(["parts", sanitizedParts]); + } + return serialized.length > 0 ? Object.fromEntries(serialized) : undefined; +} + +function sanitizeLoggedValue( + value: unknown, + seen = new WeakSet(), + depth = 0, +): unknown { + if ( + value === null || + typeof value === "string" || + typeof value === "boolean" + ) { + return value; + } + if (typeof value === "number") { + return Number.isFinite(value) ? value : undefined; + } + const object = asObject(value); + if (!object || depth >= 20 || typeof value === "function") { + return undefined; + } + if (seen.has(object)) { + return "[Circular]"; + } + seen.add(object); + + if (Array.isArray(value)) { + return value.map((entry) => sanitizeLoggedValue(entry, seen, depth + 1)); + } + + const entries: Array<[string, unknown]> = []; + for (const [key, descriptor] of Object.entries( + Object.getOwnPropertyDescriptors(object), + )) { + if ( + BLOCKED_KEYS.has(key) || + !descriptor.enumerable || + !("value" in descriptor) + ) { + continue; + } + const sanitized = sanitizeLoggedValue(descriptor.value, seen, depth + 1); + if (sanitized !== undefined) { + entries.push([key, sanitized]); + } + } + return Object.fromEntries(entries); +} diff --git a/js/src/instrumentation/registry.test.ts b/js/src/instrumentation/registry.test.ts index 4fa54be93..3f4c751b2 100644 --- a/js/src/instrumentation/registry.test.ts +++ b/js/src/instrumentation/registry.test.ts @@ -161,6 +161,7 @@ describe("configureInstrumentation API", () => { cohere: false, mastra: false, strandsAgentSDK: false, + cloudflareAIChat: false, }, }); }); diff --git a/js/src/vendor-sdk-types/cloudflare-ai-chat.ts b/js/src/vendor-sdk-types/cloudflare-ai-chat.ts new file mode 100644 index 000000000..e3cb6dc50 --- /dev/null +++ b/js/src/vendor-sdk-types/cloudflare-ai-chat.ts @@ -0,0 +1,51 @@ +/** + * Vendored types for @cloudflare/ai-chat used by Braintrust instrumentation. + * + * Keep this surface intentionally narrow. These types are not exported to SDK + * users and only cover fields that the instrumentation reads or wraps. + */ + +export interface CloudflareAIChatModule { + AIChatAgent?: CloudflareAIChatAgentConstructor; + [key: string]: unknown; +} + +export interface CloudflareAIChatAgentConstructor { + new (...args: unknown[]): CloudflareAIChatAgent; + [key: string | symbol]: unknown; +} + +export interface CloudflareAIChatAgent { + readonly messages?: CloudflareAIChatMessage[]; + _runExclusiveChatTurn( + requestId: string, + callback: CloudflareAIChatTurnCallback, + options?: CloudflareAIChatTurnOptions, + ): Promise; + onChatResponse(result: CloudflareAIChatResponseResult): unknown; + [key: string | symbol]: unknown; +} + +export type CloudflareAIChatTurnCallback = () => Promise; + +export interface CloudflareAIChatTurnOptions { + epoch?: number; + onStale?: () => void; + [key: string]: unknown; +} + +export interface CloudflareAIChatMessage { + id?: string; + role?: string; + parts?: unknown[]; + [key: string]: unknown; +} + +export interface CloudflareAIChatResponseResult { + message?: CloudflareAIChatMessage; + requestId?: string; + continuation?: boolean; + status?: "completed" | "error" | "aborted" | string; + error?: unknown; + [key: string]: unknown; +} diff --git a/js/src/wrappers/cloudflare-ai-chat.test.ts b/js/src/wrappers/cloudflare-ai-chat.test.ts new file mode 100644 index 000000000..123abe70a --- /dev/null +++ b/js/src/wrappers/cloudflare-ai-chat.test.ts @@ -0,0 +1,98 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +const { tracePromise } = vi.hoisted(() => ({ + tracePromise: vi.fn((fn: () => Promise, _event?: unknown) => fn()), +})); + +vi.mock("../isomorph", () => ({ + default: { + getEnv: vi.fn(() => undefined), + newTracingChannel: vi.fn(() => ({ + subscribe: vi.fn(), + tracePromise, + traceSync: vi.fn((fn: () => unknown) => fn()), + unsubscribe: vi.fn(), + })), + }, +})); + +import { wrapCloudflareAIChat } from "./cloudflare-ai-chat"; + +describe("wrapCloudflareAIChat", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it("returns unsupported modules unchanged", () => { + const module = { useAgentChat: () => undefined }; + expect(wrapCloudflareAIChat(module)).toBe(module); + }); + + it("wraps complete chat turns without changing subclass behavior", async () => { + class AIChatAgent { + static kind = "ai-chat"; + #secret = "private-value"; + messages = [ + { id: "user-1", role: "user", parts: [{ type: "text", text: "hi" }] }, + ]; + + async _runExclusiveChatTurn( + requestId: string, + callback: () => Promise, + ) { + return { requestId, value: await callback() }; + } + + onChatResponse() {} + + readSecret() { + return this.#secret; + } + } + + const module = wrapCloudflareAIChat({ AIChatAgent, untouched: "value" }); + class ChatAgent extends module.AIChatAgent { + onChatResponse = () => "field-hook"; + } + + const agent = new ChatAgent(); + const result = await agent._runExclusiveChatTurn("request-1", async () => + agent.readSecret(), + ); + + expect(result).toEqual({ requestId: "request-1", value: "private-value" }); + expect(agent).toBeInstanceOf(AIChatAgent); + expect(agent.onChatResponse()).toBe("field-hook"); + expect(module.AIChatAgent.kind).toBe("ai-chat"); + expect(module.untouched).toBe("value"); + expect(tracePromise).toHaveBeenCalledTimes(1); + expect(tracePromise.mock.calls[0][1]).toMatchObject({ + arguments: ["request-1", expect.any(Function)], + self: agent, + }); + }); + + it("preserves errors and does not double-wrap the class", async () => { + const failure = new Error("turn failed"); + class AIChatAgent { + async _runExclusiveChatTurn( + _requestId: string, + _callback: () => Promise, + ) { + throw failure; + } + onChatResponse() {} + } + + const wrappedOnce = wrapCloudflareAIChat({ AIChatAgent }); + const WrappedAIChatAgent = wrappedOnce.AIChatAgent; + const wrappedTwice = wrapCloudflareAIChat(wrappedOnce); + const agent = new wrappedTwice.AIChatAgent(); + + expect(wrappedTwice.AIChatAgent).toBe(WrappedAIChatAgent); + await expect( + agent._runExclusiveChatTurn("request-1", async () => {}), + ).rejects.toBe(failure); + expect(tracePromise).toHaveBeenCalledTimes(1); + }); +}); diff --git a/js/src/wrappers/cloudflare-ai-chat.ts b/js/src/wrappers/cloudflare-ai-chat.ts new file mode 100644 index 000000000..87df27775 --- /dev/null +++ b/js/src/wrappers/cloudflare-ai-chat.ts @@ -0,0 +1,102 @@ +import { debugLogger } from "../debug-logger"; +import { instrumentCloudflareAIChatAgent } from "../instrumentation/plugins/cloudflare-ai-chat-instrumentation"; +import type { + CloudflareAIChatAgent, + CloudflareAIChatAgentConstructor, + CloudflareAIChatModule, +} from "../vendor-sdk-types/cloudflare-ai-chat"; + +const wrappedClasses = new WeakMap(); + +/** + * Wraps the @cloudflare/ai-chat module with Braintrust tracing. + * + * Pass the module namespace (`import * as aiChat from "@cloudflare/ai-chat"`) + * and subclass the wrapped AIChatAgent export. The wrapper emits the same + * diagnostic-channel events as automatic instrumentation. + */ +export function wrapCloudflareAIChat(module: T): T { + if (!module || typeof module !== "object") { + return module; + } + + const candidate = module as Record; + let AIChatAgent: unknown; + try { + AIChatAgent = Reflect.get(candidate, "AIChatAgent"); + } catch (error) { + debugLogger.debug("Failed to inspect @cloudflare/ai-chat module:", error); + return module; + } + + if (typeof AIChatAgent !== "function") { + debugLogger.warn( + "Unsupported @cloudflare/ai-chat module. AIChatAgent was not found; not wrapping.", + ); + return module; + } + + const target = isModuleNamespace(module) + ? Object.setPrototypeOf({}, module) + : candidate; + + return new Proxy(target, { + get(target, property, receiver) { + const value = Reflect.get(target, property, receiver); + if (property === "AIChatAgent" && typeof value === "function") { + return wrapAIChatAgentClass( + value as unknown as CloudflareAIChatAgentConstructor, + ); + } + return value; + }, + }) as T & CloudflareAIChatModule; +} + +function wrapAIChatAgentClass( + AgentClass: CloudflareAIChatAgentConstructor, +): CloudflareAIChatAgentConstructor { + const cached = wrappedClasses.get(AgentClass); + if (cached) { + return cached; + } + + const wrapped = new Proxy(AgentClass, { + get(target, property) { + const value = Reflect.get(target, property, target); + return typeof value === "function" ? value.bind(target) : value; + }, + construct(target, args, newTarget) { + const instance = Reflect.construct( + target, + args, + newTarget, + ) as CloudflareAIChatAgent; + return instrumentCloudflareAIChatAgent(instance); + }, + }); + wrappedClasses.set(AgentClass, wrapped); + wrappedClasses.set(wrapped, wrapped); + return wrapped; +} + +function isModuleNamespace(value: unknown): boolean { + if (!value || typeof value !== "object") { + return false; + } + try { + if (Reflect.get(value, Symbol.toStringTag) === "Module") { + return true; + } + const keys = Object.keys(value); + if (keys.length === 0) { + return false; + } + const descriptor = Object.getOwnPropertyDescriptor(value, keys[0]); + return descriptor + ? !descriptor.configurable && !descriptor.writable + : false; + } catch { + return false; + } +}