diff --git a/aibridge/fixtures/openai/chatcompletions/simple.txtar b/aibridge/fixtures/openai/chatcompletions/simple.txtar
index 8f07d0c8ffa..2f458c3af98 100644
--- a/aibridge/fixtures/openai/chatcompletions/simple.txtar
+++ b/aibridge/fixtures/openai/chatcompletions/simple.txtar
@@ -492,7 +492,7 @@ data: {"id":"chatcmpl-BwoiPTGRbKkY5rncfaM0s9KtWrq5N","object":"chat.completion.c
data: {"id":"chatcmpl-BwoiPTGRbKkY5rncfaM0s9KtWrq5N","object":"chat.completion.chunk","created":1753357673,"model":"gpt-4.1-2025-04-14","service_tier":"default","system_fingerprint":"fp_51e1070cf2","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null}
-data: {"id":"chatcmpl-BwoiPTGRbKkY5rncfaM0s9KtWrq5N","object":"chat.completion.chunk","created":1753357673,"model":"gpt-4.1-2025-04-14","service_tier":"default","system_fingerprint":"fp_51e1070cf2","choices":[],"usage":{"prompt_tokens":19,"completion_tokens":238,"total_tokens":257,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}}}
+data: {"id":"chatcmpl-BwoiPTGRbKkY5rncfaM0s9KtWrq5N","object":"chat.completion.chunk","created":1753357673,"model":"gpt-4.1-2025-04-14","service_tier":"default","system_fingerprint":"fp_51e1070cf2","choices":[],"usage":{"prompt_tokens":19,"completion_tokens":238,"total_tokens":257,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":5,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}}}
data: [DONE]
@@ -521,6 +521,7 @@ data: [DONE]
"total_tokens": 219,
"prompt_tokens_details": {
"cached_tokens": 0,
+ "cache_write_tokens": 5,
"audio_tokens": 0
},
"completion_tokens_details": {
diff --git a/aibridge/fixtures/openai/chatcompletions/single_injected_tool.txtar b/aibridge/fixtures/openai/chatcompletions/single_injected_tool.txtar
index b89aac648a1..61b4c1c3212 100644
--- a/aibridge/fixtures/openai/chatcompletions/single_injected_tool.txtar
+++ b/aibridge/fixtures/openai/chatcompletions/single_injected_tool.txtar
@@ -72,7 +72,7 @@ data: {"id":"chatcmpl-C1WTooFaxeQgtyLB1kg53t41aB0NV","object":"chat.completion.c
data: {"id":"chatcmpl-C1WTooFaxeQgtyLB1kg53t41aB0NV","object":"chat.completion.chunk","created":1754479216,"model":"gpt-4.1-2025-04-14","service_tier":"default","system_fingerprint":"fp_799e4ca3f1","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"sDj"}
-data: {"id":"chatcmpl-C1WTooFaxeQgtyLB1kg53t41aB0NV","object":"chat.completion.chunk","created":1754479216,"model":"gpt-4.1-2025-04-14","service_tier":"default","system_fingerprint":"fp_799e4ca3f1","choices":[],"usage":{"prompt_tokens":4862,"completion_tokens":45,"total_tokens":4907,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"8sIWE1chOW"}
+data: {"id":"chatcmpl-C1WTooFaxeQgtyLB1kg53t41aB0NV","object":"chat.completion.chunk","created":1754479216,"model":"gpt-4.1-2025-04-14","service_tier":"default","system_fingerprint":"fp_799e4ca3f1","choices":[],"usage":{"prompt_tokens":4862,"completion_tokens":45,"total_tokens":4907,"prompt_tokens_details":{"cached_tokens":100,"cache_write_tokens":20,"audio_tokens":3},"completion_tokens_details":{"reasoning_tokens":4,"audio_tokens":5,"accepted_prediction_tokens":6,"rejected_prediction_tokens":7}},"obfuscation":"8sIWE1chOW"}
data: [DONE]
@@ -200,7 +200,7 @@ data: {"id":"chatcmpl-C1WTqhYgK7bV01bW98Lww3zqaf8ZF","object":"chat.completion.c
data: {"id":"chatcmpl-C1WTqhYgK7bV01bW98Lww3zqaf8ZF","object":"chat.completion.chunk","created":1754479218,"model":"gpt-4.1-2025-04-14","service_tier":"default","system_fingerprint":"fp_799e4ca3f1","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"EFeFvdS8m"}
-data: {"id":"chatcmpl-C1WTqhYgK7bV01bW98Lww3zqaf8ZF","object":"chat.completion.chunk","created":1754479218,"model":"gpt-4.1-2025-04-14","service_tier":"default","system_fingerprint":"fp_799e4ca3f1","choices":[],"usage":{"prompt_tokens":5049,"completion_tokens":60,"total_tokens":5109,"prompt_tokens_details":{"cached_tokens":4864,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"0JQt7Fw"}
+data: {"id":"chatcmpl-C1WTqhYgK7bV01bW98Lww3zqaf8ZF","object":"chat.completion.chunk","created":1754479218,"model":"gpt-4.1-2025-04-14","service_tier":"default","system_fingerprint":"fp_799e4ca3f1","choices":[],"usage":{"prompt_tokens":5049,"completion_tokens":60,"total_tokens":5109,"prompt_tokens_details":{"cached_tokens":4864,"cache_write_tokens":10,"audio_tokens":9},"completion_tokens_details":{"reasoning_tokens":10,"audio_tokens":11,"accepted_prediction_tokens":12,"rejected_prediction_tokens":13}},"obfuscation":"0JQt7Fw"}
data: [DONE]
@@ -237,16 +237,17 @@ data: [DONE]
"usage": {
"prompt_tokens": 4862,
"completion_tokens": 45,
- "total_tokens": 4914,
+ "total_tokens": 4907,
"prompt_tokens_details": {
- "cached_tokens": 0,
- "audio_tokens": 0
+ "cached_tokens": 100,
+ "cache_write_tokens": 20,
+ "audio_tokens": 3
},
"completion_tokens_details": {
- "reasoning_tokens": 0,
- "audio_tokens": 0,
- "accepted_prediction_tokens": 0,
- "rejected_prediction_tokens": 0
+ "reasoning_tokens": 4,
+ "audio_tokens": 5,
+ "accepted_prediction_tokens": 6,
+ "rejected_prediction_tokens": 7
}
},
"service_tier": "default",
@@ -276,16 +277,17 @@ data: [DONE]
"usage": {
"prompt_tokens": 5049,
"completion_tokens": 60,
- "total_tokens": 5119,
+ "total_tokens": 5109,
"prompt_tokens_details": {
"cached_tokens": 4864,
- "audio_tokens": 0
+ "cache_write_tokens": 10,
+ "audio_tokens": 9
},
"completion_tokens_details": {
- "reasoning_tokens": 0,
- "audio_tokens": 0,
- "accepted_prediction_tokens": 0,
- "rejected_prediction_tokens": 0
+ "reasoning_tokens": 10,
+ "audio_tokens": 11,
+ "accepted_prediction_tokens": 12,
+ "rejected_prediction_tokens": 13
}
},
"service_tier": "default",
diff --git a/aibridge/fixtures/openai/chatcompletions/streaming_cumulative_usage_injected_tool.txtar b/aibridge/fixtures/openai/chatcompletions/streaming_cumulative_usage_injected_tool.txtar
index ccdc0639776..5474c4ac975 100644
--- a/aibridge/fixtures/openai/chatcompletions/streaming_cumulative_usage_injected_tool.txtar
+++ b/aibridge/fixtures/openai/chatcompletions/streaming_cumulative_usage_injected_tool.txtar
@@ -12,19 +12,19 @@ Streaming response with cumulative usage on every JSON chunk and an injected too
}
-- streaming --
-data: {"id":"chatcmpl-cumulative-tool","object":"chat.completion.chunk","created":1754479216,"model":"gpt-4.1","choices":[{"index":0,"delta":{"content":"Calling"}}],"usage":{"prompt_tokens":6000,"completion_tokens":10,"total_tokens":6010}}
+data: {"id":"chatcmpl-cumulative-tool","object":"chat.completion.chunk","created":1754479216,"model":"gpt-4.1","choices":[{"index":0,"delta":{"content":"Calling"}}],"usage":{"prompt_tokens":6000,"completion_tokens":10,"total_tokens":6010,"prompt_tokens_details":{"cached_tokens":100,"cache_write_tokens":10,"audio_tokens":1},"completion_tokens_details":{"reasoning_tokens":1,"audio_tokens":1,"accepted_prediction_tokens":1,"rejected_prediction_tokens":1}}}
-data: {"id":"chatcmpl-cumulative-tool","object":"chat.completion.chunk","created":1754479216,"model":"gpt-4.1","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call-cumulative","type":"function","function":{"name":"bmcp_coder_coder_list_workspaces","arguments":"{}"}}]}}],"usage":{"prompt_tokens":6000,"completion_tokens":20,"total_tokens":6020}}
+data: {"id":"chatcmpl-cumulative-tool","object":"chat.completion.chunk","created":1754479216,"model":"gpt-4.1","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call-cumulative","type":"function","function":{"name":"bmcp_coder_coder_list_workspaces","arguments":"{}"}}]}}],"usage":{"prompt_tokens":6000,"completion_tokens":20,"total_tokens":6020,"prompt_tokens_details":{"cached_tokens":100,"cache_write_tokens":10,"audio_tokens":2},"completion_tokens_details":{"reasoning_tokens":2,"audio_tokens":2,"accepted_prediction_tokens":2,"rejected_prediction_tokens":2}}}
-data: {"id":"chatcmpl-cumulative-tool","object":"chat.completion.chunk","created":1754479216,"model":"gpt-4.1","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":6000,"completion_tokens":30,"total_tokens":6030}}
+data: {"id":"chatcmpl-cumulative-tool","object":"chat.completion.chunk","created":1754479216,"model":"gpt-4.1","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":6000,"completion_tokens":30,"total_tokens":6030,"prompt_tokens_details":{"cached_tokens":100,"cache_write_tokens":10,"audio_tokens":3},"completion_tokens_details":{"reasoning_tokens":4,"audio_tokens":5,"accepted_prediction_tokens":6,"rejected_prediction_tokens":7}}}
data: [DONE]
-- streaming/tool-call --
-data: {"id":"chatcmpl-cumulative-final","object":"chat.completion.chunk","created":1754479217,"model":"gpt-4.1","choices":[{"index":0,"delta":{"content":"Done"}}],"usage":{"prompt_tokens":6000,"completion_tokens":10,"total_tokens":6010}}
+data: {"id":"chatcmpl-cumulative-final","object":"chat.completion.chunk","created":1754479217,"model":"gpt-4.1","choices":[{"index":0,"delta":{"content":"Done"}}],"usage":{"prompt_tokens":6000,"completion_tokens":10,"total_tokens":6010,"prompt_tokens_details":{"cached_tokens":200,"cache_write_tokens":20,"audio_tokens":7},"completion_tokens_details":{"reasoning_tokens":8,"audio_tokens":9,"accepted_prediction_tokens":10,"rejected_prediction_tokens":11}}}
-data: {"id":"chatcmpl-cumulative-final","object":"chat.completion.chunk","created":1754479217,"model":"gpt-4.1","choices":[{"index":0,"delta":{"content":"."}}],"usage":{"prompt_tokens":6000,"completion_tokens":20,"total_tokens":6020}}
+data: {"id":"chatcmpl-cumulative-final","object":"chat.completion.chunk","created":1754479217,"model":"gpt-4.1","choices":[{"index":0,"delta":{"content":"."}}],"usage":{"prompt_tokens":6000,"completion_tokens":20,"total_tokens":6020,"prompt_tokens_details":{"cached_tokens":200,"cache_write_tokens":20,"audio_tokens":8},"completion_tokens_details":{"reasoning_tokens":9,"audio_tokens":10,"accepted_prediction_tokens":11,"rejected_prediction_tokens":12}}}
-data: {"id":"chatcmpl-cumulative-final","object":"chat.completion.chunk","created":1754479217,"model":"gpt-4.1","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":6000,"completion_tokens":30,"total_tokens":6030}}
+data: {"id":"chatcmpl-cumulative-final","object":"chat.completion.chunk","created":1754479217,"model":"gpt-4.1","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":6000,"completion_tokens":30,"total_tokens":6030,"prompt_tokens_details":{"cached_tokens":200,"cache_write_tokens":20,"audio_tokens":9},"completion_tokens_details":{"reasoning_tokens":10,"audio_tokens":11,"accepted_prediction_tokens":12,"rejected_prediction_tokens":13}}}
data: [DONE]
diff --git a/aibridge/fixtures/openai/responses/blocking/cached_input_tokens.txtar b/aibridge/fixtures/openai/responses/blocking/cached_input_tokens.txtar
index 41a6d7ca7e3..66689d6a483 100644
--- a/aibridge/fixtures/openai/responses/blocking/cached_input_tokens.txtar
+++ b/aibridge/fixtures/openai/responses/blocking/cached_input_tokens.txtar
@@ -68,7 +68,8 @@
"usage": {
"input_tokens": 12033,
"input_tokens_details": {
- "cached_tokens": 11904
+ "cached_tokens": 11904,
+ "cache_write_tokens": 15
},
"output_tokens": 44,
"output_tokens_details": {
diff --git a/aibridge/fixtures/openai/responses/blocking/single_injected_tool.txtar b/aibridge/fixtures/openai/responses/blocking/single_injected_tool.txtar
index 028377dcaa9..ce46145d78d 100644
--- a/aibridge/fixtures/openai/responses/blocking/single_injected_tool.txtar
+++ b/aibridge/fixtures/openai/responses/blocking/single_injected_tool.txtar
@@ -749,7 +749,8 @@ Coder MCP tools automatically injected.
"usage": {
"input_tokens": 6371,
"input_tokens_details": {
- "cached_tokens": 6144
+ "cached_tokens": 6144,
+ "cache_write_tokens": 7
},
"output_tokens": 75,
"output_tokens_details": {
@@ -1509,7 +1510,8 @@ Coder MCP tools automatically injected.
"usage": {
"input_tokens": 6756,
"input_tokens_details": {
- "cached_tokens": 6144
+ "cached_tokens": 6144,
+ "cache_write_tokens": 11
},
"output_tokens": 231,
"output_tokens_details": {
diff --git a/aibridge/fixtures/openai/responses/blocking/single_injected_tool_error.txtar b/aibridge/fixtures/openai/responses/blocking/single_injected_tool_error.txtar
index 9e4c2716f20..17c180d7dc5 100644
--- a/aibridge/fixtures/openai/responses/blocking/single_injected_tool_error.txtar
+++ b/aibridge/fixtures/openai/responses/blocking/single_injected_tool_error.txtar
@@ -749,7 +749,8 @@ Coder MCP tools automatically injected, and errors invoking them are recorded.
"usage": {
"input_tokens": 6377,
"input_tokens_details": {
- "cached_tokens": 6144
+ "cached_tokens": 6144,
+ "cache_write_tokens": 5
},
"output_tokens": 119,
"output_tokens_details": {
@@ -1509,7 +1510,8 @@ Coder MCP tools automatically injected, and errors invoking them are recorded.
"usage": {
"input_tokens": 6539,
"input_tokens_details": {
- "cached_tokens": 6144
+ "cached_tokens": 6144,
+ "cache_write_tokens": 9
},
"output_tokens": 144,
"output_tokens_details": {
diff --git a/aibridge/fixtures/openai/responses/streaming/cached_input_tokens.txtar b/aibridge/fixtures/openai/responses/streaming/cached_input_tokens.txtar
index cc908d5abdf..8dffa4eabe9 100644
--- a/aibridge/fixtures/openai/responses/streaming/cached_input_tokens.txtar
+++ b/aibridge/fixtures/openai/responses/streaming/cached_input_tokens.txtar
@@ -43,5 +43,5 @@ event: response.output_item.done
data: {"type":"response.output_item.done","item":{"id":"msg_05080461b406f3f501696a140a70d88195a2ce4c1a4eb39696","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"text":"Test response with cached tokens."}],"role":"assistant"},"output_index":0,"sequence_number":11}
event: response.completed
-data: {"type":"response.completed","response":{"id":"resp_05080461b406f3f501696a1409d34c8195a40ff4b092145c35","object":"response","created_at":1768559625,"status":"completed","background":false,"completed_at":1768559627,"error":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.2-codex","output":[{"id":"msg_05080461b406f3f501696a140a70d88195a2ce4c1a4eb39696","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"text":"Test response with cached tokens."}],"role":"assistant"}],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":"019bc657-f77b-7292-b5f4-2e8d6c2b0945","prompt_cache_retention":null,"reasoning":{"effort":"medium","summary":"detailed"},"service_tier":"default","store":false,"temperature":1.0,"tool_choice":"auto","tools":[],"truncation":"disabled","usage":{"input_tokens":16909,"input_tokens_details":{"cached_tokens":15744},"output_tokens":54,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":16963},"user":null,"metadata":{}},"sequence_number":12}
+data: {"type":"response.completed","response":{"id":"resp_05080461b406f3f501696a1409d34c8195a40ff4b092145c35","object":"response","created_at":1768559625,"status":"completed","background":false,"completed_at":1768559627,"error":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.2-codex","output":[{"id":"msg_05080461b406f3f501696a140a70d88195a2ce4c1a4eb39696","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"text":"Test response with cached tokens."}],"role":"assistant"}],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":"019bc657-f77b-7292-b5f4-2e8d6c2b0945","prompt_cache_retention":null,"reasoning":{"effort":"medium","summary":"detailed"},"service_tier":"default","store":false,"temperature":1.0,"tool_choice":"auto","tools":[],"truncation":"disabled","usage":{"input_tokens":16909,"input_tokens_details":{"cached_tokens":15744,"cache_write_tokens":30},"output_tokens":54,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":16963},"user":null,"metadata":{}},"sequence_number":12}
diff --git a/aibridge/fixtures/openai/responses/streaming/single_injected_tool.txtar b/aibridge/fixtures/openai/responses/streaming/single_injected_tool.txtar
index 0e079d1e7a4..7df2ec235b4 100644
--- a/aibridge/fixtures/openai/responses/streaming/single_injected_tool.txtar
+++ b/aibridge/fixtures/openai/responses/streaming/single_injected_tool.txtar
@@ -25,7 +25,7 @@ event: response.output_item.done
data: {"type":"response.output_item.done","item":{"id":"fc_016595fe42aa62ca006972441b4d0081a0bbf6b65aa91022df","type":"function_call","status":"completed","arguments":"{}","call_id":"call_GuuoyhUrVJQbWfHHz0xaX3n9","name":"bmcp_coder_coder_list_templates"},"output_index":0,"sequence_number":5}
event: response.completed
-data: {"type":"response.completed","response":{"id":"resp_016595fe42aa62ca0069724419c52081a0b7eb479c6bc8109f","object":"response","created_at":1769096217,"status":"completed","background":false,"completed_at":1769096219,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-mini-2025-04-14","output":[{"id":"fc_016595fe42aa62ca006972441b4d0081a0bbf6b65aa91022df","type":"function_call","status":"completed","arguments":"{}","call_id":"call_GuuoyhUrVJQbWfHHz0xaX3n9","name":"bmcp_coder_coder_list_templates"}],"parallel_tool_calls":false,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Create a task.","name":"bmcp_coder_coder_create_task","parameters":{"properties":{"input":{"description":"Input/prompt for the task.","type":"string"},"template_version_id":{"description":"ID of the template version to create the task from.","type":"string"},"template_version_preset_id":{"description":"Optional ID of the template version preset to create the task from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a task. Omit or use the `me` keyword to create a task for the authenticated user.","type":"string"}},"required":["input","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template in Coder. First, you must create a template version.","name":"bmcp_coder_coder_create_template","parameters":{"properties":{"description":{"type":"string"},"display_name":{"type":"string"},"icon":{"description":"A URL to an icon to use.","type":"string"},"name":{"type":"string"},"version_id":{"description":"The ID of the version to use.","type":"string"}},"required":["name","display_name","description","version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template version. This is a precursor to creating a template, or you can update an existing template.\n\nTemplates are Terraform defining a development environment. The provisioned infrastructure must run\nan Agent that connects to the Coder Control Plane to provide a rich experience.\n\nHere are some strict rules for creating a template version:\n- YOU MUST NOT use \"variable\" or \"output\" blocks in the Terraform code.\n- YOU MUST ALWAYS check template version logs after creation to ensure the template was imported successfully.\n\nWhen a template version is created, a Terraform Plan occurs that ensures the infrastructure\n_could_ be provisioned, but actual provisioning occurs when a workspace is created.\n\n\nThe Coder Terraform Provider can be imported like:\n\n```hcl\nterraform {\n required_providers {\n coder = {\n source = \"coder/coder\"\n }\n }\n}\n```\n\nA destroy does not occur when a user stops a workspace, but rather the transition changes:\n\n```hcl\ndata \"coder_workspace\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace.\n- name: The name of the workspace.\n- transition: Either \"start\" or \"stop\".\n- start_count: A computed count based on the transition field. If \"start\", this will be 1.\n\nAccess workspace owner information with:\n\n```hcl\ndata \"coder_workspace_owner\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace owner.\n- name: The name of the workspace owner.\n- full_name: The full name of the workspace owner.\n- email: The email of the workspace owner.\n- session_token: A token that can be used to authenticate the workspace owner. It is regenerated every time the workspace is started.\n- oidc_access_token: A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.\n\nParameters are defined in the template version. They are rendered in the UI on the workspace creation page:\n\n```hcl\nresource \"coder_parameter\" \"region\" {\n name = \"region\"\n type = \"string\"\n default = \"us-east-1\"\n}\n```\n\nThis resource accepts the following properties:\n- name: The name of the parameter.\n- default: The default value of the parameter.\n- type: The type of the parameter. Must be one of: \"string\", \"number\", \"bool\", or \"list(string)\".\n- display_name: The displayed name of the parameter as it will appear in the UI.\n- description: The description of the parameter as it will appear in the UI.\n- ephemeral: The value of an ephemeral parameter will not be preserved between consecutive workspace builds.\n- form_type: The type of this parameter. Must be one of: [radio, slider, input, dropdown, checkbox, switch, multi-select, tag-select, textarea, error].\n- icon: A URL to an icon to display in the UI.\n- mutable: Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution!\n- option: Each option block defines a value for a user to select from. (see below for nested schema)\n Required:\n - name: The name of the option.\n - value: The value of the option.\n Optional:\n - description: The description of the option as it will appear in the UI.\n - icon: A URL to an icon to display in the UI.\n\nA Workspace Agent runs on provisioned infrastructure to provide access to the workspace:\n\n```hcl\nresource \"coder_agent\" \"dev\" {\n arch = \"amd64\"\n os = \"linux\"\n}\n```\n\nThis resource accepts the following properties:\n- arch: The architecture of the agent. Must be one of: \"amd64\", \"arm64\", or \"armv7\".\n- os: The operating system of the agent. Must be one of: \"linux\", \"windows\", or \"darwin\".\n- auth: The authentication method for the agent. Must be one of: \"token\", \"google-instance-identity\", \"aws-instance-identity\", or \"azure-instance-identity\". It is insecure to pass the agent token via exposed variables to Virtual Machines. Instance Identity enables provisioned VMs to authenticate by instance ID on start.\n- dir: The starting directory when a user creates a shell session. Defaults to \"$HOME\".\n- env: A map of environment variables to set for the agent.\n- startup_script: A script to run after the agent starts. This script MUST exit eventually to signal that startup has completed. Use \"&\" or \"screen\" to run processes in the background.\n\nThis resource provides the following fields:\n- id: The UUID of the agent.\n- init_script: The script to run on provisioned infrastructure to fetch and start the agent.\n- token: Set the environment variable CODER_AGENT_TOKEN to this value to authenticate the agent.\n\nThe agent MUST be installed and started using the init_script. A utility like curl or wget to fetch the agent binary must exist in the provisioned infrastructure.\n\nExpose terminal or HTTP applications running in a workspace with:\n\n```hcl\nresource \"coder_app\" \"dev\" {\n agent_id = coder_agent.dev.id\n slug = \"my-app-name\"\n display_name = \"My App\"\n icon = \"https://my-app.com/icon.svg\"\n url = \"http://127.0.0.1:3000\"\n}\n```\n\nThis resource accepts the following properties:\n- agent_id: The ID of the agent to attach the app to.\n- slug: The slug of the app.\n- display_name: The displayed name of the app as it will appear in the UI.\n- icon: A URL to an icon to display in the UI.\n- url: An external url if external=true or a URL to be proxied to from inside the workspace. This should be of the form http://localhost:PORT[/SUBPATH]. Either command or url may be specified, but not both.\n- command: A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either command or url may be specified, but not both.\n- external: Whether this app is an external app. If true, the url will be opened in a new tab.\n\n\nThe Coder Server may not be authenticated with the infrastructure provider a user requests. In this scenario,\nthe user will need to provide credentials to the Coder Server before the workspace can be provisioned.\n\nHere are examples of provisioning the Coder Agent on specific infrastructure providers:\n\n\n// The agent is configured with \"aws-instance-identity\" auth.\nterraform {\n required_providers {\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n aws = {\n source = \"hashicorp/aws\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = false\n boundary = \"//\"\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${linux_user}\n\t// sudo: ALL=(ALL) NOPASSWD:ALL\n\t// shell: /bin/bash\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n hostname = local.hostname\n linux_user = local.linux_user\n })\n }\n\n part {\n filename = \"userdata.sh\"\n content_type = \"text/x-shellscript\"\n\n\t// Here is the content of the userdata.sh.tftpl file:\n\t// #!/bin/bash\n\t// sudo -u '${linux_user}' sh -c '${init_script}'\n content = templatefile(\"${path.module}/cloud-init/userdata.sh.tftpl\", {\n linux_user = local.linux_user\n\n init_script = try(coder_agent.dev[0].init_script, \"\")\n })\n }\n}\n\nresource \"aws_instance\" \"dev\" {\n ami = data.aws_ami.ubuntu.id\n availability_zone = \"${data.coder_parameter.region.value}a\"\n instance_type = data.coder_parameter.instance_type.value\n\n user_data = data.cloudinit_config.user_data.rendered\n tags = {\n Name = \"coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}\"\n }\n lifecycle {\n ignore_changes = [ami]\n }\n}\n\n\n\n// The agent is configured with \"google-instance-identity\" auth.\nterraform {\n required_providers {\n google = {\n source = \"hashicorp/google\"\n }\n }\n}\n\nresource \"google_compute_instance\" \"dev\" {\n zone = module.gcp_region.value\n count = data.coder_workspace.me.start_count\n name = \"coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-root\"\n machine_type = \"e2-medium\"\n network_interface {\n network = \"default\"\n access_config {\n // Ephemeral public IP\n }\n }\n boot_disk {\n auto_delete = false\n source = google_compute_disk.root.name\n }\n // In order to use google-instance-identity, a service account *must* be provided.\n service_account {\n email = data.google_compute_default_service_account.default.email\n scopes = [\"cloud-platform\"]\n }\n # ONLY FOR WINDOWS:\n # metadata = {\n # windows-startup-script-ps1 = coder_agent.main.init_script\n # }\n # The startup script runs as root with no $HOME environment set up, so instead of directly\n # running the agent init script, create a user (with a homedir, default shell and sudo\n # permissions) and execute the init script as that user.\n #\n # The agent MUST be started in here.\n metadata_startup_script = </dev/null 2>&1; then\n useradd -m -s /bin/bash \"${local.linux_user}\"\n echo \"${local.linux_user} ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/coder-user\nfi\n\nexec sudo -u \"${local.linux_user}\" sh -c '${coder_agent.main.init_script}'\nEOMETA\n}\n\n\n\n// The agent is configured with \"azure-instance-identity\" auth.\nterraform {\n required_providers {\n azurerm = {\n source = \"hashicorp/azurerm\"\n }\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = true\n\n boundary = \"//\"\n\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// bootcmd:\n\t// # work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117\n\t// - until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done\n\t// device_aliases:\n\t// homedir: /dev/disk/azure/scsi1/lun10\n\t// disk_setup:\n\t// homedir:\n\t// table_type: gpt\n\t// layout: true\n\t// fs_setup:\n\t// - label: coder_home\n\t// filesystem: ext4\n\t// device: homedir.1\n\t// mounts:\n\t// - [\"LABEL=coder_home\", \"/home/${username}\"]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${username}\n\t// sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n\t// groups: sudo\n\t// shell: /bin/bash\n\t// packages:\n\t// - git\n\t// write_files:\n\t// - path: /opt/coder/init\n\t// permissions: \"0755\"\n\t// encoding: b64\n\t// content: ${init_script}\n\t// - path: /etc/systemd/system/coder-agent.service\n\t// permissions: \"0644\"\n\t// content: |\n\t// [Unit]\n\t// Description=Coder Agent\n\t// After=network-online.target\n\t// Wants=network-online.target\n\n\t// [Service]\n\t// User=${username}\n\t// ExecStart=/opt/coder/init\n\t// Restart=always\n\t// RestartSec=10\n\t// TimeoutStopSec=90\n\t// KillMode=process\n\n\t// OOMScoreAdjust=-900\n\t// SyslogIdentifier=coder-agent\n\n\t// [Install]\n\t// WantedBy=multi-user.target\n\t// runcmd:\n\t// - chown ${username}:${username} /home/${username}\n\t// - systemctl enable coder-agent\n\t// - systemctl start coder-agent\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n username = \"coder\" # Ensure this user/group does not exist in your VM image\n init_script = base64encode(coder_agent.main.init_script)\n hostname = lower(data.coder_workspace.me.name)\n })\n }\n}\n\nresource \"azurerm_linux_virtual_machine\" \"main\" {\n count = data.coder_workspace.me.start_count\n name = \"vm\"\n resource_group_name = azurerm_resource_group.main.name\n location = azurerm_resource_group.main.location\n size = data.coder_parameter.instance_type.value\n // cloud-init overwrites this, so the value here doesn't matter\n admin_username = \"adminuser\"\n admin_ssh_key {\n public_key = tls_private_key.dummy.public_key_openssh\n username = \"adminuser\"\n }\n\n network_interface_ids = [\n azurerm_network_interface.main.id,\n ]\n computer_name = lower(data.coder_workspace.me.name)\n os_disk {\n caching = \"ReadWrite\"\n storage_account_type = \"Standard_LRS\"\n }\n source_image_reference {\n publisher = \"Canonical\"\n offer = \"0001-com-ubuntu-server-focal\"\n sku = \"20_04-lts-gen2\"\n version = \"latest\"\n }\n user_data = data.cloudinit_config.user_data.rendered\n}\n\n\n\nterraform {\n required_providers {\n coder = {\n source = \"kreuzwerker/docker\"\n }\n }\n}\n\n// The agent is configured with \"token\" auth.\n\nresource \"docker_container\" \"workspace\" {\n count = data.coder_workspace.me.start_count\n image = \"codercom/enterprise-base:ubuntu\"\n # Uses lower() to avoid Docker restriction on container names.\n name = \"coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}\"\n # Hostname makes the shell more user friendly: coder@my-workspace:~$\n hostname = data.coder_workspace.me.name\n # Use the docker gateway if the access URL is 127.0.0.1.\n entrypoint = [\"sh\", \"-c\", replace(coder_agent.main.init_script, \"/localhost|127\\\\.0\\\\.0\\\\.1/\", \"host.docker.internal\")]\n env = [\"CODER_AGENT_TOKEN=${coder_agent.main.token}\"]\n host {\n host = \"host.docker.internal\"\n ip = \"host-gateway\"\n }\n volumes {\n container_path = \"/home/coder\"\n volume_name = docker_volume.home_volume.name\n read_only = false\n }\n}\n\n\n\n// The agent is configured with \"token\" auth.\n\nresource \"kubernetes_deployment\" \"main\" {\n count = data.coder_workspace.me.start_count\n depends_on = [\n kubernetes_persistent_volume_claim.home\n ]\n wait_for_rollout = false\n metadata {\n name = \"coder-${data.coder_workspace.me.id}\"\n }\n\n spec {\n replicas = 1\n strategy {\n type = \"Recreate\"\n }\n\n template {\n spec {\n security_context {\n run_as_user = 1000\n fs_group = 1000\n run_as_non_root = true\n }\n\n container {\n name = \"dev\"\n image = \"codercom/enterprise-base:ubuntu\"\n image_pull_policy = \"Always\"\n command = [\"sh\", \"-c\", coder_agent.main.init_script]\n security_context {\n run_as_user = \"1000\"\n }\n env {\n name = \"CODER_AGENT_TOKEN\"\n value = coder_agent.main.token\n }\n }\n }\n }\n }\n}\n\n\nThe file_id provided is a reference to a tar file you have uploaded containing the Terraform.\n","name":"bmcp_coder_coder_create_template_version","parameters":{"properties":{"file_id":{"type":"string"},"template_id":{"type":"string"}},"required":["file_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace in Coder.\n\nIf a user is asking to \"test a template\", they are typically referring\nto creating a workspace from a template to ensure the infrastructure\nis provisioned correctly and the agent can connect to the control plane.\n\nBefore creating a workspace, always confirm the template choice with the user by:\n\n\t1. Listing the available templates that match their request.\n\t2. Recommending the most relevant option.\n\t2. Asking the user to confirm which template to use.\n\nIt is important to not create a workspace without confirming the template\nchoice with the user.\n\nAfter creating a workspace, watch the build logs and wait for the workspace to\nbe ready before trying to use or connect to the workspace.\n","name":"bmcp_coder_coder_create_workspace","parameters":{"properties":{"name":{"description":"Name of the workspace to create.","type":"string"},"rich_parameters":{"description":"Key/value pairs of rich parameters to pass to the template version to create the workspace.","type":"object"},"template_version_id":{"description":"ID of the template version to create the workspace from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a workspace. Omit or use the `me` keyword to create a workspace for the authenticated user.","type":"string"}},"required":["user","template_version_id","name","rich_parameters"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace build for an existing workspace. Use this to start, stop, or delete.\n\nAfter creating a workspace build, watch the build logs and wait for the\nworkspace build to complete before trying to start another build or use or\nconnect to the workspace.\n","name":"bmcp_coder_coder_create_workspace_build","parameters":{"properties":{"template_version_id":{"description":"(Optional) The template version ID to use for the workspace build. If not provided, the previously built version will be used.","type":"string"},"transition":{"description":"The transition to perform. Must be one of: start, stop, delete","enum":["start","stop","delete"],"type":"string"},"workspace_id":{"type":"string"}},"required":["workspace_id","transition"],"type":"object"},"strict":false},{"type":"function","description":"Delete a task.","name":"bmcp_coder_coder_delete_task","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to delete. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Delete a template. This is irreversible.","name":"bmcp_coder_coder_delete_template","parameters":{"properties":{"template_id":{"type":"string"}},"required":["template_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the currently authenticated user, similar to the `whoami` command.","name":"bmcp_coder_coder_get_authenticated_user","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a task.","name":"bmcp_coder_coder_get_task_logs","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to query. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the status of a task.","name":"bmcp_coder_coder_get_task_status","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to get. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a template version. This is useful to check whether a template version successfully imports or not.","name":"bmcp_coder_coder_get_template_version_logs","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Get a workspace by name or ID.\n\nThis returns more data than list_workspaces to reduce token usage.","name":"bmcp_coder_coder_get_workspace","parameters":{"properties":{"workspace_id":{"description":"The workspace ID or name in the format [owner/]workspace. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace agent.\n\n\t\tMore logs may appear after this call. It does not wait for the agent to finish.","name":"bmcp_coder_coder_get_workspace_agent_logs","parameters":{"properties":{"workspace_agent_id":{"type":"string"}},"required":["workspace_agent_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace build.\n\n\t\tUseful for checking whether a workspace builds successfully or not.","name":"bmcp_coder_coder_get_workspace_build_logs","parameters":{"properties":{"workspace_build_id":{"type":"string"}},"required":["workspace_build_id"],"type":"object"},"strict":false},{"type":"function","description":"List tasks.","name":"bmcp_coder_coder_list_tasks","parameters":{"properties":{"status":{"description":"Optional filter by task status.","type":"string"},"user":{"description":"Username or ID of the user for which to list tasks. Omit or use the `me` keyword to list tasks for the authenticated user.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Lists templates for the authenticated user.","name":"bmcp_coder_coder_list_templates","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Lists workspaces for the authenticated user.","name":"bmcp_coder_coder_list_workspaces","parameters":{"properties":{"owner":{"description":"The owner of the workspaces to list. Use \"me\" to list workspaces for the authenticated user. If you do not specify an owner, \"me\" will be assumed by default.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Send input to a running task.","name":"bmcp_coder_coder_send_task_input","parameters":{"properties":{"input":{"description":"The input to send to the task.","type":"string"},"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to prompt. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id","input"],"type":"object"},"strict":false},{"type":"function","description":"Get the parameters for a template version. You can refer to these as workspace parameters to the user, as they are typically important for creating a workspace.","name":"bmcp_coder_coder_template_version_parameters","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Update the active version of a template. This is helpful when iterating on templates.","name":"bmcp_coder_coder_update_template_active_version","parameters":{"properties":{"template_id":{"type":"string"},"template_version_id":{"type":"string"}},"required":["template_id","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create and upload a tar file by key/value mapping of file names to file contents. Use this to create template versions. Reference the tool description of \"create_template_version\" to understand template requirements.","name":"bmcp_coder_coder_upload_tar_file","parameters":{"properties":{"files":{"description":"A map of file names to file contents.","type":"object"}},"required":["files"],"type":"object"},"strict":false},{"type":"function","description":"Execute a bash command in a Coder workspace.\n\nThis tool provides the same functionality as the 'coder ssh ' CLI command.\nIt automatically starts the workspace if it's stopped and waits for the agent to be ready.\nThe output is trimmed of leading and trailing whitespace.\n\nThe workspace parameter supports various formats:\n- workspace (uses current user)\n- owner/workspace\n- owner--workspace\n- workspace.agent (specific agent)\n- owner/workspace.agent\n\nThe timeout_ms parameter specifies the command timeout in milliseconds (defaults to 60000ms, maximum of 300000ms).\nIf the command times out, all output captured up to that point is returned with a cancellation message.\n\nFor background commands (background: true), output is captured until the timeout is reached, then the command\ncontinues running in the background. The captured output is returned as the result.\n\nFor file operations (list, write, edit), always prefer the dedicated file tools.\nDo not use bash commands (ls, cat, echo, heredoc, etc.) to list, write, or read\nfiles when the file tools are available. The bash tool should be used for:\n\n\t- Running commands and scripts\n\t- Installing packages\n\t- Starting services\n\t- Executing programs\n\nExamples:\n- workspace: \"john/dev-env\", command: \"git status\", timeout_ms: 30000\n- workspace: \"my-workspace\", command: \"npm run dev\", background: true, timeout_ms: 10000\n- workspace: \"my-workspace.main\", command: \"docker ps\"","name":"bmcp_coder_coder_workspace_bash","parameters":{"properties":{"background":{"description":"Whether to run the command in the background. Output is captured until timeout, then the command continues running in the background.","type":"boolean"},"command":{"description":"The bash command to execute in the workspace.","type":"string"},"timeout_ms":{"default":60000,"description":"Command timeout in milliseconds. Defaults to 60000ms (60 seconds) if not specified.","minimum":1,"type":"integer"},"workspace":{"description":"The workspace name in format [owner/]workspace[.agent]. If owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","command"],"type":"object"},"strict":false},{"type":"function","description":"Edit a file in a workspace.","name":"bmcp_coder_coder_workspace_edit_file","parameters":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","edits"],"type":"object"},"strict":false},{"type":"function","description":"Edit one or more files in a workspace.","name":"bmcp_coder_coder_workspace_edit_files","parameters":{"properties":{"files":{"description":"An array of files to edit.","items":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"}},"required":["path","edits"],"type":"object"},"type":"array"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","files"],"type":"object"},"strict":false},{"type":"function","description":"List the URLs of Coder apps running in a workspace for a single agent.","name":"bmcp_coder_coder_workspace_list_apps","parameters":{"properties":{"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace"],"type":"object"},"strict":false},{"type":"function","description":"List directories in a workspace.","name":"bmcp_coder_coder_workspace_ls","parameters":{"properties":{"path":{"description":"The absolute path of the directory in the workspace to list.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Fetch URLs that forward to the specified port.","name":"bmcp_coder_coder_workspace_port_forward","parameters":{"properties":{"port":{"description":"The port to forward.","type":"number"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","port"],"type":"object"},"strict":false},{"type":"function","description":"Read from a file in a workspace.","name":"bmcp_coder_coder_workspace_read_file","parameters":{"properties":{"limit":{"description":"The number of bytes to read. Cannot exceed 1 MiB. Defaults to the full size of the file or 1 MiB, whichever is lower.","type":"integer"},"offset":{"description":"A byte offset indicating where in the file to start reading. Defaults to zero. An empty string indicates the end of the file has been reached.","type":"integer"},"path":{"description":"The absolute path of the file to read in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Write a file in a workspace.\n\nIf a file write fails due to syntax errors or encoding issues, do NOT switch\nto using bash commands as a workaround. Instead:\n\n\t1. Read the error message carefully to identify the issue\n\t2. Fix the content encoding/syntax\n\t3. Retry with this tool\n\nThe content parameter expects base64-encoded bytes. Ensure your source content\nis correct before encoding it. If you encounter errors, decode and verify the\ncontent you are trying to write, then re-encode it properly.\n","name":"bmcp_coder_coder_workspace_write_file","parameters":{"properties":{"content":{"description":"The base64-encoded bytes to write to the file.","type":"string"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","content"],"type":"object"},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":6269,"input_tokens_details":{"cached_tokens":0},"output_tokens":18,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":6287},"user":null,"metadata":{}},"sequence_number":6}
+data: {"type":"response.completed","response":{"id":"resp_016595fe42aa62ca0069724419c52081a0b7eb479c6bc8109f","object":"response","created_at":1769096217,"status":"completed","background":false,"completed_at":1769096219,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-mini-2025-04-14","output":[{"id":"fc_016595fe42aa62ca006972441b4d0081a0bbf6b65aa91022df","type":"function_call","status":"completed","arguments":"{}","call_id":"call_GuuoyhUrVJQbWfHHz0xaX3n9","name":"bmcp_coder_coder_list_templates"}],"parallel_tool_calls":false,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Create a task.","name":"bmcp_coder_coder_create_task","parameters":{"properties":{"input":{"description":"Input/prompt for the task.","type":"string"},"template_version_id":{"description":"ID of the template version to create the task from.","type":"string"},"template_version_preset_id":{"description":"Optional ID of the template version preset to create the task from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a task. Omit or use the `me` keyword to create a task for the authenticated user.","type":"string"}},"required":["input","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template in Coder. First, you must create a template version.","name":"bmcp_coder_coder_create_template","parameters":{"properties":{"description":{"type":"string"},"display_name":{"type":"string"},"icon":{"description":"A URL to an icon to use.","type":"string"},"name":{"type":"string"},"version_id":{"description":"The ID of the version to use.","type":"string"}},"required":["name","display_name","description","version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template version. This is a precursor to creating a template, or you can update an existing template.\n\nTemplates are Terraform defining a development environment. The provisioned infrastructure must run\nan Agent that connects to the Coder Control Plane to provide a rich experience.\n\nHere are some strict rules for creating a template version:\n- YOU MUST NOT use \"variable\" or \"output\" blocks in the Terraform code.\n- YOU MUST ALWAYS check template version logs after creation to ensure the template was imported successfully.\n\nWhen a template version is created, a Terraform Plan occurs that ensures the infrastructure\n_could_ be provisioned, but actual provisioning occurs when a workspace is created.\n\n\nThe Coder Terraform Provider can be imported like:\n\n```hcl\nterraform {\n required_providers {\n coder = {\n source = \"coder/coder\"\n }\n }\n}\n```\n\nA destroy does not occur when a user stops a workspace, but rather the transition changes:\n\n```hcl\ndata \"coder_workspace\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace.\n- name: The name of the workspace.\n- transition: Either \"start\" or \"stop\".\n- start_count: A computed count based on the transition field. If \"start\", this will be 1.\n\nAccess workspace owner information with:\n\n```hcl\ndata \"coder_workspace_owner\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace owner.\n- name: The name of the workspace owner.\n- full_name: The full name of the workspace owner.\n- email: The email of the workspace owner.\n- session_token: A token that can be used to authenticate the workspace owner. It is regenerated every time the workspace is started.\n- oidc_access_token: A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.\n\nParameters are defined in the template version. They are rendered in the UI on the workspace creation page:\n\n```hcl\nresource \"coder_parameter\" \"region\" {\n name = \"region\"\n type = \"string\"\n default = \"us-east-1\"\n}\n```\n\nThis resource accepts the following properties:\n- name: The name of the parameter.\n- default: The default value of the parameter.\n- type: The type of the parameter. Must be one of: \"string\", \"number\", \"bool\", or \"list(string)\".\n- display_name: The displayed name of the parameter as it will appear in the UI.\n- description: The description of the parameter as it will appear in the UI.\n- ephemeral: The value of an ephemeral parameter will not be preserved between consecutive workspace builds.\n- form_type: The type of this parameter. Must be one of: [radio, slider, input, dropdown, checkbox, switch, multi-select, tag-select, textarea, error].\n- icon: A URL to an icon to display in the UI.\n- mutable: Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution!\n- option: Each option block defines a value for a user to select from. (see below for nested schema)\n Required:\n - name: The name of the option.\n - value: The value of the option.\n Optional:\n - description: The description of the option as it will appear in the UI.\n - icon: A URL to an icon to display in the UI.\n\nA Workspace Agent runs on provisioned infrastructure to provide access to the workspace:\n\n```hcl\nresource \"coder_agent\" \"dev\" {\n arch = \"amd64\"\n os = \"linux\"\n}\n```\n\nThis resource accepts the following properties:\n- arch: The architecture of the agent. Must be one of: \"amd64\", \"arm64\", or \"armv7\".\n- os: The operating system of the agent. Must be one of: \"linux\", \"windows\", or \"darwin\".\n- auth: The authentication method for the agent. Must be one of: \"token\", \"google-instance-identity\", \"aws-instance-identity\", or \"azure-instance-identity\". It is insecure to pass the agent token via exposed variables to Virtual Machines. Instance Identity enables provisioned VMs to authenticate by instance ID on start.\n- dir: The starting directory when a user creates a shell session. Defaults to \"$HOME\".\n- env: A map of environment variables to set for the agent.\n- startup_script: A script to run after the agent starts. This script MUST exit eventually to signal that startup has completed. Use \"&\" or \"screen\" to run processes in the background.\n\nThis resource provides the following fields:\n- id: The UUID of the agent.\n- init_script: The script to run on provisioned infrastructure to fetch and start the agent.\n- token: Set the environment variable CODER_AGENT_TOKEN to this value to authenticate the agent.\n\nThe agent MUST be installed and started using the init_script. A utility like curl or wget to fetch the agent binary must exist in the provisioned infrastructure.\n\nExpose terminal or HTTP applications running in a workspace with:\n\n```hcl\nresource \"coder_app\" \"dev\" {\n agent_id = coder_agent.dev.id\n slug = \"my-app-name\"\n display_name = \"My App\"\n icon = \"https://my-app.com/icon.svg\"\n url = \"http://127.0.0.1:3000\"\n}\n```\n\nThis resource accepts the following properties:\n- agent_id: The ID of the agent to attach the app to.\n- slug: The slug of the app.\n- display_name: The displayed name of the app as it will appear in the UI.\n- icon: A URL to an icon to display in the UI.\n- url: An external url if external=true or a URL to be proxied to from inside the workspace. This should be of the form http://localhost:PORT[/SUBPATH]. Either command or url may be specified, but not both.\n- command: A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either command or url may be specified, but not both.\n- external: Whether this app is an external app. If true, the url will be opened in a new tab.\n\n\nThe Coder Server may not be authenticated with the infrastructure provider a user requests. In this scenario,\nthe user will need to provide credentials to the Coder Server before the workspace can be provisioned.\n\nHere are examples of provisioning the Coder Agent on specific infrastructure providers:\n\n\n// The agent is configured with \"aws-instance-identity\" auth.\nterraform {\n required_providers {\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n aws = {\n source = \"hashicorp/aws\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = false\n boundary = \"//\"\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${linux_user}\n\t// sudo: ALL=(ALL) NOPASSWD:ALL\n\t// shell: /bin/bash\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n hostname = local.hostname\n linux_user = local.linux_user\n })\n }\n\n part {\n filename = \"userdata.sh\"\n content_type = \"text/x-shellscript\"\n\n\t// Here is the content of the userdata.sh.tftpl file:\n\t// #!/bin/bash\n\t// sudo -u '${linux_user}' sh -c '${init_script}'\n content = templatefile(\"${path.module}/cloud-init/userdata.sh.tftpl\", {\n linux_user = local.linux_user\n\n init_script = try(coder_agent.dev[0].init_script, \"\")\n })\n }\n}\n\nresource \"aws_instance\" \"dev\" {\n ami = data.aws_ami.ubuntu.id\n availability_zone = \"${data.coder_parameter.region.value}a\"\n instance_type = data.coder_parameter.instance_type.value\n\n user_data = data.cloudinit_config.user_data.rendered\n tags = {\n Name = \"coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}\"\n }\n lifecycle {\n ignore_changes = [ami]\n }\n}\n\n\n\n// The agent is configured with \"google-instance-identity\" auth.\nterraform {\n required_providers {\n google = {\n source = \"hashicorp/google\"\n }\n }\n}\n\nresource \"google_compute_instance\" \"dev\" {\n zone = module.gcp_region.value\n count = data.coder_workspace.me.start_count\n name = \"coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-root\"\n machine_type = \"e2-medium\"\n network_interface {\n network = \"default\"\n access_config {\n // Ephemeral public IP\n }\n }\n boot_disk {\n auto_delete = false\n source = google_compute_disk.root.name\n }\n // In order to use google-instance-identity, a service account *must* be provided.\n service_account {\n email = data.google_compute_default_service_account.default.email\n scopes = [\"cloud-platform\"]\n }\n # ONLY FOR WINDOWS:\n # metadata = {\n # windows-startup-script-ps1 = coder_agent.main.init_script\n # }\n # The startup script runs as root with no $HOME environment set up, so instead of directly\n # running the agent init script, create a user (with a homedir, default shell and sudo\n # permissions) and execute the init script as that user.\n #\n # The agent MUST be started in here.\n metadata_startup_script = </dev/null 2>&1; then\n useradd -m -s /bin/bash \"${local.linux_user}\"\n echo \"${local.linux_user} ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/coder-user\nfi\n\nexec sudo -u \"${local.linux_user}\" sh -c '${coder_agent.main.init_script}'\nEOMETA\n}\n\n\n\n// The agent is configured with \"azure-instance-identity\" auth.\nterraform {\n required_providers {\n azurerm = {\n source = \"hashicorp/azurerm\"\n }\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = true\n\n boundary = \"//\"\n\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// bootcmd:\n\t// # work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117\n\t// - until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done\n\t// device_aliases:\n\t// homedir: /dev/disk/azure/scsi1/lun10\n\t// disk_setup:\n\t// homedir:\n\t// table_type: gpt\n\t// layout: true\n\t// fs_setup:\n\t// - label: coder_home\n\t// filesystem: ext4\n\t// device: homedir.1\n\t// mounts:\n\t// - [\"LABEL=coder_home\", \"/home/${username}\"]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${username}\n\t// sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n\t// groups: sudo\n\t// shell: /bin/bash\n\t// packages:\n\t// - git\n\t// write_files:\n\t// - path: /opt/coder/init\n\t// permissions: \"0755\"\n\t// encoding: b64\n\t// content: ${init_script}\n\t// - path: /etc/systemd/system/coder-agent.service\n\t// permissions: \"0644\"\n\t// content: |\n\t// [Unit]\n\t// Description=Coder Agent\n\t// After=network-online.target\n\t// Wants=network-online.target\n\n\t// [Service]\n\t// User=${username}\n\t// ExecStart=/opt/coder/init\n\t// Restart=always\n\t// RestartSec=10\n\t// TimeoutStopSec=90\n\t// KillMode=process\n\n\t// OOMScoreAdjust=-900\n\t// SyslogIdentifier=coder-agent\n\n\t// [Install]\n\t// WantedBy=multi-user.target\n\t// runcmd:\n\t// - chown ${username}:${username} /home/${username}\n\t// - systemctl enable coder-agent\n\t// - systemctl start coder-agent\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n username = \"coder\" # Ensure this user/group does not exist in your VM image\n init_script = base64encode(coder_agent.main.init_script)\n hostname = lower(data.coder_workspace.me.name)\n })\n }\n}\n\nresource \"azurerm_linux_virtual_machine\" \"main\" {\n count = data.coder_workspace.me.start_count\n name = \"vm\"\n resource_group_name = azurerm_resource_group.main.name\n location = azurerm_resource_group.main.location\n size = data.coder_parameter.instance_type.value\n // cloud-init overwrites this, so the value here doesn't matter\n admin_username = \"adminuser\"\n admin_ssh_key {\n public_key = tls_private_key.dummy.public_key_openssh\n username = \"adminuser\"\n }\n\n network_interface_ids = [\n azurerm_network_interface.main.id,\n ]\n computer_name = lower(data.coder_workspace.me.name)\n os_disk {\n caching = \"ReadWrite\"\n storage_account_type = \"Standard_LRS\"\n }\n source_image_reference {\n publisher = \"Canonical\"\n offer = \"0001-com-ubuntu-server-focal\"\n sku = \"20_04-lts-gen2\"\n version = \"latest\"\n }\n user_data = data.cloudinit_config.user_data.rendered\n}\n\n\n\nterraform {\n required_providers {\n coder = {\n source = \"kreuzwerker/docker\"\n }\n }\n}\n\n// The agent is configured with \"token\" auth.\n\nresource \"docker_container\" \"workspace\" {\n count = data.coder_workspace.me.start_count\n image = \"codercom/enterprise-base:ubuntu\"\n # Uses lower() to avoid Docker restriction on container names.\n name = \"coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}\"\n # Hostname makes the shell more user friendly: coder@my-workspace:~$\n hostname = data.coder_workspace.me.name\n # Use the docker gateway if the access URL is 127.0.0.1.\n entrypoint = [\"sh\", \"-c\", replace(coder_agent.main.init_script, \"/localhost|127\\\\.0\\\\.0\\\\.1/\", \"host.docker.internal\")]\n env = [\"CODER_AGENT_TOKEN=${coder_agent.main.token}\"]\n host {\n host = \"host.docker.internal\"\n ip = \"host-gateway\"\n }\n volumes {\n container_path = \"/home/coder\"\n volume_name = docker_volume.home_volume.name\n read_only = false\n }\n}\n\n\n\n// The agent is configured with \"token\" auth.\n\nresource \"kubernetes_deployment\" \"main\" {\n count = data.coder_workspace.me.start_count\n depends_on = [\n kubernetes_persistent_volume_claim.home\n ]\n wait_for_rollout = false\n metadata {\n name = \"coder-${data.coder_workspace.me.id}\"\n }\n\n spec {\n replicas = 1\n strategy {\n type = \"Recreate\"\n }\n\n template {\n spec {\n security_context {\n run_as_user = 1000\n fs_group = 1000\n run_as_non_root = true\n }\n\n container {\n name = \"dev\"\n image = \"codercom/enterprise-base:ubuntu\"\n image_pull_policy = \"Always\"\n command = [\"sh\", \"-c\", coder_agent.main.init_script]\n security_context {\n run_as_user = \"1000\"\n }\n env {\n name = \"CODER_AGENT_TOKEN\"\n value = coder_agent.main.token\n }\n }\n }\n }\n }\n}\n\n\nThe file_id provided is a reference to a tar file you have uploaded containing the Terraform.\n","name":"bmcp_coder_coder_create_template_version","parameters":{"properties":{"file_id":{"type":"string"},"template_id":{"type":"string"}},"required":["file_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace in Coder.\n\nIf a user is asking to \"test a template\", they are typically referring\nto creating a workspace from a template to ensure the infrastructure\nis provisioned correctly and the agent can connect to the control plane.\n\nBefore creating a workspace, always confirm the template choice with the user by:\n\n\t1. Listing the available templates that match their request.\n\t2. Recommending the most relevant option.\n\t2. Asking the user to confirm which template to use.\n\nIt is important to not create a workspace without confirming the template\nchoice with the user.\n\nAfter creating a workspace, watch the build logs and wait for the workspace to\nbe ready before trying to use or connect to the workspace.\n","name":"bmcp_coder_coder_create_workspace","parameters":{"properties":{"name":{"description":"Name of the workspace to create.","type":"string"},"rich_parameters":{"description":"Key/value pairs of rich parameters to pass to the template version to create the workspace.","type":"object"},"template_version_id":{"description":"ID of the template version to create the workspace from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a workspace. Omit or use the `me` keyword to create a workspace for the authenticated user.","type":"string"}},"required":["user","template_version_id","name","rich_parameters"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace build for an existing workspace. Use this to start, stop, or delete.\n\nAfter creating a workspace build, watch the build logs and wait for the\nworkspace build to complete before trying to start another build or use or\nconnect to the workspace.\n","name":"bmcp_coder_coder_create_workspace_build","parameters":{"properties":{"template_version_id":{"description":"(Optional) The template version ID to use for the workspace build. If not provided, the previously built version will be used.","type":"string"},"transition":{"description":"The transition to perform. Must be one of: start, stop, delete","enum":["start","stop","delete"],"type":"string"},"workspace_id":{"type":"string"}},"required":["workspace_id","transition"],"type":"object"},"strict":false},{"type":"function","description":"Delete a task.","name":"bmcp_coder_coder_delete_task","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to delete. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Delete a template. This is irreversible.","name":"bmcp_coder_coder_delete_template","parameters":{"properties":{"template_id":{"type":"string"}},"required":["template_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the currently authenticated user, similar to the `whoami` command.","name":"bmcp_coder_coder_get_authenticated_user","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a task.","name":"bmcp_coder_coder_get_task_logs","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to query. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the status of a task.","name":"bmcp_coder_coder_get_task_status","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to get. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a template version. This is useful to check whether a template version successfully imports or not.","name":"bmcp_coder_coder_get_template_version_logs","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Get a workspace by name or ID.\n\nThis returns more data than list_workspaces to reduce token usage.","name":"bmcp_coder_coder_get_workspace","parameters":{"properties":{"workspace_id":{"description":"The workspace ID or name in the format [owner/]workspace. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace agent.\n\n\t\tMore logs may appear after this call. It does not wait for the agent to finish.","name":"bmcp_coder_coder_get_workspace_agent_logs","parameters":{"properties":{"workspace_agent_id":{"type":"string"}},"required":["workspace_agent_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace build.\n\n\t\tUseful for checking whether a workspace builds successfully or not.","name":"bmcp_coder_coder_get_workspace_build_logs","parameters":{"properties":{"workspace_build_id":{"type":"string"}},"required":["workspace_build_id"],"type":"object"},"strict":false},{"type":"function","description":"List tasks.","name":"bmcp_coder_coder_list_tasks","parameters":{"properties":{"status":{"description":"Optional filter by task status.","type":"string"},"user":{"description":"Username or ID of the user for which to list tasks. Omit or use the `me` keyword to list tasks for the authenticated user.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Lists templates for the authenticated user.","name":"bmcp_coder_coder_list_templates","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Lists workspaces for the authenticated user.","name":"bmcp_coder_coder_list_workspaces","parameters":{"properties":{"owner":{"description":"The owner of the workspaces to list. Use \"me\" to list workspaces for the authenticated user. If you do not specify an owner, \"me\" will be assumed by default.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Send input to a running task.","name":"bmcp_coder_coder_send_task_input","parameters":{"properties":{"input":{"description":"The input to send to the task.","type":"string"},"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to prompt. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id","input"],"type":"object"},"strict":false},{"type":"function","description":"Get the parameters for a template version. You can refer to these as workspace parameters to the user, as they are typically important for creating a workspace.","name":"bmcp_coder_coder_template_version_parameters","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Update the active version of a template. This is helpful when iterating on templates.","name":"bmcp_coder_coder_update_template_active_version","parameters":{"properties":{"template_id":{"type":"string"},"template_version_id":{"type":"string"}},"required":["template_id","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create and upload a tar file by key/value mapping of file names to file contents. Use this to create template versions. Reference the tool description of \"create_template_version\" to understand template requirements.","name":"bmcp_coder_coder_upload_tar_file","parameters":{"properties":{"files":{"description":"A map of file names to file contents.","type":"object"}},"required":["files"],"type":"object"},"strict":false},{"type":"function","description":"Execute a bash command in a Coder workspace.\n\nThis tool provides the same functionality as the 'coder ssh ' CLI command.\nIt automatically starts the workspace if it's stopped and waits for the agent to be ready.\nThe output is trimmed of leading and trailing whitespace.\n\nThe workspace parameter supports various formats:\n- workspace (uses current user)\n- owner/workspace\n- owner--workspace\n- workspace.agent (specific agent)\n- owner/workspace.agent\n\nThe timeout_ms parameter specifies the command timeout in milliseconds (defaults to 60000ms, maximum of 300000ms).\nIf the command times out, all output captured up to that point is returned with a cancellation message.\n\nFor background commands (background: true), output is captured until the timeout is reached, then the command\ncontinues running in the background. The captured output is returned as the result.\n\nFor file operations (list, write, edit), always prefer the dedicated file tools.\nDo not use bash commands (ls, cat, echo, heredoc, etc.) to list, write, or read\nfiles when the file tools are available. The bash tool should be used for:\n\n\t- Running commands and scripts\n\t- Installing packages\n\t- Starting services\n\t- Executing programs\n\nExamples:\n- workspace: \"john/dev-env\", command: \"git status\", timeout_ms: 30000\n- workspace: \"my-workspace\", command: \"npm run dev\", background: true, timeout_ms: 10000\n- workspace: \"my-workspace.main\", command: \"docker ps\"","name":"bmcp_coder_coder_workspace_bash","parameters":{"properties":{"background":{"description":"Whether to run the command in the background. Output is captured until timeout, then the command continues running in the background.","type":"boolean"},"command":{"description":"The bash command to execute in the workspace.","type":"string"},"timeout_ms":{"default":60000,"description":"Command timeout in milliseconds. Defaults to 60000ms (60 seconds) if not specified.","minimum":1,"type":"integer"},"workspace":{"description":"The workspace name in format [owner/]workspace[.agent]. If owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","command"],"type":"object"},"strict":false},{"type":"function","description":"Edit a file in a workspace.","name":"bmcp_coder_coder_workspace_edit_file","parameters":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","edits"],"type":"object"},"strict":false},{"type":"function","description":"Edit one or more files in a workspace.","name":"bmcp_coder_coder_workspace_edit_files","parameters":{"properties":{"files":{"description":"An array of files to edit.","items":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"}},"required":["path","edits"],"type":"object"},"type":"array"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","files"],"type":"object"},"strict":false},{"type":"function","description":"List the URLs of Coder apps running in a workspace for a single agent.","name":"bmcp_coder_coder_workspace_list_apps","parameters":{"properties":{"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace"],"type":"object"},"strict":false},{"type":"function","description":"List directories in a workspace.","name":"bmcp_coder_coder_workspace_ls","parameters":{"properties":{"path":{"description":"The absolute path of the directory in the workspace to list.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Fetch URLs that forward to the specified port.","name":"bmcp_coder_coder_workspace_port_forward","parameters":{"properties":{"port":{"description":"The port to forward.","type":"number"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","port"],"type":"object"},"strict":false},{"type":"function","description":"Read from a file in a workspace.","name":"bmcp_coder_coder_workspace_read_file","parameters":{"properties":{"limit":{"description":"The number of bytes to read. Cannot exceed 1 MiB. Defaults to the full size of the file or 1 MiB, whichever is lower.","type":"integer"},"offset":{"description":"A byte offset indicating where in the file to start reading. Defaults to zero. An empty string indicates the end of the file has been reached.","type":"integer"},"path":{"description":"The absolute path of the file to read in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Write a file in a workspace.\n\nIf a file write fails due to syntax errors or encoding issues, do NOT switch\nto using bash commands as a workaround. Instead:\n\n\t1. Read the error message carefully to identify the issue\n\t2. Fix the content encoding/syntax\n\t3. Retry with this tool\n\nThe content parameter expects base64-encoded bytes. Ensure your source content\nis correct before encoding it. If you encounter errors, decode and verify the\ncontent you are trying to write, then re-encode it properly.\n","name":"bmcp_coder_coder_workspace_write_file","parameters":{"properties":{"content":{"description":"The base64-encoded bytes to write to the file.","type":"string"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","content"],"type":"object"},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":6269,"input_tokens_details":{"cached_tokens":100,"cache_write_tokens":7},"output_tokens":18,"output_tokens_details":{"reasoning_tokens":3},"total_tokens":6287},"user":null,"metadata":{}},"sequence_number":6}
-- streaming/tool-call --
@@ -591,5 +591,5 @@ event: response.output_item.done
data: {"type":"response.output_item.done","item":{"id":"msg_0bc5f54fce6df69a0069724421feb88194acb48ce194f3ee14","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"You have two Coder templates:\n\n1. Template Name: codex-test\n - Template ID: d85cac35-15a1-4bde-97d9-1f3e4b851246\n - Active Version ID: 22a3face-0c93-4b88-a63a-1ec1651e0199\n - Active User Count: 1\n\n2. Template Name: docker\n - Template ID: 7e799e56-6591-4c44-b575-3c72b55b7217\n - Active Version ID: 8057a565-1c12-489e-a563-8e8bb162c867\n - Active User Count: 1\n\nLet me know if you want more details or want to perform any actions with these templates."}],"role":"assistant"},"output_index":0,"sequence_number":186}
event: response.completed
-data: {"type":"response.completed","response":{"id":"resp_0bc5f54fce6df69a006972442175908194bb81d31f576e6ca6","object":"response","created_at":1769096225,"status":"completed","background":false,"completed_at":1769096230,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-mini-2025-04-14","output":[{"id":"msg_0bc5f54fce6df69a0069724421feb88194acb48ce194f3ee14","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"You have two Coder templates:\n\n1. Template Name: codex-test\n - Template ID: d85cac35-15a1-4bde-97d9-1f3e4b851246\n - Active Version ID: 22a3face-0c93-4b88-a63a-1ec1651e0199\n - Active User Count: 1\n\n2. Template Name: docker\n - Template ID: 7e799e56-6591-4c44-b575-3c72b55b7217\n - Active Version ID: 8057a565-1c12-489e-a563-8e8bb162c867\n - Active User Count: 1\n\nLet me know if you want more details or want to perform any actions with these templates."}],"role":"assistant"}],"parallel_tool_calls":false,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Create a task.","name":"bmcp_coder_coder_create_task","parameters":{"properties":{"input":{"description":"Input/prompt for the task.","type":"string"},"template_version_id":{"description":"ID of the template version to create the task from.","type":"string"},"template_version_preset_id":{"description":"Optional ID of the template version preset to create the task from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a task. Omit or use the `me` keyword to create a task for the authenticated user.","type":"string"}},"required":["input","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template in Coder. First, you must create a template version.","name":"bmcp_coder_coder_create_template","parameters":{"properties":{"description":{"type":"string"},"display_name":{"type":"string"},"icon":{"description":"A URL to an icon to use.","type":"string"},"name":{"type":"string"},"version_id":{"description":"The ID of the version to use.","type":"string"}},"required":["name","display_name","description","version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template version. This is a precursor to creating a template, or you can update an existing template.\n\nTemplates are Terraform defining a development environment. The provisioned infrastructure must run\nan Agent that connects to the Coder Control Plane to provide a rich experience.\n\nHere are some strict rules for creating a template version:\n- YOU MUST NOT use \"variable\" or \"output\" blocks in the Terraform code.\n- YOU MUST ALWAYS check template version logs after creation to ensure the template was imported successfully.\n\nWhen a template version is created, a Terraform Plan occurs that ensures the infrastructure\n_could_ be provisioned, but actual provisioning occurs when a workspace is created.\n\n\nThe Coder Terraform Provider can be imported like:\n\n```hcl\nterraform {\n required_providers {\n coder = {\n source = \"coder/coder\"\n }\n }\n}\n```\n\nA destroy does not occur when a user stops a workspace, but rather the transition changes:\n\n```hcl\ndata \"coder_workspace\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace.\n- name: The name of the workspace.\n- transition: Either \"start\" or \"stop\".\n- start_count: A computed count based on the transition field. If \"start\", this will be 1.\n\nAccess workspace owner information with:\n\n```hcl\ndata \"coder_workspace_owner\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace owner.\n- name: The name of the workspace owner.\n- full_name: The full name of the workspace owner.\n- email: The email of the workspace owner.\n- session_token: A token that can be used to authenticate the workspace owner. It is regenerated every time the workspace is started.\n- oidc_access_token: A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.\n\nParameters are defined in the template version. They are rendered in the UI on the workspace creation page:\n\n```hcl\nresource \"coder_parameter\" \"region\" {\n name = \"region\"\n type = \"string\"\n default = \"us-east-1\"\n}\n```\n\nThis resource accepts the following properties:\n- name: The name of the parameter.\n- default: The default value of the parameter.\n- type: The type of the parameter. Must be one of: \"string\", \"number\", \"bool\", or \"list(string)\".\n- display_name: The displayed name of the parameter as it will appear in the UI.\n- description: The description of the parameter as it will appear in the UI.\n- ephemeral: The value of an ephemeral parameter will not be preserved between consecutive workspace builds.\n- form_type: The type of this parameter. Must be one of: [radio, slider, input, dropdown, checkbox, switch, multi-select, tag-select, textarea, error].\n- icon: A URL to an icon to display in the UI.\n- mutable: Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution!\n- option: Each option block defines a value for a user to select from. (see below for nested schema)\n Required:\n - name: The name of the option.\n - value: The value of the option.\n Optional:\n - description: The description of the option as it will appear in the UI.\n - icon: A URL to an icon to display in the UI.\n\nA Workspace Agent runs on provisioned infrastructure to provide access to the workspace:\n\n```hcl\nresource \"coder_agent\" \"dev\" {\n arch = \"amd64\"\n os = \"linux\"\n}\n```\n\nThis resource accepts the following properties:\n- arch: The architecture of the agent. Must be one of: \"amd64\", \"arm64\", or \"armv7\".\n- os: The operating system of the agent. Must be one of: \"linux\", \"windows\", or \"darwin\".\n- auth: The authentication method for the agent. Must be one of: \"token\", \"google-instance-identity\", \"aws-instance-identity\", or \"azure-instance-identity\". It is insecure to pass the agent token via exposed variables to Virtual Machines. Instance Identity enables provisioned VMs to authenticate by instance ID on start.\n- dir: The starting directory when a user creates a shell session. Defaults to \"$HOME\".\n- env: A map of environment variables to set for the agent.\n- startup_script: A script to run after the agent starts. This script MUST exit eventually to signal that startup has completed. Use \"&\" or \"screen\" to run processes in the background.\n\nThis resource provides the following fields:\n- id: The UUID of the agent.\n- init_script: The script to run on provisioned infrastructure to fetch and start the agent.\n- token: Set the environment variable CODER_AGENT_TOKEN to this value to authenticate the agent.\n\nThe agent MUST be installed and started using the init_script. A utility like curl or wget to fetch the agent binary must exist in the provisioned infrastructure.\n\nExpose terminal or HTTP applications running in a workspace with:\n\n```hcl\nresource \"coder_app\" \"dev\" {\n agent_id = coder_agent.dev.id\n slug = \"my-app-name\"\n display_name = \"My App\"\n icon = \"https://my-app.com/icon.svg\"\n url = \"http://127.0.0.1:3000\"\n}\n```\n\nThis resource accepts the following properties:\n- agent_id: The ID of the agent to attach the app to.\n- slug: The slug of the app.\n- display_name: The displayed name of the app as it will appear in the UI.\n- icon: A URL to an icon to display in the UI.\n- url: An external url if external=true or a URL to be proxied to from inside the workspace. This should be of the form http://localhost:PORT[/SUBPATH]. Either command or url may be specified, but not both.\n- command: A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either command or url may be specified, but not both.\n- external: Whether this app is an external app. If true, the url will be opened in a new tab.\n\n\nThe Coder Server may not be authenticated with the infrastructure provider a user requests. In this scenario,\nthe user will need to provide credentials to the Coder Server before the workspace can be provisioned.\n\nHere are examples of provisioning the Coder Agent on specific infrastructure providers:\n\n\n// The agent is configured with \"aws-instance-identity\" auth.\nterraform {\n required_providers {\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n aws = {\n source = \"hashicorp/aws\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = false\n boundary = \"//\"\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${linux_user}\n\t// sudo: ALL=(ALL) NOPASSWD:ALL\n\t// shell: /bin/bash\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n hostname = local.hostname\n linux_user = local.linux_user\n })\n }\n\n part {\n filename = \"userdata.sh\"\n content_type = \"text/x-shellscript\"\n\n\t// Here is the content of the userdata.sh.tftpl file:\n\t// #!/bin/bash\n\t// sudo -u '${linux_user}' sh -c '${init_script}'\n content = templatefile(\"${path.module}/cloud-init/userdata.sh.tftpl\", {\n linux_user = local.linux_user\n\n init_script = try(coder_agent.dev[0].init_script, \"\")\n })\n }\n}\n\nresource \"aws_instance\" \"dev\" {\n ami = data.aws_ami.ubuntu.id\n availability_zone = \"${data.coder_parameter.region.value}a\"\n instance_type = data.coder_parameter.instance_type.value\n\n user_data = data.cloudinit_config.user_data.rendered\n tags = {\n Name = \"coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}\"\n }\n lifecycle {\n ignore_changes = [ami]\n }\n}\n\n\n\n// The agent is configured with \"google-instance-identity\" auth.\nterraform {\n required_providers {\n google = {\n source = \"hashicorp/google\"\n }\n }\n}\n\nresource \"google_compute_instance\" \"dev\" {\n zone = module.gcp_region.value\n count = data.coder_workspace.me.start_count\n name = \"coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-root\"\n machine_type = \"e2-medium\"\n network_interface {\n network = \"default\"\n access_config {\n // Ephemeral public IP\n }\n }\n boot_disk {\n auto_delete = false\n source = google_compute_disk.root.name\n }\n // In order to use google-instance-identity, a service account *must* be provided.\n service_account {\n email = data.google_compute_default_service_account.default.email\n scopes = [\"cloud-platform\"]\n }\n # ONLY FOR WINDOWS:\n # metadata = {\n # windows-startup-script-ps1 = coder_agent.main.init_script\n # }\n # The startup script runs as root with no $HOME environment set up, so instead of directly\n # running the agent init script, create a user (with a homedir, default shell and sudo\n # permissions) and execute the init script as that user.\n #\n # The agent MUST be started in here.\n metadata_startup_script = </dev/null 2>&1; then\n useradd -m -s /bin/bash \"${local.linux_user}\"\n echo \"${local.linux_user} ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/coder-user\nfi\n\nexec sudo -u \"${local.linux_user}\" sh -c '${coder_agent.main.init_script}'\nEOMETA\n}\n\n\n\n// The agent is configured with \"azure-instance-identity\" auth.\nterraform {\n required_providers {\n azurerm = {\n source = \"hashicorp/azurerm\"\n }\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = true\n\n boundary = \"//\"\n\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// bootcmd:\n\t// # work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117\n\t// - until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done\n\t// device_aliases:\n\t// homedir: /dev/disk/azure/scsi1/lun10\n\t// disk_setup:\n\t// homedir:\n\t// table_type: gpt\n\t// layout: true\n\t// fs_setup:\n\t// - label: coder_home\n\t// filesystem: ext4\n\t// device: homedir.1\n\t// mounts:\n\t// - [\"LABEL=coder_home\", \"/home/${username}\"]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${username}\n\t// sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n\t// groups: sudo\n\t// shell: /bin/bash\n\t// packages:\n\t// - git\n\t// write_files:\n\t// - path: /opt/coder/init\n\t// permissions: \"0755\"\n\t// encoding: b64\n\t// content: ${init_script}\n\t// - path: /etc/systemd/system/coder-agent.service\n\t// permissions: \"0644\"\n\t// content: |\n\t// [Unit]\n\t// Description=Coder Agent\n\t// After=network-online.target\n\t// Wants=network-online.target\n\n\t// [Service]\n\t// User=${username}\n\t// ExecStart=/opt/coder/init\n\t// Restart=always\n\t// RestartSec=10\n\t// TimeoutStopSec=90\n\t// KillMode=process\n\n\t// OOMScoreAdjust=-900\n\t// SyslogIdentifier=coder-agent\n\n\t// [Install]\n\t// WantedBy=multi-user.target\n\t// runcmd:\n\t// - chown ${username}:${username} /home/${username}\n\t// - systemctl enable coder-agent\n\t// - systemctl start coder-agent\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n username = \"coder\" # Ensure this user/group does not exist in your VM image\n init_script = base64encode(coder_agent.main.init_script)\n hostname = lower(data.coder_workspace.me.name)\n })\n }\n}\n\nresource \"azurerm_linux_virtual_machine\" \"main\" {\n count = data.coder_workspace.me.start_count\n name = \"vm\"\n resource_group_name = azurerm_resource_group.main.name\n location = azurerm_resource_group.main.location\n size = data.coder_parameter.instance_type.value\n // cloud-init overwrites this, so the value here doesn't matter\n admin_username = \"adminuser\"\n admin_ssh_key {\n public_key = tls_private_key.dummy.public_key_openssh\n username = \"adminuser\"\n }\n\n network_interface_ids = [\n azurerm_network_interface.main.id,\n ]\n computer_name = lower(data.coder_workspace.me.name)\n os_disk {\n caching = \"ReadWrite\"\n storage_account_type = \"Standard_LRS\"\n }\n source_image_reference {\n publisher = \"Canonical\"\n offer = \"0001-com-ubuntu-server-focal\"\n sku = \"20_04-lts-gen2\"\n version = \"latest\"\n }\n user_data = data.cloudinit_config.user_data.rendered\n}\n\n\n\nterraform {\n required_providers {\n coder = {\n source = \"kreuzwerker/docker\"\n }\n }\n}\n\n// The agent is configured with \"token\" auth.\n\nresource \"docker_container\" \"workspace\" {\n count = data.coder_workspace.me.start_count\n image = \"codercom/enterprise-base:ubuntu\"\n # Uses lower() to avoid Docker restriction on container names.\n name = \"coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}\"\n # Hostname makes the shell more user friendly: coder@my-workspace:~$\n hostname = data.coder_workspace.me.name\n # Use the docker gateway if the access URL is 127.0.0.1.\n entrypoint = [\"sh\", \"-c\", replace(coder_agent.main.init_script, \"/localhost|127\\\\.0\\\\.0\\\\.1/\", \"host.docker.internal\")]\n env = [\"CODER_AGENT_TOKEN=${coder_agent.main.token}\"]\n host {\n host = \"host.docker.internal\"\n ip = \"host-gateway\"\n }\n volumes {\n container_path = \"/home/coder\"\n volume_name = docker_volume.home_volume.name\n read_only = false\n }\n}\n\n\n\n// The agent is configured with \"token\" auth.\n\nresource \"kubernetes_deployment\" \"main\" {\n count = data.coder_workspace.me.start_count\n depends_on = [\n kubernetes_persistent_volume_claim.home\n ]\n wait_for_rollout = false\n metadata {\n name = \"coder-${data.coder_workspace.me.id}\"\n }\n\n spec {\n replicas = 1\n strategy {\n type = \"Recreate\"\n }\n\n template {\n spec {\n security_context {\n run_as_user = 1000\n fs_group = 1000\n run_as_non_root = true\n }\n\n container {\n name = \"dev\"\n image = \"codercom/enterprise-base:ubuntu\"\n image_pull_policy = \"Always\"\n command = [\"sh\", \"-c\", coder_agent.main.init_script]\n security_context {\n run_as_user = \"1000\"\n }\n env {\n name = \"CODER_AGENT_TOKEN\"\n value = coder_agent.main.token\n }\n }\n }\n }\n }\n}\n\n\nThe file_id provided is a reference to a tar file you have uploaded containing the Terraform.\n","name":"bmcp_coder_coder_create_template_version","parameters":{"properties":{"file_id":{"type":"string"},"template_id":{"type":"string"}},"required":["file_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace in Coder.\n\nIf a user is asking to \"test a template\", they are typically referring\nto creating a workspace from a template to ensure the infrastructure\nis provisioned correctly and the agent can connect to the control plane.\n\nBefore creating a workspace, always confirm the template choice with the user by:\n\n\t1. Listing the available templates that match their request.\n\t2. Recommending the most relevant option.\n\t2. Asking the user to confirm which template to use.\n\nIt is important to not create a workspace without confirming the template\nchoice with the user.\n\nAfter creating a workspace, watch the build logs and wait for the workspace to\nbe ready before trying to use or connect to the workspace.\n","name":"bmcp_coder_coder_create_workspace","parameters":{"properties":{"name":{"description":"Name of the workspace to create.","type":"string"},"rich_parameters":{"description":"Key/value pairs of rich parameters to pass to the template version to create the workspace.","type":"object"},"template_version_id":{"description":"ID of the template version to create the workspace from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a workspace. Omit or use the `me` keyword to create a workspace for the authenticated user.","type":"string"}},"required":["user","template_version_id","name","rich_parameters"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace build for an existing workspace. Use this to start, stop, or delete.\n\nAfter creating a workspace build, watch the build logs and wait for the\nworkspace build to complete before trying to start another build or use or\nconnect to the workspace.\n","name":"bmcp_coder_coder_create_workspace_build","parameters":{"properties":{"template_version_id":{"description":"(Optional) The template version ID to use for the workspace build. If not provided, the previously built version will be used.","type":"string"},"transition":{"description":"The transition to perform. Must be one of: start, stop, delete","enum":["start","stop","delete"],"type":"string"},"workspace_id":{"type":"string"}},"required":["workspace_id","transition"],"type":"object"},"strict":false},{"type":"function","description":"Delete a task.","name":"bmcp_coder_coder_delete_task","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to delete. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Delete a template. This is irreversible.","name":"bmcp_coder_coder_delete_template","parameters":{"properties":{"template_id":{"type":"string"}},"required":["template_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the currently authenticated user, similar to the `whoami` command.","name":"bmcp_coder_coder_get_authenticated_user","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a task.","name":"bmcp_coder_coder_get_task_logs","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to query. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the status of a task.","name":"bmcp_coder_coder_get_task_status","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to get. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a template version. This is useful to check whether a template version successfully imports or not.","name":"bmcp_coder_coder_get_template_version_logs","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Get a workspace by name or ID.\n\nThis returns more data than list_workspaces to reduce token usage.","name":"bmcp_coder_coder_get_workspace","parameters":{"properties":{"workspace_id":{"description":"The workspace ID or name in the format [owner/]workspace. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace agent.\n\n\t\tMore logs may appear after this call. It does not wait for the agent to finish.","name":"bmcp_coder_coder_get_workspace_agent_logs","parameters":{"properties":{"workspace_agent_id":{"type":"string"}},"required":["workspace_agent_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace build.\n\n\t\tUseful for checking whether a workspace builds successfully or not.","name":"bmcp_coder_coder_get_workspace_build_logs","parameters":{"properties":{"workspace_build_id":{"type":"string"}},"required":["workspace_build_id"],"type":"object"},"strict":false},{"type":"function","description":"List tasks.","name":"bmcp_coder_coder_list_tasks","parameters":{"properties":{"status":{"description":"Optional filter by task status.","type":"string"},"user":{"description":"Username or ID of the user for which to list tasks. Omit or use the `me` keyword to list tasks for the authenticated user.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Lists templates for the authenticated user.","name":"bmcp_coder_coder_list_templates","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Lists workspaces for the authenticated user.","name":"bmcp_coder_coder_list_workspaces","parameters":{"properties":{"owner":{"description":"The owner of the workspaces to list. Use \"me\" to list workspaces for the authenticated user. If you do not specify an owner, \"me\" will be assumed by default.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Send input to a running task.","name":"bmcp_coder_coder_send_task_input","parameters":{"properties":{"input":{"description":"The input to send to the task.","type":"string"},"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to prompt. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id","input"],"type":"object"},"strict":false},{"type":"function","description":"Get the parameters for a template version. You can refer to these as workspace parameters to the user, as they are typically important for creating a workspace.","name":"bmcp_coder_coder_template_version_parameters","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Update the active version of a template. This is helpful when iterating on templates.","name":"bmcp_coder_coder_update_template_active_version","parameters":{"properties":{"template_id":{"type":"string"},"template_version_id":{"type":"string"}},"required":["template_id","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create and upload a tar file by key/value mapping of file names to file contents. Use this to create template versions. Reference the tool description of \"create_template_version\" to understand template requirements.","name":"bmcp_coder_coder_upload_tar_file","parameters":{"properties":{"files":{"description":"A map of file names to file contents.","type":"object"}},"required":["files"],"type":"object"},"strict":false},{"type":"function","description":"Execute a bash command in a Coder workspace.\n\nThis tool provides the same functionality as the 'coder ssh ' CLI command.\nIt automatically starts the workspace if it's stopped and waits for the agent to be ready.\nThe output is trimmed of leading and trailing whitespace.\n\nThe workspace parameter supports various formats:\n- workspace (uses current user)\n- owner/workspace\n- owner--workspace\n- workspace.agent (specific agent)\n- owner/workspace.agent\n\nThe timeout_ms parameter specifies the command timeout in milliseconds (defaults to 60000ms, maximum of 300000ms).\nIf the command times out, all output captured up to that point is returned with a cancellation message.\n\nFor background commands (background: true), output is captured until the timeout is reached, then the command\ncontinues running in the background. The captured output is returned as the result.\n\nFor file operations (list, write, edit), always prefer the dedicated file tools.\nDo not use bash commands (ls, cat, echo, heredoc, etc.) to list, write, or read\nfiles when the file tools are available. The bash tool should be used for:\n\n\t- Running commands and scripts\n\t- Installing packages\n\t- Starting services\n\t- Executing programs\n\nExamples:\n- workspace: \"john/dev-env\", command: \"git status\", timeout_ms: 30000\n- workspace: \"my-workspace\", command: \"npm run dev\", background: true, timeout_ms: 10000\n- workspace: \"my-workspace.main\", command: \"docker ps\"","name":"bmcp_coder_coder_workspace_bash","parameters":{"properties":{"background":{"description":"Whether to run the command in the background. Output is captured until timeout, then the command continues running in the background.","type":"boolean"},"command":{"description":"The bash command to execute in the workspace.","type":"string"},"timeout_ms":{"default":60000,"description":"Command timeout in milliseconds. Defaults to 60000ms (60 seconds) if not specified.","minimum":1,"type":"integer"},"workspace":{"description":"The workspace name in format [owner/]workspace[.agent]. If owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","command"],"type":"object"},"strict":false},{"type":"function","description":"Edit a file in a workspace.","name":"bmcp_coder_coder_workspace_edit_file","parameters":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","edits"],"type":"object"},"strict":false},{"type":"function","description":"Edit one or more files in a workspace.","name":"bmcp_coder_coder_workspace_edit_files","parameters":{"properties":{"files":{"description":"An array of files to edit.","items":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"}},"required":["path","edits"],"type":"object"},"type":"array"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","files"],"type":"object"},"strict":false},{"type":"function","description":"List the URLs of Coder apps running in a workspace for a single agent.","name":"bmcp_coder_coder_workspace_list_apps","parameters":{"properties":{"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace"],"type":"object"},"strict":false},{"type":"function","description":"List directories in a workspace.","name":"bmcp_coder_coder_workspace_ls","parameters":{"properties":{"path":{"description":"The absolute path of the directory in the workspace to list.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Fetch URLs that forward to the specified port.","name":"bmcp_coder_coder_workspace_port_forward","parameters":{"properties":{"port":{"description":"The port to forward.","type":"number"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","port"],"type":"object"},"strict":false},{"type":"function","description":"Read from a file in a workspace.","name":"bmcp_coder_coder_workspace_read_file","parameters":{"properties":{"limit":{"description":"The number of bytes to read. Cannot exceed 1 MiB. Defaults to the full size of the file or 1 MiB, whichever is lower.","type":"integer"},"offset":{"description":"A byte offset indicating where in the file to start reading. Defaults to zero. An empty string indicates the end of the file has been reached.","type":"integer"},"path":{"description":"The absolute path of the file to read in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Write a file in a workspace.\n\nIf a file write fails due to syntax errors or encoding issues, do NOT switch\nto using bash commands as a workaround. Instead:\n\n\t1. Read the error message carefully to identify the issue\n\t2. Fix the content encoding/syntax\n\t3. Retry with this tool\n\nThe content parameter expects base64-encoded bytes. Ensure your source content\nis correct before encoding it. If you encounter errors, decode and verify the\ncontent you are trying to write, then re-encode it properly.\n","name":"bmcp_coder_coder_workspace_write_file","parameters":{"properties":{"content":{"description":"The base64-encoded bytes to write to the file.","type":"string"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","content"],"type":"object"},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":6463,"input_tokens_details":{"cached_tokens":6144},"output_tokens":182,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":6645},"user":null,"metadata":{}},"sequence_number":187}
+data: {"type":"response.completed","response":{"id":"resp_0bc5f54fce6df69a006972442175908194bb81d31f576e6ca6","object":"response","created_at":1769096225,"status":"completed","background":false,"completed_at":1769096230,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-mini-2025-04-14","output":[{"id":"msg_0bc5f54fce6df69a0069724421feb88194acb48ce194f3ee14","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"You have two Coder templates:\n\n1. Template Name: codex-test\n - Template ID: d85cac35-15a1-4bde-97d9-1f3e4b851246\n - Active Version ID: 22a3face-0c93-4b88-a63a-1ec1651e0199\n - Active User Count: 1\n\n2. Template Name: docker\n - Template ID: 7e799e56-6591-4c44-b575-3c72b55b7217\n - Active Version ID: 8057a565-1c12-489e-a563-8e8bb162c867\n - Active User Count: 1\n\nLet me know if you want more details or want to perform any actions with these templates."}],"role":"assistant"}],"parallel_tool_calls":false,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Create a task.","name":"bmcp_coder_coder_create_task","parameters":{"properties":{"input":{"description":"Input/prompt for the task.","type":"string"},"template_version_id":{"description":"ID of the template version to create the task from.","type":"string"},"template_version_preset_id":{"description":"Optional ID of the template version preset to create the task from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a task. Omit or use the `me` keyword to create a task for the authenticated user.","type":"string"}},"required":["input","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template in Coder. First, you must create a template version.","name":"bmcp_coder_coder_create_template","parameters":{"properties":{"description":{"type":"string"},"display_name":{"type":"string"},"icon":{"description":"A URL to an icon to use.","type":"string"},"name":{"type":"string"},"version_id":{"description":"The ID of the version to use.","type":"string"}},"required":["name","display_name","description","version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template version. This is a precursor to creating a template, or you can update an existing template.\n\nTemplates are Terraform defining a development environment. The provisioned infrastructure must run\nan Agent that connects to the Coder Control Plane to provide a rich experience.\n\nHere are some strict rules for creating a template version:\n- YOU MUST NOT use \"variable\" or \"output\" blocks in the Terraform code.\n- YOU MUST ALWAYS check template version logs after creation to ensure the template was imported successfully.\n\nWhen a template version is created, a Terraform Plan occurs that ensures the infrastructure\n_could_ be provisioned, but actual provisioning occurs when a workspace is created.\n\n\nThe Coder Terraform Provider can be imported like:\n\n```hcl\nterraform {\n required_providers {\n coder = {\n source = \"coder/coder\"\n }\n }\n}\n```\n\nA destroy does not occur when a user stops a workspace, but rather the transition changes:\n\n```hcl\ndata \"coder_workspace\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace.\n- name: The name of the workspace.\n- transition: Either \"start\" or \"stop\".\n- start_count: A computed count based on the transition field. If \"start\", this will be 1.\n\nAccess workspace owner information with:\n\n```hcl\ndata \"coder_workspace_owner\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace owner.\n- name: The name of the workspace owner.\n- full_name: The full name of the workspace owner.\n- email: The email of the workspace owner.\n- session_token: A token that can be used to authenticate the workspace owner. It is regenerated every time the workspace is started.\n- oidc_access_token: A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.\n\nParameters are defined in the template version. They are rendered in the UI on the workspace creation page:\n\n```hcl\nresource \"coder_parameter\" \"region\" {\n name = \"region\"\n type = \"string\"\n default = \"us-east-1\"\n}\n```\n\nThis resource accepts the following properties:\n- name: The name of the parameter.\n- default: The default value of the parameter.\n- type: The type of the parameter. Must be one of: \"string\", \"number\", \"bool\", or \"list(string)\".\n- display_name: The displayed name of the parameter as it will appear in the UI.\n- description: The description of the parameter as it will appear in the UI.\n- ephemeral: The value of an ephemeral parameter will not be preserved between consecutive workspace builds.\n- form_type: The type of this parameter. Must be one of: [radio, slider, input, dropdown, checkbox, switch, multi-select, tag-select, textarea, error].\n- icon: A URL to an icon to display in the UI.\n- mutable: Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution!\n- option: Each option block defines a value for a user to select from. (see below for nested schema)\n Required:\n - name: The name of the option.\n - value: The value of the option.\n Optional:\n - description: The description of the option as it will appear in the UI.\n - icon: A URL to an icon to display in the UI.\n\nA Workspace Agent runs on provisioned infrastructure to provide access to the workspace:\n\n```hcl\nresource \"coder_agent\" \"dev\" {\n arch = \"amd64\"\n os = \"linux\"\n}\n```\n\nThis resource accepts the following properties:\n- arch: The architecture of the agent. Must be one of: \"amd64\", \"arm64\", or \"armv7\".\n- os: The operating system of the agent. Must be one of: \"linux\", \"windows\", or \"darwin\".\n- auth: The authentication method for the agent. Must be one of: \"token\", \"google-instance-identity\", \"aws-instance-identity\", or \"azure-instance-identity\". It is insecure to pass the agent token via exposed variables to Virtual Machines. Instance Identity enables provisioned VMs to authenticate by instance ID on start.\n- dir: The starting directory when a user creates a shell session. Defaults to \"$HOME\".\n- env: A map of environment variables to set for the agent.\n- startup_script: A script to run after the agent starts. This script MUST exit eventually to signal that startup has completed. Use \"&\" or \"screen\" to run processes in the background.\n\nThis resource provides the following fields:\n- id: The UUID of the agent.\n- init_script: The script to run on provisioned infrastructure to fetch and start the agent.\n- token: Set the environment variable CODER_AGENT_TOKEN to this value to authenticate the agent.\n\nThe agent MUST be installed and started using the init_script. A utility like curl or wget to fetch the agent binary must exist in the provisioned infrastructure.\n\nExpose terminal or HTTP applications running in a workspace with:\n\n```hcl\nresource \"coder_app\" \"dev\" {\n agent_id = coder_agent.dev.id\n slug = \"my-app-name\"\n display_name = \"My App\"\n icon = \"https://my-app.com/icon.svg\"\n url = \"http://127.0.0.1:3000\"\n}\n```\n\nThis resource accepts the following properties:\n- agent_id: The ID of the agent to attach the app to.\n- slug: The slug of the app.\n- display_name: The displayed name of the app as it will appear in the UI.\n- icon: A URL to an icon to display in the UI.\n- url: An external url if external=true or a URL to be proxied to from inside the workspace. This should be of the form http://localhost:PORT[/SUBPATH]. Either command or url may be specified, but not both.\n- command: A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either command or url may be specified, but not both.\n- external: Whether this app is an external app. If true, the url will be opened in a new tab.\n\n\nThe Coder Server may not be authenticated with the infrastructure provider a user requests. In this scenario,\nthe user will need to provide credentials to the Coder Server before the workspace can be provisioned.\n\nHere are examples of provisioning the Coder Agent on specific infrastructure providers:\n\n\n// The agent is configured with \"aws-instance-identity\" auth.\nterraform {\n required_providers {\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n aws = {\n source = \"hashicorp/aws\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = false\n boundary = \"//\"\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${linux_user}\n\t// sudo: ALL=(ALL) NOPASSWD:ALL\n\t// shell: /bin/bash\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n hostname = local.hostname\n linux_user = local.linux_user\n })\n }\n\n part {\n filename = \"userdata.sh\"\n content_type = \"text/x-shellscript\"\n\n\t// Here is the content of the userdata.sh.tftpl file:\n\t// #!/bin/bash\n\t// sudo -u '${linux_user}' sh -c '${init_script}'\n content = templatefile(\"${path.module}/cloud-init/userdata.sh.tftpl\", {\n linux_user = local.linux_user\n\n init_script = try(coder_agent.dev[0].init_script, \"\")\n })\n }\n}\n\nresource \"aws_instance\" \"dev\" {\n ami = data.aws_ami.ubuntu.id\n availability_zone = \"${data.coder_parameter.region.value}a\"\n instance_type = data.coder_parameter.instance_type.value\n\n user_data = data.cloudinit_config.user_data.rendered\n tags = {\n Name = \"coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}\"\n }\n lifecycle {\n ignore_changes = [ami]\n }\n}\n\n\n\n// The agent is configured with \"google-instance-identity\" auth.\nterraform {\n required_providers {\n google = {\n source = \"hashicorp/google\"\n }\n }\n}\n\nresource \"google_compute_instance\" \"dev\" {\n zone = module.gcp_region.value\n count = data.coder_workspace.me.start_count\n name = \"coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-root\"\n machine_type = \"e2-medium\"\n network_interface {\n network = \"default\"\n access_config {\n // Ephemeral public IP\n }\n }\n boot_disk {\n auto_delete = false\n source = google_compute_disk.root.name\n }\n // In order to use google-instance-identity, a service account *must* be provided.\n service_account {\n email = data.google_compute_default_service_account.default.email\n scopes = [\"cloud-platform\"]\n }\n # ONLY FOR WINDOWS:\n # metadata = {\n # windows-startup-script-ps1 = coder_agent.main.init_script\n # }\n # The startup script runs as root with no $HOME environment set up, so instead of directly\n # running the agent init script, create a user (with a homedir, default shell and sudo\n # permissions) and execute the init script as that user.\n #\n # The agent MUST be started in here.\n metadata_startup_script = </dev/null 2>&1; then\n useradd -m -s /bin/bash \"${local.linux_user}\"\n echo \"${local.linux_user} ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/coder-user\nfi\n\nexec sudo -u \"${local.linux_user}\" sh -c '${coder_agent.main.init_script}'\nEOMETA\n}\n\n\n\n// The agent is configured with \"azure-instance-identity\" auth.\nterraform {\n required_providers {\n azurerm = {\n source = \"hashicorp/azurerm\"\n }\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = true\n\n boundary = \"//\"\n\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// bootcmd:\n\t// # work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117\n\t// - until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done\n\t// device_aliases:\n\t// homedir: /dev/disk/azure/scsi1/lun10\n\t// disk_setup:\n\t// homedir:\n\t// table_type: gpt\n\t// layout: true\n\t// fs_setup:\n\t// - label: coder_home\n\t// filesystem: ext4\n\t// device: homedir.1\n\t// mounts:\n\t// - [\"LABEL=coder_home\", \"/home/${username}\"]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${username}\n\t// sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n\t// groups: sudo\n\t// shell: /bin/bash\n\t// packages:\n\t// - git\n\t// write_files:\n\t// - path: /opt/coder/init\n\t// permissions: \"0755\"\n\t// encoding: b64\n\t// content: ${init_script}\n\t// - path: /etc/systemd/system/coder-agent.service\n\t// permissions: \"0644\"\n\t// content: |\n\t// [Unit]\n\t// Description=Coder Agent\n\t// After=network-online.target\n\t// Wants=network-online.target\n\n\t// [Service]\n\t// User=${username}\n\t// ExecStart=/opt/coder/init\n\t// Restart=always\n\t// RestartSec=10\n\t// TimeoutStopSec=90\n\t// KillMode=process\n\n\t// OOMScoreAdjust=-900\n\t// SyslogIdentifier=coder-agent\n\n\t// [Install]\n\t// WantedBy=multi-user.target\n\t// runcmd:\n\t// - chown ${username}:${username} /home/${username}\n\t// - systemctl enable coder-agent\n\t// - systemctl start coder-agent\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n username = \"coder\" # Ensure this user/group does not exist in your VM image\n init_script = base64encode(coder_agent.main.init_script)\n hostname = lower(data.coder_workspace.me.name)\n })\n }\n}\n\nresource \"azurerm_linux_virtual_machine\" \"main\" {\n count = data.coder_workspace.me.start_count\n name = \"vm\"\n resource_group_name = azurerm_resource_group.main.name\n location = azurerm_resource_group.main.location\n size = data.coder_parameter.instance_type.value\n // cloud-init overwrites this, so the value here doesn't matter\n admin_username = \"adminuser\"\n admin_ssh_key {\n public_key = tls_private_key.dummy.public_key_openssh\n username = \"adminuser\"\n }\n\n network_interface_ids = [\n azurerm_network_interface.main.id,\n ]\n computer_name = lower(data.coder_workspace.me.name)\n os_disk {\n caching = \"ReadWrite\"\n storage_account_type = \"Standard_LRS\"\n }\n source_image_reference {\n publisher = \"Canonical\"\n offer = \"0001-com-ubuntu-server-focal\"\n sku = \"20_04-lts-gen2\"\n version = \"latest\"\n }\n user_data = data.cloudinit_config.user_data.rendered\n}\n\n\n\nterraform {\n required_providers {\n coder = {\n source = \"kreuzwerker/docker\"\n }\n }\n}\n\n// The agent is configured with \"token\" auth.\n\nresource \"docker_container\" \"workspace\" {\n count = data.coder_workspace.me.start_count\n image = \"codercom/enterprise-base:ubuntu\"\n # Uses lower() to avoid Docker restriction on container names.\n name = \"coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}\"\n # Hostname makes the shell more user friendly: coder@my-workspace:~$\n hostname = data.coder_workspace.me.name\n # Use the docker gateway if the access URL is 127.0.0.1.\n entrypoint = [\"sh\", \"-c\", replace(coder_agent.main.init_script, \"/localhost|127\\\\.0\\\\.0\\\\.1/\", \"host.docker.internal\")]\n env = [\"CODER_AGENT_TOKEN=${coder_agent.main.token}\"]\n host {\n host = \"host.docker.internal\"\n ip = \"host-gateway\"\n }\n volumes {\n container_path = \"/home/coder\"\n volume_name = docker_volume.home_volume.name\n read_only = false\n }\n}\n\n\n\n// The agent is configured with \"token\" auth.\n\nresource \"kubernetes_deployment\" \"main\" {\n count = data.coder_workspace.me.start_count\n depends_on = [\n kubernetes_persistent_volume_claim.home\n ]\n wait_for_rollout = false\n metadata {\n name = \"coder-${data.coder_workspace.me.id}\"\n }\n\n spec {\n replicas = 1\n strategy {\n type = \"Recreate\"\n }\n\n template {\n spec {\n security_context {\n run_as_user = 1000\n fs_group = 1000\n run_as_non_root = true\n }\n\n container {\n name = \"dev\"\n image = \"codercom/enterprise-base:ubuntu\"\n image_pull_policy = \"Always\"\n command = [\"sh\", \"-c\", coder_agent.main.init_script]\n security_context {\n run_as_user = \"1000\"\n }\n env {\n name = \"CODER_AGENT_TOKEN\"\n value = coder_agent.main.token\n }\n }\n }\n }\n }\n}\n\n\nThe file_id provided is a reference to a tar file you have uploaded containing the Terraform.\n","name":"bmcp_coder_coder_create_template_version","parameters":{"properties":{"file_id":{"type":"string"},"template_id":{"type":"string"}},"required":["file_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace in Coder.\n\nIf a user is asking to \"test a template\", they are typically referring\nto creating a workspace from a template to ensure the infrastructure\nis provisioned correctly and the agent can connect to the control plane.\n\nBefore creating a workspace, always confirm the template choice with the user by:\n\n\t1. Listing the available templates that match their request.\n\t2. Recommending the most relevant option.\n\t2. Asking the user to confirm which template to use.\n\nIt is important to not create a workspace without confirming the template\nchoice with the user.\n\nAfter creating a workspace, watch the build logs and wait for the workspace to\nbe ready before trying to use or connect to the workspace.\n","name":"bmcp_coder_coder_create_workspace","parameters":{"properties":{"name":{"description":"Name of the workspace to create.","type":"string"},"rich_parameters":{"description":"Key/value pairs of rich parameters to pass to the template version to create the workspace.","type":"object"},"template_version_id":{"description":"ID of the template version to create the workspace from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a workspace. Omit or use the `me` keyword to create a workspace for the authenticated user.","type":"string"}},"required":["user","template_version_id","name","rich_parameters"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace build for an existing workspace. Use this to start, stop, or delete.\n\nAfter creating a workspace build, watch the build logs and wait for the\nworkspace build to complete before trying to start another build or use or\nconnect to the workspace.\n","name":"bmcp_coder_coder_create_workspace_build","parameters":{"properties":{"template_version_id":{"description":"(Optional) The template version ID to use for the workspace build. If not provided, the previously built version will be used.","type":"string"},"transition":{"description":"The transition to perform. Must be one of: start, stop, delete","enum":["start","stop","delete"],"type":"string"},"workspace_id":{"type":"string"}},"required":["workspace_id","transition"],"type":"object"},"strict":false},{"type":"function","description":"Delete a task.","name":"bmcp_coder_coder_delete_task","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to delete. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Delete a template. This is irreversible.","name":"bmcp_coder_coder_delete_template","parameters":{"properties":{"template_id":{"type":"string"}},"required":["template_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the currently authenticated user, similar to the `whoami` command.","name":"bmcp_coder_coder_get_authenticated_user","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a task.","name":"bmcp_coder_coder_get_task_logs","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to query. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the status of a task.","name":"bmcp_coder_coder_get_task_status","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to get. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a template version. This is useful to check whether a template version successfully imports or not.","name":"bmcp_coder_coder_get_template_version_logs","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Get a workspace by name or ID.\n\nThis returns more data than list_workspaces to reduce token usage.","name":"bmcp_coder_coder_get_workspace","parameters":{"properties":{"workspace_id":{"description":"The workspace ID or name in the format [owner/]workspace. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace agent.\n\n\t\tMore logs may appear after this call. It does not wait for the agent to finish.","name":"bmcp_coder_coder_get_workspace_agent_logs","parameters":{"properties":{"workspace_agent_id":{"type":"string"}},"required":["workspace_agent_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace build.\n\n\t\tUseful for checking whether a workspace builds successfully or not.","name":"bmcp_coder_coder_get_workspace_build_logs","parameters":{"properties":{"workspace_build_id":{"type":"string"}},"required":["workspace_build_id"],"type":"object"},"strict":false},{"type":"function","description":"List tasks.","name":"bmcp_coder_coder_list_tasks","parameters":{"properties":{"status":{"description":"Optional filter by task status.","type":"string"},"user":{"description":"Username or ID of the user for which to list tasks. Omit or use the `me` keyword to list tasks for the authenticated user.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Lists templates for the authenticated user.","name":"bmcp_coder_coder_list_templates","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Lists workspaces for the authenticated user.","name":"bmcp_coder_coder_list_workspaces","parameters":{"properties":{"owner":{"description":"The owner of the workspaces to list. Use \"me\" to list workspaces for the authenticated user. If you do not specify an owner, \"me\" will be assumed by default.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Send input to a running task.","name":"bmcp_coder_coder_send_task_input","parameters":{"properties":{"input":{"description":"The input to send to the task.","type":"string"},"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to prompt. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id","input"],"type":"object"},"strict":false},{"type":"function","description":"Get the parameters for a template version. You can refer to these as workspace parameters to the user, as they are typically important for creating a workspace.","name":"bmcp_coder_coder_template_version_parameters","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Update the active version of a template. This is helpful when iterating on templates.","name":"bmcp_coder_coder_update_template_active_version","parameters":{"properties":{"template_id":{"type":"string"},"template_version_id":{"type":"string"}},"required":["template_id","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create and upload a tar file by key/value mapping of file names to file contents. Use this to create template versions. Reference the tool description of \"create_template_version\" to understand template requirements.","name":"bmcp_coder_coder_upload_tar_file","parameters":{"properties":{"files":{"description":"A map of file names to file contents.","type":"object"}},"required":["files"],"type":"object"},"strict":false},{"type":"function","description":"Execute a bash command in a Coder workspace.\n\nThis tool provides the same functionality as the 'coder ssh ' CLI command.\nIt automatically starts the workspace if it's stopped and waits for the agent to be ready.\nThe output is trimmed of leading and trailing whitespace.\n\nThe workspace parameter supports various formats:\n- workspace (uses current user)\n- owner/workspace\n- owner--workspace\n- workspace.agent (specific agent)\n- owner/workspace.agent\n\nThe timeout_ms parameter specifies the command timeout in milliseconds (defaults to 60000ms, maximum of 300000ms).\nIf the command times out, all output captured up to that point is returned with a cancellation message.\n\nFor background commands (background: true), output is captured until the timeout is reached, then the command\ncontinues running in the background. The captured output is returned as the result.\n\nFor file operations (list, write, edit), always prefer the dedicated file tools.\nDo not use bash commands (ls, cat, echo, heredoc, etc.) to list, write, or read\nfiles when the file tools are available. The bash tool should be used for:\n\n\t- Running commands and scripts\n\t- Installing packages\n\t- Starting services\n\t- Executing programs\n\nExamples:\n- workspace: \"john/dev-env\", command: \"git status\", timeout_ms: 30000\n- workspace: \"my-workspace\", command: \"npm run dev\", background: true, timeout_ms: 10000\n- workspace: \"my-workspace.main\", command: \"docker ps\"","name":"bmcp_coder_coder_workspace_bash","parameters":{"properties":{"background":{"description":"Whether to run the command in the background. Output is captured until timeout, then the command continues running in the background.","type":"boolean"},"command":{"description":"The bash command to execute in the workspace.","type":"string"},"timeout_ms":{"default":60000,"description":"Command timeout in milliseconds. Defaults to 60000ms (60 seconds) if not specified.","minimum":1,"type":"integer"},"workspace":{"description":"The workspace name in format [owner/]workspace[.agent]. If owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","command"],"type":"object"},"strict":false},{"type":"function","description":"Edit a file in a workspace.","name":"bmcp_coder_coder_workspace_edit_file","parameters":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","edits"],"type":"object"},"strict":false},{"type":"function","description":"Edit one or more files in a workspace.","name":"bmcp_coder_coder_workspace_edit_files","parameters":{"properties":{"files":{"description":"An array of files to edit.","items":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"}},"required":["path","edits"],"type":"object"},"type":"array"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","files"],"type":"object"},"strict":false},{"type":"function","description":"List the URLs of Coder apps running in a workspace for a single agent.","name":"bmcp_coder_coder_workspace_list_apps","parameters":{"properties":{"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace"],"type":"object"},"strict":false},{"type":"function","description":"List directories in a workspace.","name":"bmcp_coder_coder_workspace_ls","parameters":{"properties":{"path":{"description":"The absolute path of the directory in the workspace to list.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Fetch URLs that forward to the specified port.","name":"bmcp_coder_coder_workspace_port_forward","parameters":{"properties":{"port":{"description":"The port to forward.","type":"number"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","port"],"type":"object"},"strict":false},{"type":"function","description":"Read from a file in a workspace.","name":"bmcp_coder_coder_workspace_read_file","parameters":{"properties":{"limit":{"description":"The number of bytes to read. Cannot exceed 1 MiB. Defaults to the full size of the file or 1 MiB, whichever is lower.","type":"integer"},"offset":{"description":"A byte offset indicating where in the file to start reading. Defaults to zero. An empty string indicates the end of the file has been reached.","type":"integer"},"path":{"description":"The absolute path of the file to read in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Write a file in a workspace.\n\nIf a file write fails due to syntax errors or encoding issues, do NOT switch\nto using bash commands as a workaround. Instead:\n\n\t1. Read the error message carefully to identify the issue\n\t2. Fix the content encoding/syntax\n\t3. Retry with this tool\n\nThe content parameter expects base64-encoded bytes. Ensure your source content\nis correct before encoding it. If you encounter errors, decode and verify the\ncontent you are trying to write, then re-encode it properly.\n","name":"bmcp_coder_coder_workspace_write_file","parameters":{"properties":{"content":{"description":"The base64-encoded bytes to write to the file.","type":"string"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","content"],"type":"object"},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":6463,"input_tokens_details":{"cached_tokens":6144,"cache_write_tokens":11},"output_tokens":182,"output_tokens_details":{"reasoning_tokens":5},"total_tokens":6645},"user":null,"metadata":{}},"sequence_number":187}
diff --git a/aibridge/fixtures/openai/responses/streaming/single_injected_tool_error.txtar b/aibridge/fixtures/openai/responses/streaming/single_injected_tool_error.txtar
index 95dd43e5433..a6ea96c8c3e 100644
--- a/aibridge/fixtures/openai/responses/streaming/single_injected_tool_error.txtar
+++ b/aibridge/fixtures/openai/responses/streaming/single_injected_tool_error.txtar
@@ -58,7 +58,7 @@ event: response.output_item.done
data: {"type":"response.output_item.done","item":{"id":"fc_0dfed48e1052ad7f0069725ca2cbac8193a79ff3716ec63dda","type":"function_call","status":"completed","arguments":"{\"transition\":\"start\",\"workspace_id\":\"non_existing_id\"}","call_id":"call_1wHAlwmnxtbUzowDJkmlcpJ4","name":"bmcp_coder_coder_create_workspace_build"},"output_index":0,"sequence_number":16}
event: response.completed
-data: {"type":"response.completed","response":{"id":"resp_0dfed48e1052ad7f0069725ca129f88193b97d6deff1760524","object":"response","created_at":1769102497,"status":"completed","background":false,"completed_at":1769102499,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","output":[{"id":"fc_0dfed48e1052ad7f0069725ca2cbac8193a79ff3716ec63dda","type":"function_call","status":"completed","arguments":"{\"transition\":\"start\",\"workspace_id\":\"non_existing_id\"}","call_id":"call_1wHAlwmnxtbUzowDJkmlcpJ4","name":"bmcp_coder_coder_create_workspace_build"}],"parallel_tool_calls":false,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Create a task.","name":"bmcp_coder_coder_create_task","parameters":{"properties":{"input":{"description":"Input/prompt for the task.","type":"string"},"template_version_id":{"description":"ID of the template version to create the task from.","type":"string"},"template_version_preset_id":{"description":"Optional ID of the template version preset to create the task from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a task. Omit or use the `me` keyword to create a task for the authenticated user.","type":"string"}},"required":["input","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template in Coder. First, you must create a template version.","name":"bmcp_coder_coder_create_template","parameters":{"properties":{"description":{"type":"string"},"display_name":{"type":"string"},"icon":{"description":"A URL to an icon to use.","type":"string"},"name":{"type":"string"},"version_id":{"description":"The ID of the version to use.","type":"string"}},"required":["name","display_name","description","version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template version. This is a precursor to creating a template, or you can update an existing template.\n\nTemplates are Terraform defining a development environment. The provisioned infrastructure must run\nan Agent that connects to the Coder Control Plane to provide a rich experience.\n\nHere are some strict rules for creating a template version:\n- YOU MUST NOT use \"variable\" or \"output\" blocks in the Terraform code.\n- YOU MUST ALWAYS check template version logs after creation to ensure the template was imported successfully.\n\nWhen a template version is created, a Terraform Plan occurs that ensures the infrastructure\n_could_ be provisioned, but actual provisioning occurs when a workspace is created.\n\n\nThe Coder Terraform Provider can be imported like:\n\n```hcl\nterraform {\n required_providers {\n coder = {\n source = \"coder/coder\"\n }\n }\n}\n```\n\nA destroy does not occur when a user stops a workspace, but rather the transition changes:\n\n```hcl\ndata \"coder_workspace\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace.\n- name: The name of the workspace.\n- transition: Either \"start\" or \"stop\".\n- start_count: A computed count based on the transition field. If \"start\", this will be 1.\n\nAccess workspace owner information with:\n\n```hcl\ndata \"coder_workspace_owner\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace owner.\n- name: The name of the workspace owner.\n- full_name: The full name of the workspace owner.\n- email: The email of the workspace owner.\n- session_token: A token that can be used to authenticate the workspace owner. It is regenerated every time the workspace is started.\n- oidc_access_token: A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.\n\nParameters are defined in the template version. They are rendered in the UI on the workspace creation page:\n\n```hcl\nresource \"coder_parameter\" \"region\" {\n name = \"region\"\n type = \"string\"\n default = \"us-east-1\"\n}\n```\n\nThis resource accepts the following properties:\n- name: The name of the parameter.\n- default: The default value of the parameter.\n- type: The type of the parameter. Must be one of: \"string\", \"number\", \"bool\", or \"list(string)\".\n- display_name: The displayed name of the parameter as it will appear in the UI.\n- description: The description of the parameter as it will appear in the UI.\n- ephemeral: The value of an ephemeral parameter will not be preserved between consecutive workspace builds.\n- form_type: The type of this parameter. Must be one of: [radio, slider, input, dropdown, checkbox, switch, multi-select, tag-select, textarea, error].\n- icon: A URL to an icon to display in the UI.\n- mutable: Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution!\n- option: Each option block defines a value for a user to select from. (see below for nested schema)\n Required:\n - name: The name of the option.\n - value: The value of the option.\n Optional:\n - description: The description of the option as it will appear in the UI.\n - icon: A URL to an icon to display in the UI.\n\nA Workspace Agent runs on provisioned infrastructure to provide access to the workspace:\n\n```hcl\nresource \"coder_agent\" \"dev\" {\n arch = \"amd64\"\n os = \"linux\"\n}\n```\n\nThis resource accepts the following properties:\n- arch: The architecture of the agent. Must be one of: \"amd64\", \"arm64\", or \"armv7\".\n- os: The operating system of the agent. Must be one of: \"linux\", \"windows\", or \"darwin\".\n- auth: The authentication method for the agent. Must be one of: \"token\", \"google-instance-identity\", \"aws-instance-identity\", or \"azure-instance-identity\". It is insecure to pass the agent token via exposed variables to Virtual Machines. Instance Identity enables provisioned VMs to authenticate by instance ID on start.\n- dir: The starting directory when a user creates a shell session. Defaults to \"$HOME\".\n- env: A map of environment variables to set for the agent.\n- startup_script: A script to run after the agent starts. This script MUST exit eventually to signal that startup has completed. Use \"&\" or \"screen\" to run processes in the background.\n\nThis resource provides the following fields:\n- id: The UUID of the agent.\n- init_script: The script to run on provisioned infrastructure to fetch and start the agent.\n- token: Set the environment variable CODER_AGENT_TOKEN to this value to authenticate the agent.\n\nThe agent MUST be installed and started using the init_script. A utility like curl or wget to fetch the agent binary must exist in the provisioned infrastructure.\n\nExpose terminal or HTTP applications running in a workspace with:\n\n```hcl\nresource \"coder_app\" \"dev\" {\n agent_id = coder_agent.dev.id\n slug = \"my-app-name\"\n display_name = \"My App\"\n icon = \"https://my-app.com/icon.svg\"\n url = \"http://127.0.0.1:3000\"\n}\n```\n\nThis resource accepts the following properties:\n- agent_id: The ID of the agent to attach the app to.\n- slug: The slug of the app.\n- display_name: The displayed name of the app as it will appear in the UI.\n- icon: A URL to an icon to display in the UI.\n- url: An external url if external=true or a URL to be proxied to from inside the workspace. This should be of the form http://localhost:PORT[/SUBPATH]. Either command or url may be specified, but not both.\n- command: A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either command or url may be specified, but not both.\n- external: Whether this app is an external app. If true, the url will be opened in a new tab.\n\n\nThe Coder Server may not be authenticated with the infrastructure provider a user requests. In this scenario,\nthe user will need to provide credentials to the Coder Server before the workspace can be provisioned.\n\nHere are examples of provisioning the Coder Agent on specific infrastructure providers:\n\n\n// The agent is configured with \"aws-instance-identity\" auth.\nterraform {\n required_providers {\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n aws = {\n source = \"hashicorp/aws\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = false\n boundary = \"//\"\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${linux_user}\n\t// sudo: ALL=(ALL) NOPASSWD:ALL\n\t// shell: /bin/bash\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n hostname = local.hostname\n linux_user = local.linux_user\n })\n }\n\n part {\n filename = \"userdata.sh\"\n content_type = \"text/x-shellscript\"\n\n\t// Here is the content of the userdata.sh.tftpl file:\n\t// #!/bin/bash\n\t// sudo -u '${linux_user}' sh -c '${init_script}'\n content = templatefile(\"${path.module}/cloud-init/userdata.sh.tftpl\", {\n linux_user = local.linux_user\n\n init_script = try(coder_agent.dev[0].init_script, \"\")\n })\n }\n}\n\nresource \"aws_instance\" \"dev\" {\n ami = data.aws_ami.ubuntu.id\n availability_zone = \"${data.coder_parameter.region.value}a\"\n instance_type = data.coder_parameter.instance_type.value\n\n user_data = data.cloudinit_config.user_data.rendered\n tags = {\n Name = \"coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}\"\n }\n lifecycle {\n ignore_changes = [ami]\n }\n}\n\n\n\n// The agent is configured with \"google-instance-identity\" auth.\nterraform {\n required_providers {\n google = {\n source = \"hashicorp/google\"\n }\n }\n}\n\nresource \"google_compute_instance\" \"dev\" {\n zone = module.gcp_region.value\n count = data.coder_workspace.me.start_count\n name = \"coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-root\"\n machine_type = \"e2-medium\"\n network_interface {\n network = \"default\"\n access_config {\n // Ephemeral public IP\n }\n }\n boot_disk {\n auto_delete = false\n source = google_compute_disk.root.name\n }\n // In order to use google-instance-identity, a service account *must* be provided.\n service_account {\n email = data.google_compute_default_service_account.default.email\n scopes = [\"cloud-platform\"]\n }\n # ONLY FOR WINDOWS:\n # metadata = {\n # windows-startup-script-ps1 = coder_agent.main.init_script\n # }\n # The startup script runs as root with no $HOME environment set up, so instead of directly\n # running the agent init script, create a user (with a homedir, default shell and sudo\n # permissions) and execute the init script as that user.\n #\n # The agent MUST be started in here.\n metadata_startup_script = </dev/null 2>&1; then\n useradd -m -s /bin/bash \"${local.linux_user}\"\n echo \"${local.linux_user} ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/coder-user\nfi\n\nexec sudo -u \"${local.linux_user}\" sh -c '${coder_agent.main.init_script}'\nEOMETA\n}\n\n\n\n// The agent is configured with \"azure-instance-identity\" auth.\nterraform {\n required_providers {\n azurerm = {\n source = \"hashicorp/azurerm\"\n }\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = true\n\n boundary = \"//\"\n\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// bootcmd:\n\t// # work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117\n\t// - until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done\n\t// device_aliases:\n\t// homedir: /dev/disk/azure/scsi1/lun10\n\t// disk_setup:\n\t// homedir:\n\t// table_type: gpt\n\t// layout: true\n\t// fs_setup:\n\t// - label: coder_home\n\t// filesystem: ext4\n\t// device: homedir.1\n\t// mounts:\n\t// - [\"LABEL=coder_home\", \"/home/${username}\"]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${username}\n\t// sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n\t// groups: sudo\n\t// shell: /bin/bash\n\t// packages:\n\t// - git\n\t// write_files:\n\t// - path: /opt/coder/init\n\t// permissions: \"0755\"\n\t// encoding: b64\n\t// content: ${init_script}\n\t// - path: /etc/systemd/system/coder-agent.service\n\t// permissions: \"0644\"\n\t// content: |\n\t// [Unit]\n\t// Description=Coder Agent\n\t// After=network-online.target\n\t// Wants=network-online.target\n\n\t// [Service]\n\t// User=${username}\n\t// ExecStart=/opt/coder/init\n\t// Restart=always\n\t// RestartSec=10\n\t// TimeoutStopSec=90\n\t// KillMode=process\n\n\t// OOMScoreAdjust=-900\n\t// SyslogIdentifier=coder-agent\n\n\t// [Install]\n\t// WantedBy=multi-user.target\n\t// runcmd:\n\t// - chown ${username}:${username} /home/${username}\n\t// - systemctl enable coder-agent\n\t// - systemctl start coder-agent\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n username = \"coder\" # Ensure this user/group does not exist in your VM image\n init_script = base64encode(coder_agent.main.init_script)\n hostname = lower(data.coder_workspace.me.name)\n })\n }\n}\n\nresource \"azurerm_linux_virtual_machine\" \"main\" {\n count = data.coder_workspace.me.start_count\n name = \"vm\"\n resource_group_name = azurerm_resource_group.main.name\n location = azurerm_resource_group.main.location\n size = data.coder_parameter.instance_type.value\n // cloud-init overwrites this, so the value here doesn't matter\n admin_username = \"adminuser\"\n admin_ssh_key {\n public_key = tls_private_key.dummy.public_key_openssh\n username = \"adminuser\"\n }\n\n network_interface_ids = [\n azurerm_network_interface.main.id,\n ]\n computer_name = lower(data.coder_workspace.me.name)\n os_disk {\n caching = \"ReadWrite\"\n storage_account_type = \"Standard_LRS\"\n }\n source_image_reference {\n publisher = \"Canonical\"\n offer = \"0001-com-ubuntu-server-focal\"\n sku = \"20_04-lts-gen2\"\n version = \"latest\"\n }\n user_data = data.cloudinit_config.user_data.rendered\n}\n\n\n\nterraform {\n required_providers {\n coder = {\n source = \"kreuzwerker/docker\"\n }\n }\n}\n\n// The agent is configured with \"token\" auth.\n\nresource \"docker_container\" \"workspace\" {\n count = data.coder_workspace.me.start_count\n image = \"codercom/enterprise-base:ubuntu\"\n # Uses lower() to avoid Docker restriction on container names.\n name = \"coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}\"\n # Hostname makes the shell more user friendly: coder@my-workspace:~$\n hostname = data.coder_workspace.me.name\n # Use the docker gateway if the access URL is 127.0.0.1.\n entrypoint = [\"sh\", \"-c\", replace(coder_agent.main.init_script, \"/localhost|127\\\\.0\\\\.0\\\\.1/\", \"host.docker.internal\")]\n env = [\"CODER_AGENT_TOKEN=${coder_agent.main.token}\"]\n host {\n host = \"host.docker.internal\"\n ip = \"host-gateway\"\n }\n volumes {\n container_path = \"/home/coder\"\n volume_name = docker_volume.home_volume.name\n read_only = false\n }\n}\n\n\n\n// The agent is configured with \"token\" auth.\n\nresource \"kubernetes_deployment\" \"main\" {\n count = data.coder_workspace.me.start_count\n depends_on = [\n kubernetes_persistent_volume_claim.home\n ]\n wait_for_rollout = false\n metadata {\n name = \"coder-${data.coder_workspace.me.id}\"\n }\n\n spec {\n replicas = 1\n strategy {\n type = \"Recreate\"\n }\n\n template {\n spec {\n security_context {\n run_as_user = 1000\n fs_group = 1000\n run_as_non_root = true\n }\n\n container {\n name = \"dev\"\n image = \"codercom/enterprise-base:ubuntu\"\n image_pull_policy = \"Always\"\n command = [\"sh\", \"-c\", coder_agent.main.init_script]\n security_context {\n run_as_user = \"1000\"\n }\n env {\n name = \"CODER_AGENT_TOKEN\"\n value = coder_agent.main.token\n }\n }\n }\n }\n }\n}\n\n\nThe file_id provided is a reference to a tar file you have uploaded containing the Terraform.\n","name":"bmcp_coder_coder_create_template_version","parameters":{"properties":{"file_id":{"type":"string"},"template_id":{"type":"string"}},"required":["file_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace in Coder.\n\nIf a user is asking to \"test a template\", they are typically referring\nto creating a workspace from a template to ensure the infrastructure\nis provisioned correctly and the agent can connect to the control plane.\n\nBefore creating a workspace, always confirm the template choice with the user by:\n\n\t1. Listing the available templates that match their request.\n\t2. Recommending the most relevant option.\n\t2. Asking the user to confirm which template to use.\n\nIt is important to not create a workspace without confirming the template\nchoice with the user.\n\nAfter creating a workspace, watch the build logs and wait for the workspace to\nbe ready before trying to use or connect to the workspace.\n","name":"bmcp_coder_coder_create_workspace","parameters":{"properties":{"name":{"description":"Name of the workspace to create.","type":"string"},"rich_parameters":{"description":"Key/value pairs of rich parameters to pass to the template version to create the workspace.","type":"object"},"template_version_id":{"description":"ID of the template version to create the workspace from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a workspace. Omit or use the `me` keyword to create a workspace for the authenticated user.","type":"string"}},"required":["user","template_version_id","name","rich_parameters"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace build for an existing workspace. Use this to start, stop, or delete.\n\nAfter creating a workspace build, watch the build logs and wait for the\nworkspace build to complete before trying to start another build or use or\nconnect to the workspace.\n","name":"bmcp_coder_coder_create_workspace_build","parameters":{"properties":{"template_version_id":{"description":"(Optional) The template version ID to use for the workspace build. If not provided, the previously built version will be used.","type":"string"},"transition":{"description":"The transition to perform. Must be one of: start, stop, delete","enum":["start","stop","delete"],"type":"string"},"workspace_id":{"type":"string"}},"required":["workspace_id","transition"],"type":"object"},"strict":false},{"type":"function","description":"Delete a task.","name":"bmcp_coder_coder_delete_task","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to delete. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Delete a template. This is irreversible.","name":"bmcp_coder_coder_delete_template","parameters":{"properties":{"template_id":{"type":"string"}},"required":["template_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the currently authenticated user, similar to the `whoami` command.","name":"bmcp_coder_coder_get_authenticated_user","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a task.","name":"bmcp_coder_coder_get_task_logs","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to query. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the status of a task.","name":"bmcp_coder_coder_get_task_status","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to get. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a template version. This is useful to check whether a template version successfully imports or not.","name":"bmcp_coder_coder_get_template_version_logs","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Get a workspace by name or ID.\n\nThis returns more data than list_workspaces to reduce token usage.","name":"bmcp_coder_coder_get_workspace","parameters":{"properties":{"workspace_id":{"description":"The workspace ID or name in the format [owner/]workspace. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace agent.\n\n\t\tMore logs may appear after this call. It does not wait for the agent to finish.","name":"bmcp_coder_coder_get_workspace_agent_logs","parameters":{"properties":{"workspace_agent_id":{"type":"string"}},"required":["workspace_agent_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace build.\n\n\t\tUseful for checking whether a workspace builds successfully or not.","name":"bmcp_coder_coder_get_workspace_build_logs","parameters":{"properties":{"workspace_build_id":{"type":"string"}},"required":["workspace_build_id"],"type":"object"},"strict":false},{"type":"function","description":"List tasks.","name":"bmcp_coder_coder_list_tasks","parameters":{"properties":{"status":{"description":"Optional filter by task status.","type":"string"},"user":{"description":"Username or ID of the user for which to list tasks. Omit or use the `me` keyword to list tasks for the authenticated user.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Lists templates for the authenticated user.","name":"bmcp_coder_coder_list_templates","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Lists workspaces for the authenticated user.","name":"bmcp_coder_coder_list_workspaces","parameters":{"properties":{"owner":{"description":"The owner of the workspaces to list. Use \"me\" to list workspaces for the authenticated user. If you do not specify an owner, \"me\" will be assumed by default.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Send input to a running task.","name":"bmcp_coder_coder_send_task_input","parameters":{"properties":{"input":{"description":"The input to send to the task.","type":"string"},"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to prompt. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id","input"],"type":"object"},"strict":false},{"type":"function","description":"Get the parameters for a template version. You can refer to these as workspace parameters to the user, as they are typically important for creating a workspace.","name":"bmcp_coder_coder_template_version_parameters","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Update the active version of a template. This is helpful when iterating on templates.","name":"bmcp_coder_coder_update_template_active_version","parameters":{"properties":{"template_id":{"type":"string"},"template_version_id":{"type":"string"}},"required":["template_id","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create and upload a tar file by key/value mapping of file names to file contents. Use this to create template versions. Reference the tool description of \"create_template_version\" to understand template requirements.","name":"bmcp_coder_coder_upload_tar_file","parameters":{"properties":{"files":{"description":"A map of file names to file contents.","type":"object"}},"required":["files"],"type":"object"},"strict":false},{"type":"function","description":"Execute a bash command in a Coder workspace.\n\nThis tool provides the same functionality as the 'coder ssh ' CLI command.\nIt automatically starts the workspace if it's stopped and waits for the agent to be ready.\nThe output is trimmed of leading and trailing whitespace.\n\nThe workspace parameter supports various formats:\n- workspace (uses current user)\n- owner/workspace\n- owner--workspace\n- workspace.agent (specific agent)\n- owner/workspace.agent\n\nThe timeout_ms parameter specifies the command timeout in milliseconds (defaults to 60000ms, maximum of 300000ms).\nIf the command times out, all output captured up to that point is returned with a cancellation message.\n\nFor background commands (background: true), output is captured until the timeout is reached, then the command\ncontinues running in the background. The captured output is returned as the result.\n\nFor file operations (list, write, edit), always prefer the dedicated file tools.\nDo not use bash commands (ls, cat, echo, heredoc, etc.) to list, write, or read\nfiles when the file tools are available. The bash tool should be used for:\n\n\t- Running commands and scripts\n\t- Installing packages\n\t- Starting services\n\t- Executing programs\n\nExamples:\n- workspace: \"john/dev-env\", command: \"git status\", timeout_ms: 30000\n- workspace: \"my-workspace\", command: \"npm run dev\", background: true, timeout_ms: 10000\n- workspace: \"my-workspace.main\", command: \"docker ps\"","name":"bmcp_coder_coder_workspace_bash","parameters":{"properties":{"background":{"description":"Whether to run the command in the background. Output is captured until timeout, then the command continues running in the background.","type":"boolean"},"command":{"description":"The bash command to execute in the workspace.","type":"string"},"timeout_ms":{"default":60000,"description":"Command timeout in milliseconds. Defaults to 60000ms (60 seconds) if not specified.","minimum":1,"type":"integer"},"workspace":{"description":"The workspace name in format [owner/]workspace[.agent]. If owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","command"],"type":"object"},"strict":false},{"type":"function","description":"Edit a file in a workspace.","name":"bmcp_coder_coder_workspace_edit_file","parameters":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","edits"],"type":"object"},"strict":false},{"type":"function","description":"Edit one or more files in a workspace.","name":"bmcp_coder_coder_workspace_edit_files","parameters":{"properties":{"files":{"description":"An array of files to edit.","items":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"}},"required":["path","edits"],"type":"object"},"type":"array"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","files"],"type":"object"},"strict":false},{"type":"function","description":"List the URLs of Coder apps running in a workspace for a single agent.","name":"bmcp_coder_coder_workspace_list_apps","parameters":{"properties":{"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace"],"type":"object"},"strict":false},{"type":"function","description":"List directories in a workspace.","name":"bmcp_coder_coder_workspace_ls","parameters":{"properties":{"path":{"description":"The absolute path of the directory in the workspace to list.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Fetch URLs that forward to the specified port.","name":"bmcp_coder_coder_workspace_port_forward","parameters":{"properties":{"port":{"description":"The port to forward.","type":"number"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","port"],"type":"object"},"strict":false},{"type":"function","description":"Read from a file in a workspace.","name":"bmcp_coder_coder_workspace_read_file","parameters":{"properties":{"limit":{"description":"The number of bytes to read. Cannot exceed 1 MiB. Defaults to the full size of the file or 1 MiB, whichever is lower.","type":"integer"},"offset":{"description":"A byte offset indicating where in the file to start reading. Defaults to zero. An empty string indicates the end of the file has been reached.","type":"integer"},"path":{"description":"The absolute path of the file to read in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Write a file in a workspace.\n\nIf a file write fails due to syntax errors or encoding issues, do NOT switch\nto using bash commands as a workaround. Instead:\n\n\t1. Read the error message carefully to identify the issue\n\t2. Fix the content encoding/syntax\n\t3. Retry with this tool\n\nThe content parameter expects base64-encoded bytes. Ensure your source content\nis correct before encoding it. If you encounter errors, decode and verify the\ncontent you are trying to write, then re-encode it properly.\n","name":"bmcp_coder_coder_workspace_write_file","parameters":{"properties":{"content":{"description":"The base64-encoded bytes to write to the file.","type":"string"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","content"],"type":"object"},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":6280,"input_tokens_details":{"cached_tokens":0},"output_tokens":30,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":6310},"user":null,"metadata":{}},"sequence_number":17}
+data: {"type":"response.completed","response":{"id":"resp_0dfed48e1052ad7f0069725ca129f88193b97d6deff1760524","object":"response","created_at":1769102497,"status":"completed","background":false,"completed_at":1769102499,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","output":[{"id":"fc_0dfed48e1052ad7f0069725ca2cbac8193a79ff3716ec63dda","type":"function_call","status":"completed","arguments":"{\"transition\":\"start\",\"workspace_id\":\"non_existing_id\"}","call_id":"call_1wHAlwmnxtbUzowDJkmlcpJ4","name":"bmcp_coder_coder_create_workspace_build"}],"parallel_tool_calls":false,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Create a task.","name":"bmcp_coder_coder_create_task","parameters":{"properties":{"input":{"description":"Input/prompt for the task.","type":"string"},"template_version_id":{"description":"ID of the template version to create the task from.","type":"string"},"template_version_preset_id":{"description":"Optional ID of the template version preset to create the task from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a task. Omit or use the `me` keyword to create a task for the authenticated user.","type":"string"}},"required":["input","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template in Coder. First, you must create a template version.","name":"bmcp_coder_coder_create_template","parameters":{"properties":{"description":{"type":"string"},"display_name":{"type":"string"},"icon":{"description":"A URL to an icon to use.","type":"string"},"name":{"type":"string"},"version_id":{"description":"The ID of the version to use.","type":"string"}},"required":["name","display_name","description","version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template version. This is a precursor to creating a template, or you can update an existing template.\n\nTemplates are Terraform defining a development environment. The provisioned infrastructure must run\nan Agent that connects to the Coder Control Plane to provide a rich experience.\n\nHere are some strict rules for creating a template version:\n- YOU MUST NOT use \"variable\" or \"output\" blocks in the Terraform code.\n- YOU MUST ALWAYS check template version logs after creation to ensure the template was imported successfully.\n\nWhen a template version is created, a Terraform Plan occurs that ensures the infrastructure\n_could_ be provisioned, but actual provisioning occurs when a workspace is created.\n\n\nThe Coder Terraform Provider can be imported like:\n\n```hcl\nterraform {\n required_providers {\n coder = {\n source = \"coder/coder\"\n }\n }\n}\n```\n\nA destroy does not occur when a user stops a workspace, but rather the transition changes:\n\n```hcl\ndata \"coder_workspace\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace.\n- name: The name of the workspace.\n- transition: Either \"start\" or \"stop\".\n- start_count: A computed count based on the transition field. If \"start\", this will be 1.\n\nAccess workspace owner information with:\n\n```hcl\ndata \"coder_workspace_owner\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace owner.\n- name: The name of the workspace owner.\n- full_name: The full name of the workspace owner.\n- email: The email of the workspace owner.\n- session_token: A token that can be used to authenticate the workspace owner. It is regenerated every time the workspace is started.\n- oidc_access_token: A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.\n\nParameters are defined in the template version. They are rendered in the UI on the workspace creation page:\n\n```hcl\nresource \"coder_parameter\" \"region\" {\n name = \"region\"\n type = \"string\"\n default = \"us-east-1\"\n}\n```\n\nThis resource accepts the following properties:\n- name: The name of the parameter.\n- default: The default value of the parameter.\n- type: The type of the parameter. Must be one of: \"string\", \"number\", \"bool\", or \"list(string)\".\n- display_name: The displayed name of the parameter as it will appear in the UI.\n- description: The description of the parameter as it will appear in the UI.\n- ephemeral: The value of an ephemeral parameter will not be preserved between consecutive workspace builds.\n- form_type: The type of this parameter. Must be one of: [radio, slider, input, dropdown, checkbox, switch, multi-select, tag-select, textarea, error].\n- icon: A URL to an icon to display in the UI.\n- mutable: Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution!\n- option: Each option block defines a value for a user to select from. (see below for nested schema)\n Required:\n - name: The name of the option.\n - value: The value of the option.\n Optional:\n - description: The description of the option as it will appear in the UI.\n - icon: A URL to an icon to display in the UI.\n\nA Workspace Agent runs on provisioned infrastructure to provide access to the workspace:\n\n```hcl\nresource \"coder_agent\" \"dev\" {\n arch = \"amd64\"\n os = \"linux\"\n}\n```\n\nThis resource accepts the following properties:\n- arch: The architecture of the agent. Must be one of: \"amd64\", \"arm64\", or \"armv7\".\n- os: The operating system of the agent. Must be one of: \"linux\", \"windows\", or \"darwin\".\n- auth: The authentication method for the agent. Must be one of: \"token\", \"google-instance-identity\", \"aws-instance-identity\", or \"azure-instance-identity\". It is insecure to pass the agent token via exposed variables to Virtual Machines. Instance Identity enables provisioned VMs to authenticate by instance ID on start.\n- dir: The starting directory when a user creates a shell session. Defaults to \"$HOME\".\n- env: A map of environment variables to set for the agent.\n- startup_script: A script to run after the agent starts. This script MUST exit eventually to signal that startup has completed. Use \"&\" or \"screen\" to run processes in the background.\n\nThis resource provides the following fields:\n- id: The UUID of the agent.\n- init_script: The script to run on provisioned infrastructure to fetch and start the agent.\n- token: Set the environment variable CODER_AGENT_TOKEN to this value to authenticate the agent.\n\nThe agent MUST be installed and started using the init_script. A utility like curl or wget to fetch the agent binary must exist in the provisioned infrastructure.\n\nExpose terminal or HTTP applications running in a workspace with:\n\n```hcl\nresource \"coder_app\" \"dev\" {\n agent_id = coder_agent.dev.id\n slug = \"my-app-name\"\n display_name = \"My App\"\n icon = \"https://my-app.com/icon.svg\"\n url = \"http://127.0.0.1:3000\"\n}\n```\n\nThis resource accepts the following properties:\n- agent_id: The ID of the agent to attach the app to.\n- slug: The slug of the app.\n- display_name: The displayed name of the app as it will appear in the UI.\n- icon: A URL to an icon to display in the UI.\n- url: An external url if external=true or a URL to be proxied to from inside the workspace. This should be of the form http://localhost:PORT[/SUBPATH]. Either command or url may be specified, but not both.\n- command: A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either command or url may be specified, but not both.\n- external: Whether this app is an external app. If true, the url will be opened in a new tab.\n\n\nThe Coder Server may not be authenticated with the infrastructure provider a user requests. In this scenario,\nthe user will need to provide credentials to the Coder Server before the workspace can be provisioned.\n\nHere are examples of provisioning the Coder Agent on specific infrastructure providers:\n\n\n// The agent is configured with \"aws-instance-identity\" auth.\nterraform {\n required_providers {\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n aws = {\n source = \"hashicorp/aws\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = false\n boundary = \"//\"\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${linux_user}\n\t// sudo: ALL=(ALL) NOPASSWD:ALL\n\t// shell: /bin/bash\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n hostname = local.hostname\n linux_user = local.linux_user\n })\n }\n\n part {\n filename = \"userdata.sh\"\n content_type = \"text/x-shellscript\"\n\n\t// Here is the content of the userdata.sh.tftpl file:\n\t// #!/bin/bash\n\t// sudo -u '${linux_user}' sh -c '${init_script}'\n content = templatefile(\"${path.module}/cloud-init/userdata.sh.tftpl\", {\n linux_user = local.linux_user\n\n init_script = try(coder_agent.dev[0].init_script, \"\")\n })\n }\n}\n\nresource \"aws_instance\" \"dev\" {\n ami = data.aws_ami.ubuntu.id\n availability_zone = \"${data.coder_parameter.region.value}a\"\n instance_type = data.coder_parameter.instance_type.value\n\n user_data = data.cloudinit_config.user_data.rendered\n tags = {\n Name = \"coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}\"\n }\n lifecycle {\n ignore_changes = [ami]\n }\n}\n\n\n\n// The agent is configured with \"google-instance-identity\" auth.\nterraform {\n required_providers {\n google = {\n source = \"hashicorp/google\"\n }\n }\n}\n\nresource \"google_compute_instance\" \"dev\" {\n zone = module.gcp_region.value\n count = data.coder_workspace.me.start_count\n name = \"coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-root\"\n machine_type = \"e2-medium\"\n network_interface {\n network = \"default\"\n access_config {\n // Ephemeral public IP\n }\n }\n boot_disk {\n auto_delete = false\n source = google_compute_disk.root.name\n }\n // In order to use google-instance-identity, a service account *must* be provided.\n service_account {\n email = data.google_compute_default_service_account.default.email\n scopes = [\"cloud-platform\"]\n }\n # ONLY FOR WINDOWS:\n # metadata = {\n # windows-startup-script-ps1 = coder_agent.main.init_script\n # }\n # The startup script runs as root with no $HOME environment set up, so instead of directly\n # running the agent init script, create a user (with a homedir, default shell and sudo\n # permissions) and execute the init script as that user.\n #\n # The agent MUST be started in here.\n metadata_startup_script = </dev/null 2>&1; then\n useradd -m -s /bin/bash \"${local.linux_user}\"\n echo \"${local.linux_user} ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/coder-user\nfi\n\nexec sudo -u \"${local.linux_user}\" sh -c '${coder_agent.main.init_script}'\nEOMETA\n}\n\n\n\n// The agent is configured with \"azure-instance-identity\" auth.\nterraform {\n required_providers {\n azurerm = {\n source = \"hashicorp/azurerm\"\n }\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = true\n\n boundary = \"//\"\n\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// bootcmd:\n\t// # work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117\n\t// - until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done\n\t// device_aliases:\n\t// homedir: /dev/disk/azure/scsi1/lun10\n\t// disk_setup:\n\t// homedir:\n\t// table_type: gpt\n\t// layout: true\n\t// fs_setup:\n\t// - label: coder_home\n\t// filesystem: ext4\n\t// device: homedir.1\n\t// mounts:\n\t// - [\"LABEL=coder_home\", \"/home/${username}\"]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${username}\n\t// sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n\t// groups: sudo\n\t// shell: /bin/bash\n\t// packages:\n\t// - git\n\t// write_files:\n\t// - path: /opt/coder/init\n\t// permissions: \"0755\"\n\t// encoding: b64\n\t// content: ${init_script}\n\t// - path: /etc/systemd/system/coder-agent.service\n\t// permissions: \"0644\"\n\t// content: |\n\t// [Unit]\n\t// Description=Coder Agent\n\t// After=network-online.target\n\t// Wants=network-online.target\n\n\t// [Service]\n\t// User=${username}\n\t// ExecStart=/opt/coder/init\n\t// Restart=always\n\t// RestartSec=10\n\t// TimeoutStopSec=90\n\t// KillMode=process\n\n\t// OOMScoreAdjust=-900\n\t// SyslogIdentifier=coder-agent\n\n\t// [Install]\n\t// WantedBy=multi-user.target\n\t// runcmd:\n\t// - chown ${username}:${username} /home/${username}\n\t// - systemctl enable coder-agent\n\t// - systemctl start coder-agent\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n username = \"coder\" # Ensure this user/group does not exist in your VM image\n init_script = base64encode(coder_agent.main.init_script)\n hostname = lower(data.coder_workspace.me.name)\n })\n }\n}\n\nresource \"azurerm_linux_virtual_machine\" \"main\" {\n count = data.coder_workspace.me.start_count\n name = \"vm\"\n resource_group_name = azurerm_resource_group.main.name\n location = azurerm_resource_group.main.location\n size = data.coder_parameter.instance_type.value\n // cloud-init overwrites this, so the value here doesn't matter\n admin_username = \"adminuser\"\n admin_ssh_key {\n public_key = tls_private_key.dummy.public_key_openssh\n username = \"adminuser\"\n }\n\n network_interface_ids = [\n azurerm_network_interface.main.id,\n ]\n computer_name = lower(data.coder_workspace.me.name)\n os_disk {\n caching = \"ReadWrite\"\n storage_account_type = \"Standard_LRS\"\n }\n source_image_reference {\n publisher = \"Canonical\"\n offer = \"0001-com-ubuntu-server-focal\"\n sku = \"20_04-lts-gen2\"\n version = \"latest\"\n }\n user_data = data.cloudinit_config.user_data.rendered\n}\n\n\n\nterraform {\n required_providers {\n coder = {\n source = \"kreuzwerker/docker\"\n }\n }\n}\n\n// The agent is configured with \"token\" auth.\n\nresource \"docker_container\" \"workspace\" {\n count = data.coder_workspace.me.start_count\n image = \"codercom/enterprise-base:ubuntu\"\n # Uses lower() to avoid Docker restriction on container names.\n name = \"coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}\"\n # Hostname makes the shell more user friendly: coder@my-workspace:~$\n hostname = data.coder_workspace.me.name\n # Use the docker gateway if the access URL is 127.0.0.1.\n entrypoint = [\"sh\", \"-c\", replace(coder_agent.main.init_script, \"/localhost|127\\\\.0\\\\.0\\\\.1/\", \"host.docker.internal\")]\n env = [\"CODER_AGENT_TOKEN=${coder_agent.main.token}\"]\n host {\n host = \"host.docker.internal\"\n ip = \"host-gateway\"\n }\n volumes {\n container_path = \"/home/coder\"\n volume_name = docker_volume.home_volume.name\n read_only = false\n }\n}\n\n\n\n// The agent is configured with \"token\" auth.\n\nresource \"kubernetes_deployment\" \"main\" {\n count = data.coder_workspace.me.start_count\n depends_on = [\n kubernetes_persistent_volume_claim.home\n ]\n wait_for_rollout = false\n metadata {\n name = \"coder-${data.coder_workspace.me.id}\"\n }\n\n spec {\n replicas = 1\n strategy {\n type = \"Recreate\"\n }\n\n template {\n spec {\n security_context {\n run_as_user = 1000\n fs_group = 1000\n run_as_non_root = true\n }\n\n container {\n name = \"dev\"\n image = \"codercom/enterprise-base:ubuntu\"\n image_pull_policy = \"Always\"\n command = [\"sh\", \"-c\", coder_agent.main.init_script]\n security_context {\n run_as_user = \"1000\"\n }\n env {\n name = \"CODER_AGENT_TOKEN\"\n value = coder_agent.main.token\n }\n }\n }\n }\n }\n}\n\n\nThe file_id provided is a reference to a tar file you have uploaded containing the Terraform.\n","name":"bmcp_coder_coder_create_template_version","parameters":{"properties":{"file_id":{"type":"string"},"template_id":{"type":"string"}},"required":["file_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace in Coder.\n\nIf a user is asking to \"test a template\", they are typically referring\nto creating a workspace from a template to ensure the infrastructure\nis provisioned correctly and the agent can connect to the control plane.\n\nBefore creating a workspace, always confirm the template choice with the user by:\n\n\t1. Listing the available templates that match their request.\n\t2. Recommending the most relevant option.\n\t2. Asking the user to confirm which template to use.\n\nIt is important to not create a workspace without confirming the template\nchoice with the user.\n\nAfter creating a workspace, watch the build logs and wait for the workspace to\nbe ready before trying to use or connect to the workspace.\n","name":"bmcp_coder_coder_create_workspace","parameters":{"properties":{"name":{"description":"Name of the workspace to create.","type":"string"},"rich_parameters":{"description":"Key/value pairs of rich parameters to pass to the template version to create the workspace.","type":"object"},"template_version_id":{"description":"ID of the template version to create the workspace from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a workspace. Omit or use the `me` keyword to create a workspace for the authenticated user.","type":"string"}},"required":["user","template_version_id","name","rich_parameters"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace build for an existing workspace. Use this to start, stop, or delete.\n\nAfter creating a workspace build, watch the build logs and wait for the\nworkspace build to complete before trying to start another build or use or\nconnect to the workspace.\n","name":"bmcp_coder_coder_create_workspace_build","parameters":{"properties":{"template_version_id":{"description":"(Optional) The template version ID to use for the workspace build. If not provided, the previously built version will be used.","type":"string"},"transition":{"description":"The transition to perform. Must be one of: start, stop, delete","enum":["start","stop","delete"],"type":"string"},"workspace_id":{"type":"string"}},"required":["workspace_id","transition"],"type":"object"},"strict":false},{"type":"function","description":"Delete a task.","name":"bmcp_coder_coder_delete_task","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to delete. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Delete a template. This is irreversible.","name":"bmcp_coder_coder_delete_template","parameters":{"properties":{"template_id":{"type":"string"}},"required":["template_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the currently authenticated user, similar to the `whoami` command.","name":"bmcp_coder_coder_get_authenticated_user","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a task.","name":"bmcp_coder_coder_get_task_logs","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to query. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the status of a task.","name":"bmcp_coder_coder_get_task_status","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to get. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a template version. This is useful to check whether a template version successfully imports or not.","name":"bmcp_coder_coder_get_template_version_logs","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Get a workspace by name or ID.\n\nThis returns more data than list_workspaces to reduce token usage.","name":"bmcp_coder_coder_get_workspace","parameters":{"properties":{"workspace_id":{"description":"The workspace ID or name in the format [owner/]workspace. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace agent.\n\n\t\tMore logs may appear after this call. It does not wait for the agent to finish.","name":"bmcp_coder_coder_get_workspace_agent_logs","parameters":{"properties":{"workspace_agent_id":{"type":"string"}},"required":["workspace_agent_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace build.\n\n\t\tUseful for checking whether a workspace builds successfully or not.","name":"bmcp_coder_coder_get_workspace_build_logs","parameters":{"properties":{"workspace_build_id":{"type":"string"}},"required":["workspace_build_id"],"type":"object"},"strict":false},{"type":"function","description":"List tasks.","name":"bmcp_coder_coder_list_tasks","parameters":{"properties":{"status":{"description":"Optional filter by task status.","type":"string"},"user":{"description":"Username or ID of the user for which to list tasks. Omit or use the `me` keyword to list tasks for the authenticated user.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Lists templates for the authenticated user.","name":"bmcp_coder_coder_list_templates","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Lists workspaces for the authenticated user.","name":"bmcp_coder_coder_list_workspaces","parameters":{"properties":{"owner":{"description":"The owner of the workspaces to list. Use \"me\" to list workspaces for the authenticated user. If you do not specify an owner, \"me\" will be assumed by default.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Send input to a running task.","name":"bmcp_coder_coder_send_task_input","parameters":{"properties":{"input":{"description":"The input to send to the task.","type":"string"},"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to prompt. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id","input"],"type":"object"},"strict":false},{"type":"function","description":"Get the parameters for a template version. You can refer to these as workspace parameters to the user, as they are typically important for creating a workspace.","name":"bmcp_coder_coder_template_version_parameters","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Update the active version of a template. This is helpful when iterating on templates.","name":"bmcp_coder_coder_update_template_active_version","parameters":{"properties":{"template_id":{"type":"string"},"template_version_id":{"type":"string"}},"required":["template_id","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create and upload a tar file by key/value mapping of file names to file contents. Use this to create template versions. Reference the tool description of \"create_template_version\" to understand template requirements.","name":"bmcp_coder_coder_upload_tar_file","parameters":{"properties":{"files":{"description":"A map of file names to file contents.","type":"object"}},"required":["files"],"type":"object"},"strict":false},{"type":"function","description":"Execute a bash command in a Coder workspace.\n\nThis tool provides the same functionality as the 'coder ssh ' CLI command.\nIt automatically starts the workspace if it's stopped and waits for the agent to be ready.\nThe output is trimmed of leading and trailing whitespace.\n\nThe workspace parameter supports various formats:\n- workspace (uses current user)\n- owner/workspace\n- owner--workspace\n- workspace.agent (specific agent)\n- owner/workspace.agent\n\nThe timeout_ms parameter specifies the command timeout in milliseconds (defaults to 60000ms, maximum of 300000ms).\nIf the command times out, all output captured up to that point is returned with a cancellation message.\n\nFor background commands (background: true), output is captured until the timeout is reached, then the command\ncontinues running in the background. The captured output is returned as the result.\n\nFor file operations (list, write, edit), always prefer the dedicated file tools.\nDo not use bash commands (ls, cat, echo, heredoc, etc.) to list, write, or read\nfiles when the file tools are available. The bash tool should be used for:\n\n\t- Running commands and scripts\n\t- Installing packages\n\t- Starting services\n\t- Executing programs\n\nExamples:\n- workspace: \"john/dev-env\", command: \"git status\", timeout_ms: 30000\n- workspace: \"my-workspace\", command: \"npm run dev\", background: true, timeout_ms: 10000\n- workspace: \"my-workspace.main\", command: \"docker ps\"","name":"bmcp_coder_coder_workspace_bash","parameters":{"properties":{"background":{"description":"Whether to run the command in the background. Output is captured until timeout, then the command continues running in the background.","type":"boolean"},"command":{"description":"The bash command to execute in the workspace.","type":"string"},"timeout_ms":{"default":60000,"description":"Command timeout in milliseconds. Defaults to 60000ms (60 seconds) if not specified.","minimum":1,"type":"integer"},"workspace":{"description":"The workspace name in format [owner/]workspace[.agent]. If owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","command"],"type":"object"},"strict":false},{"type":"function","description":"Edit a file in a workspace.","name":"bmcp_coder_coder_workspace_edit_file","parameters":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","edits"],"type":"object"},"strict":false},{"type":"function","description":"Edit one or more files in a workspace.","name":"bmcp_coder_coder_workspace_edit_files","parameters":{"properties":{"files":{"description":"An array of files to edit.","items":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"}},"required":["path","edits"],"type":"object"},"type":"array"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","files"],"type":"object"},"strict":false},{"type":"function","description":"List the URLs of Coder apps running in a workspace for a single agent.","name":"bmcp_coder_coder_workspace_list_apps","parameters":{"properties":{"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace"],"type":"object"},"strict":false},{"type":"function","description":"List directories in a workspace.","name":"bmcp_coder_coder_workspace_ls","parameters":{"properties":{"path":{"description":"The absolute path of the directory in the workspace to list.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Fetch URLs that forward to the specified port.","name":"bmcp_coder_coder_workspace_port_forward","parameters":{"properties":{"port":{"description":"The port to forward.","type":"number"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","port"],"type":"object"},"strict":false},{"type":"function","description":"Read from a file in a workspace.","name":"bmcp_coder_coder_workspace_read_file","parameters":{"properties":{"limit":{"description":"The number of bytes to read. Cannot exceed 1 MiB. Defaults to the full size of the file or 1 MiB, whichever is lower.","type":"integer"},"offset":{"description":"A byte offset indicating where in the file to start reading. Defaults to zero. An empty string indicates the end of the file has been reached.","type":"integer"},"path":{"description":"The absolute path of the file to read in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Write a file in a workspace.\n\nIf a file write fails due to syntax errors or encoding issues, do NOT switch\nto using bash commands as a workaround. Instead:\n\n\t1. Read the error message carefully to identify the issue\n\t2. Fix the content encoding/syntax\n\t3. Retry with this tool\n\nThe content parameter expects base64-encoded bytes. Ensure your source content\nis correct before encoding it. If you encounter errors, decode and verify the\ncontent you are trying to write, then re-encode it properly.\n","name":"bmcp_coder_coder_workspace_write_file","parameters":{"properties":{"content":{"description":"The base64-encoded bytes to write to the file.","type":"string"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","content"],"type":"object"},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":6280,"input_tokens_details":{"cached_tokens":100,"cache_write_tokens":5},"output_tokens":30,"output_tokens_details":{"reasoning_tokens":3},"total_tokens":6310},"user":null,"metadata":{}},"sequence_number":17}
-- streaming/tool-call --
@@ -246,5 +246,5 @@ event: response.output_item.done
data: {"type":"response.output_item.done","item":{"id":"msg_0dfed48e1052ad7f0069725ca4c2488193a652eba330c51e5b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The workspace ID you provided ('non_existing_id') is not valid. Workspace IDs must be valid UUIDs (typically 36 characters long). Please provide a valid workspace ID to create a new workspace build. If you need help finding your workspace ID, let me know!"}],"role":"assistant"},"output_index":0,"sequence_number":60}
event: response.completed
-data: {"type":"response.completed","response":{"id":"resp_0dfed48e1052ad7f0069725ca39880819390fcc5b2eb8cf8c6","object":"response","created_at":1769102499,"status":"completed","background":false,"completed_at":1769102501,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","output":[{"id":"msg_0dfed48e1052ad7f0069725ca4c2488193a652eba330c51e5b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The workspace ID you provided ('non_existing_id') is not valid. Workspace IDs must be valid UUIDs (typically 36 characters long). Please provide a valid workspace ID to create a new workspace build. If you need help finding your workspace ID, let me know!"}],"role":"assistant"}],"parallel_tool_calls":false,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Create a task.","name":"bmcp_coder_coder_create_task","parameters":{"properties":{"input":{"description":"Input/prompt for the task.","type":"string"},"template_version_id":{"description":"ID of the template version to create the task from.","type":"string"},"template_version_preset_id":{"description":"Optional ID of the template version preset to create the task from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a task. Omit or use the `me` keyword to create a task for the authenticated user.","type":"string"}},"required":["input","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template in Coder. First, you must create a template version.","name":"bmcp_coder_coder_create_template","parameters":{"properties":{"description":{"type":"string"},"display_name":{"type":"string"},"icon":{"description":"A URL to an icon to use.","type":"string"},"name":{"type":"string"},"version_id":{"description":"The ID of the version to use.","type":"string"}},"required":["name","display_name","description","version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template version. This is a precursor to creating a template, or you can update an existing template.\n\nTemplates are Terraform defining a development environment. The provisioned infrastructure must run\nan Agent that connects to the Coder Control Plane to provide a rich experience.\n\nHere are some strict rules for creating a template version:\n- YOU MUST NOT use \"variable\" or \"output\" blocks in the Terraform code.\n- YOU MUST ALWAYS check template version logs after creation to ensure the template was imported successfully.\n\nWhen a template version is created, a Terraform Plan occurs that ensures the infrastructure\n_could_ be provisioned, but actual provisioning occurs when a workspace is created.\n\n\nThe Coder Terraform Provider can be imported like:\n\n```hcl\nterraform {\n required_providers {\n coder = {\n source = \"coder/coder\"\n }\n }\n}\n```\n\nA destroy does not occur when a user stops a workspace, but rather the transition changes:\n\n```hcl\ndata \"coder_workspace\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace.\n- name: The name of the workspace.\n- transition: Either \"start\" or \"stop\".\n- start_count: A computed count based on the transition field. If \"start\", this will be 1.\n\nAccess workspace owner information with:\n\n```hcl\ndata \"coder_workspace_owner\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace owner.\n- name: The name of the workspace owner.\n- full_name: The full name of the workspace owner.\n- email: The email of the workspace owner.\n- session_token: A token that can be used to authenticate the workspace owner. It is regenerated every time the workspace is started.\n- oidc_access_token: A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.\n\nParameters are defined in the template version. They are rendered in the UI on the workspace creation page:\n\n```hcl\nresource \"coder_parameter\" \"region\" {\n name = \"region\"\n type = \"string\"\n default = \"us-east-1\"\n}\n```\n\nThis resource accepts the following properties:\n- name: The name of the parameter.\n- default: The default value of the parameter.\n- type: The type of the parameter. Must be one of: \"string\", \"number\", \"bool\", or \"list(string)\".\n- display_name: The displayed name of the parameter as it will appear in the UI.\n- description: The description of the parameter as it will appear in the UI.\n- ephemeral: The value of an ephemeral parameter will not be preserved between consecutive workspace builds.\n- form_type: The type of this parameter. Must be one of: [radio, slider, input, dropdown, checkbox, switch, multi-select, tag-select, textarea, error].\n- icon: A URL to an icon to display in the UI.\n- mutable: Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution!\n- option: Each option block defines a value for a user to select from. (see below for nested schema)\n Required:\n - name: The name of the option.\n - value: The value of the option.\n Optional:\n - description: The description of the option as it will appear in the UI.\n - icon: A URL to an icon to display in the UI.\n\nA Workspace Agent runs on provisioned infrastructure to provide access to the workspace:\n\n```hcl\nresource \"coder_agent\" \"dev\" {\n arch = \"amd64\"\n os = \"linux\"\n}\n```\n\nThis resource accepts the following properties:\n- arch: The architecture of the agent. Must be one of: \"amd64\", \"arm64\", or \"armv7\".\n- os: The operating system of the agent. Must be one of: \"linux\", \"windows\", or \"darwin\".\n- auth: The authentication method for the agent. Must be one of: \"token\", \"google-instance-identity\", \"aws-instance-identity\", or \"azure-instance-identity\". It is insecure to pass the agent token via exposed variables to Virtual Machines. Instance Identity enables provisioned VMs to authenticate by instance ID on start.\n- dir: The starting directory when a user creates a shell session. Defaults to \"$HOME\".\n- env: A map of environment variables to set for the agent.\n- startup_script: A script to run after the agent starts. This script MUST exit eventually to signal that startup has completed. Use \"&\" or \"screen\" to run processes in the background.\n\nThis resource provides the following fields:\n- id: The UUID of the agent.\n- init_script: The script to run on provisioned infrastructure to fetch and start the agent.\n- token: Set the environment variable CODER_AGENT_TOKEN to this value to authenticate the agent.\n\nThe agent MUST be installed and started using the init_script. A utility like curl or wget to fetch the agent binary must exist in the provisioned infrastructure.\n\nExpose terminal or HTTP applications running in a workspace with:\n\n```hcl\nresource \"coder_app\" \"dev\" {\n agent_id = coder_agent.dev.id\n slug = \"my-app-name\"\n display_name = \"My App\"\n icon = \"https://my-app.com/icon.svg\"\n url = \"http://127.0.0.1:3000\"\n}\n```\n\nThis resource accepts the following properties:\n- agent_id: The ID of the agent to attach the app to.\n- slug: The slug of the app.\n- display_name: The displayed name of the app as it will appear in the UI.\n- icon: A URL to an icon to display in the UI.\n- url: An external url if external=true or a URL to be proxied to from inside the workspace. This should be of the form http://localhost:PORT[/SUBPATH]. Either command or url may be specified, but not both.\n- command: A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either command or url may be specified, but not both.\n- external: Whether this app is an external app. If true, the url will be opened in a new tab.\n\n\nThe Coder Server may not be authenticated with the infrastructure provider a user requests. In this scenario,\nthe user will need to provide credentials to the Coder Server before the workspace can be provisioned.\n\nHere are examples of provisioning the Coder Agent on specific infrastructure providers:\n\n\n// The agent is configured with \"aws-instance-identity\" auth.\nterraform {\n required_providers {\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n aws = {\n source = \"hashicorp/aws\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = false\n boundary = \"//\"\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${linux_user}\n\t// sudo: ALL=(ALL) NOPASSWD:ALL\n\t// shell: /bin/bash\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n hostname = local.hostname\n linux_user = local.linux_user\n })\n }\n\n part {\n filename = \"userdata.sh\"\n content_type = \"text/x-shellscript\"\n\n\t// Here is the content of the userdata.sh.tftpl file:\n\t// #!/bin/bash\n\t// sudo -u '${linux_user}' sh -c '${init_script}'\n content = templatefile(\"${path.module}/cloud-init/userdata.sh.tftpl\", {\n linux_user = local.linux_user\n\n init_script = try(coder_agent.dev[0].init_script, \"\")\n })\n }\n}\n\nresource \"aws_instance\" \"dev\" {\n ami = data.aws_ami.ubuntu.id\n availability_zone = \"${data.coder_parameter.region.value}a\"\n instance_type = data.coder_parameter.instance_type.value\n\n user_data = data.cloudinit_config.user_data.rendered\n tags = {\n Name = \"coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}\"\n }\n lifecycle {\n ignore_changes = [ami]\n }\n}\n\n\n\n// The agent is configured with \"google-instance-identity\" auth.\nterraform {\n required_providers {\n google = {\n source = \"hashicorp/google\"\n }\n }\n}\n\nresource \"google_compute_instance\" \"dev\" {\n zone = module.gcp_region.value\n count = data.coder_workspace.me.start_count\n name = \"coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-root\"\n machine_type = \"e2-medium\"\n network_interface {\n network = \"default\"\n access_config {\n // Ephemeral public IP\n }\n }\n boot_disk {\n auto_delete = false\n source = google_compute_disk.root.name\n }\n // In order to use google-instance-identity, a service account *must* be provided.\n service_account {\n email = data.google_compute_default_service_account.default.email\n scopes = [\"cloud-platform\"]\n }\n # ONLY FOR WINDOWS:\n # metadata = {\n # windows-startup-script-ps1 = coder_agent.main.init_script\n # }\n # The startup script runs as root with no $HOME environment set up, so instead of directly\n # running the agent init script, create a user (with a homedir, default shell and sudo\n # permissions) and execute the init script as that user.\n #\n # The agent MUST be started in here.\n metadata_startup_script = </dev/null 2>&1; then\n useradd -m -s /bin/bash \"${local.linux_user}\"\n echo \"${local.linux_user} ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/coder-user\nfi\n\nexec sudo -u \"${local.linux_user}\" sh -c '${coder_agent.main.init_script}'\nEOMETA\n}\n\n\n\n// The agent is configured with \"azure-instance-identity\" auth.\nterraform {\n required_providers {\n azurerm = {\n source = \"hashicorp/azurerm\"\n }\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = true\n\n boundary = \"//\"\n\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// bootcmd:\n\t// # work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117\n\t// - until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done\n\t// device_aliases:\n\t// homedir: /dev/disk/azure/scsi1/lun10\n\t// disk_setup:\n\t// homedir:\n\t// table_type: gpt\n\t// layout: true\n\t// fs_setup:\n\t// - label: coder_home\n\t// filesystem: ext4\n\t// device: homedir.1\n\t// mounts:\n\t// - [\"LABEL=coder_home\", \"/home/${username}\"]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${username}\n\t// sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n\t// groups: sudo\n\t// shell: /bin/bash\n\t// packages:\n\t// - git\n\t// write_files:\n\t// - path: /opt/coder/init\n\t// permissions: \"0755\"\n\t// encoding: b64\n\t// content: ${init_script}\n\t// - path: /etc/systemd/system/coder-agent.service\n\t// permissions: \"0644\"\n\t// content: |\n\t// [Unit]\n\t// Description=Coder Agent\n\t// After=network-online.target\n\t// Wants=network-online.target\n\n\t// [Service]\n\t// User=${username}\n\t// ExecStart=/opt/coder/init\n\t// Restart=always\n\t// RestartSec=10\n\t// TimeoutStopSec=90\n\t// KillMode=process\n\n\t// OOMScoreAdjust=-900\n\t// SyslogIdentifier=coder-agent\n\n\t// [Install]\n\t// WantedBy=multi-user.target\n\t// runcmd:\n\t// - chown ${username}:${username} /home/${username}\n\t// - systemctl enable coder-agent\n\t// - systemctl start coder-agent\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n username = \"coder\" # Ensure this user/group does not exist in your VM image\n init_script = base64encode(coder_agent.main.init_script)\n hostname = lower(data.coder_workspace.me.name)\n })\n }\n}\n\nresource \"azurerm_linux_virtual_machine\" \"main\" {\n count = data.coder_workspace.me.start_count\n name = \"vm\"\n resource_group_name = azurerm_resource_group.main.name\n location = azurerm_resource_group.main.location\n size = data.coder_parameter.instance_type.value\n // cloud-init overwrites this, so the value here doesn't matter\n admin_username = \"adminuser\"\n admin_ssh_key {\n public_key = tls_private_key.dummy.public_key_openssh\n username = \"adminuser\"\n }\n\n network_interface_ids = [\n azurerm_network_interface.main.id,\n ]\n computer_name = lower(data.coder_workspace.me.name)\n os_disk {\n caching = \"ReadWrite\"\n storage_account_type = \"Standard_LRS\"\n }\n source_image_reference {\n publisher = \"Canonical\"\n offer = \"0001-com-ubuntu-server-focal\"\n sku = \"20_04-lts-gen2\"\n version = \"latest\"\n }\n user_data = data.cloudinit_config.user_data.rendered\n}\n\n\n\nterraform {\n required_providers {\n coder = {\n source = \"kreuzwerker/docker\"\n }\n }\n}\n\n// The agent is configured with \"token\" auth.\n\nresource \"docker_container\" \"workspace\" {\n count = data.coder_workspace.me.start_count\n image = \"codercom/enterprise-base:ubuntu\"\n # Uses lower() to avoid Docker restriction on container names.\n name = \"coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}\"\n # Hostname makes the shell more user friendly: coder@my-workspace:~$\n hostname = data.coder_workspace.me.name\n # Use the docker gateway if the access URL is 127.0.0.1.\n entrypoint = [\"sh\", \"-c\", replace(coder_agent.main.init_script, \"/localhost|127\\\\.0\\\\.0\\\\.1/\", \"host.docker.internal\")]\n env = [\"CODER_AGENT_TOKEN=${coder_agent.main.token}\"]\n host {\n host = \"host.docker.internal\"\n ip = \"host-gateway\"\n }\n volumes {\n container_path = \"/home/coder\"\n volume_name = docker_volume.home_volume.name\n read_only = false\n }\n}\n\n\n\n// The agent is configured with \"token\" auth.\n\nresource \"kubernetes_deployment\" \"main\" {\n count = data.coder_workspace.me.start_count\n depends_on = [\n kubernetes_persistent_volume_claim.home\n ]\n wait_for_rollout = false\n metadata {\n name = \"coder-${data.coder_workspace.me.id}\"\n }\n\n spec {\n replicas = 1\n strategy {\n type = \"Recreate\"\n }\n\n template {\n spec {\n security_context {\n run_as_user = 1000\n fs_group = 1000\n run_as_non_root = true\n }\n\n container {\n name = \"dev\"\n image = \"codercom/enterprise-base:ubuntu\"\n image_pull_policy = \"Always\"\n command = [\"sh\", \"-c\", coder_agent.main.init_script]\n security_context {\n run_as_user = \"1000\"\n }\n env {\n name = \"CODER_AGENT_TOKEN\"\n value = coder_agent.main.token\n }\n }\n }\n }\n }\n}\n\n\nThe file_id provided is a reference to a tar file you have uploaded containing the Terraform.\n","name":"bmcp_coder_coder_create_template_version","parameters":{"properties":{"file_id":{"type":"string"},"template_id":{"type":"string"}},"required":["file_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace in Coder.\n\nIf a user is asking to \"test a template\", they are typically referring\nto creating a workspace from a template to ensure the infrastructure\nis provisioned correctly and the agent can connect to the control plane.\n\nBefore creating a workspace, always confirm the template choice with the user by:\n\n\t1. Listing the available templates that match their request.\n\t2. Recommending the most relevant option.\n\t2. Asking the user to confirm which template to use.\n\nIt is important to not create a workspace without confirming the template\nchoice with the user.\n\nAfter creating a workspace, watch the build logs and wait for the workspace to\nbe ready before trying to use or connect to the workspace.\n","name":"bmcp_coder_coder_create_workspace","parameters":{"properties":{"name":{"description":"Name of the workspace to create.","type":"string"},"rich_parameters":{"description":"Key/value pairs of rich parameters to pass to the template version to create the workspace.","type":"object"},"template_version_id":{"description":"ID of the template version to create the workspace from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a workspace. Omit or use the `me` keyword to create a workspace for the authenticated user.","type":"string"}},"required":["user","template_version_id","name","rich_parameters"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace build for an existing workspace. Use this to start, stop, or delete.\n\nAfter creating a workspace build, watch the build logs and wait for the\nworkspace build to complete before trying to start another build or use or\nconnect to the workspace.\n","name":"bmcp_coder_coder_create_workspace_build","parameters":{"properties":{"template_version_id":{"description":"(Optional) The template version ID to use for the workspace build. If not provided, the previously built version will be used.","type":"string"},"transition":{"description":"The transition to perform. Must be one of: start, stop, delete","enum":["start","stop","delete"],"type":"string"},"workspace_id":{"type":"string"}},"required":["workspace_id","transition"],"type":"object"},"strict":false},{"type":"function","description":"Delete a task.","name":"bmcp_coder_coder_delete_task","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to delete. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Delete a template. This is irreversible.","name":"bmcp_coder_coder_delete_template","parameters":{"properties":{"template_id":{"type":"string"}},"required":["template_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the currently authenticated user, similar to the `whoami` command.","name":"bmcp_coder_coder_get_authenticated_user","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a task.","name":"bmcp_coder_coder_get_task_logs","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to query. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the status of a task.","name":"bmcp_coder_coder_get_task_status","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to get. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a template version. This is useful to check whether a template version successfully imports or not.","name":"bmcp_coder_coder_get_template_version_logs","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Get a workspace by name or ID.\n\nThis returns more data than list_workspaces to reduce token usage.","name":"bmcp_coder_coder_get_workspace","parameters":{"properties":{"workspace_id":{"description":"The workspace ID or name in the format [owner/]workspace. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace agent.\n\n\t\tMore logs may appear after this call. It does not wait for the agent to finish.","name":"bmcp_coder_coder_get_workspace_agent_logs","parameters":{"properties":{"workspace_agent_id":{"type":"string"}},"required":["workspace_agent_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace build.\n\n\t\tUseful for checking whether a workspace builds successfully or not.","name":"bmcp_coder_coder_get_workspace_build_logs","parameters":{"properties":{"workspace_build_id":{"type":"string"}},"required":["workspace_build_id"],"type":"object"},"strict":false},{"type":"function","description":"List tasks.","name":"bmcp_coder_coder_list_tasks","parameters":{"properties":{"status":{"description":"Optional filter by task status.","type":"string"},"user":{"description":"Username or ID of the user for which to list tasks. Omit or use the `me` keyword to list tasks for the authenticated user.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Lists templates for the authenticated user.","name":"bmcp_coder_coder_list_templates","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Lists workspaces for the authenticated user.","name":"bmcp_coder_coder_list_workspaces","parameters":{"properties":{"owner":{"description":"The owner of the workspaces to list. Use \"me\" to list workspaces for the authenticated user. If you do not specify an owner, \"me\" will be assumed by default.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Send input to a running task.","name":"bmcp_coder_coder_send_task_input","parameters":{"properties":{"input":{"description":"The input to send to the task.","type":"string"},"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to prompt. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id","input"],"type":"object"},"strict":false},{"type":"function","description":"Get the parameters for a template version. You can refer to these as workspace parameters to the user, as they are typically important for creating a workspace.","name":"bmcp_coder_coder_template_version_parameters","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Update the active version of a template. This is helpful when iterating on templates.","name":"bmcp_coder_coder_update_template_active_version","parameters":{"properties":{"template_id":{"type":"string"},"template_version_id":{"type":"string"}},"required":["template_id","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create and upload a tar file by key/value mapping of file names to file contents. Use this to create template versions. Reference the tool description of \"create_template_version\" to understand template requirements.","name":"bmcp_coder_coder_upload_tar_file","parameters":{"properties":{"files":{"description":"A map of file names to file contents.","type":"object"}},"required":["files"],"type":"object"},"strict":false},{"type":"function","description":"Execute a bash command in a Coder workspace.\n\nThis tool provides the same functionality as the 'coder ssh ' CLI command.\nIt automatically starts the workspace if it's stopped and waits for the agent to be ready.\nThe output is trimmed of leading and trailing whitespace.\n\nThe workspace parameter supports various formats:\n- workspace (uses current user)\n- owner/workspace\n- owner--workspace\n- workspace.agent (specific agent)\n- owner/workspace.agent\n\nThe timeout_ms parameter specifies the command timeout in milliseconds (defaults to 60000ms, maximum of 300000ms).\nIf the command times out, all output captured up to that point is returned with a cancellation message.\n\nFor background commands (background: true), output is captured until the timeout is reached, then the command\ncontinues running in the background. The captured output is returned as the result.\n\nFor file operations (list, write, edit), always prefer the dedicated file tools.\nDo not use bash commands (ls, cat, echo, heredoc, etc.) to list, write, or read\nfiles when the file tools are available. The bash tool should be used for:\n\n\t- Running commands and scripts\n\t- Installing packages\n\t- Starting services\n\t- Executing programs\n\nExamples:\n- workspace: \"john/dev-env\", command: \"git status\", timeout_ms: 30000\n- workspace: \"my-workspace\", command: \"npm run dev\", background: true, timeout_ms: 10000\n- workspace: \"my-workspace.main\", command: \"docker ps\"","name":"bmcp_coder_coder_workspace_bash","parameters":{"properties":{"background":{"description":"Whether to run the command in the background. Output is captured until timeout, then the command continues running in the background.","type":"boolean"},"command":{"description":"The bash command to execute in the workspace.","type":"string"},"timeout_ms":{"default":60000,"description":"Command timeout in milliseconds. Defaults to 60000ms (60 seconds) if not specified.","minimum":1,"type":"integer"},"workspace":{"description":"The workspace name in format [owner/]workspace[.agent]. If owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","command"],"type":"object"},"strict":false},{"type":"function","description":"Edit a file in a workspace.","name":"bmcp_coder_coder_workspace_edit_file","parameters":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","edits"],"type":"object"},"strict":false},{"type":"function","description":"Edit one or more files in a workspace.","name":"bmcp_coder_coder_workspace_edit_files","parameters":{"properties":{"files":{"description":"An array of files to edit.","items":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"}},"required":["path","edits"],"type":"object"},"type":"array"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","files"],"type":"object"},"strict":false},{"type":"function","description":"List the URLs of Coder apps running in a workspace for a single agent.","name":"bmcp_coder_coder_workspace_list_apps","parameters":{"properties":{"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace"],"type":"object"},"strict":false},{"type":"function","description":"List directories in a workspace.","name":"bmcp_coder_coder_workspace_ls","parameters":{"properties":{"path":{"description":"The absolute path of the directory in the workspace to list.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Fetch URLs that forward to the specified port.","name":"bmcp_coder_coder_workspace_port_forward","parameters":{"properties":{"port":{"description":"The port to forward.","type":"number"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","port"],"type":"object"},"strict":false},{"type":"function","description":"Read from a file in a workspace.","name":"bmcp_coder_coder_workspace_read_file","parameters":{"properties":{"limit":{"description":"The number of bytes to read. Cannot exceed 1 MiB. Defaults to the full size of the file or 1 MiB, whichever is lower.","type":"integer"},"offset":{"description":"A byte offset indicating where in the file to start reading. Defaults to zero. An empty string indicates the end of the file has been reached.","type":"integer"},"path":{"description":"The absolute path of the file to read in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Write a file in a workspace.\n\nIf a file write fails due to syntax errors or encoding issues, do NOT switch\nto using bash commands as a workaround. Instead:\n\n\t1. Read the error message carefully to identify the issue\n\t2. Fix the content encoding/syntax\n\t3. Retry with this tool\n\nThe content parameter expects base64-encoded bytes. Ensure your source content\nis correct before encoding it. If you encounter errors, decode and verify the\ncontent you are trying to write, then re-encode it properly.\n","name":"bmcp_coder_coder_workspace_write_file","parameters":{"properties":{"content":{"description":"The base64-encoded bytes to write to the file.","type":"string"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","content"],"type":"object"},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":6346,"input_tokens_details":{"cached_tokens":0},"output_tokens":56,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":6402},"user":null,"metadata":{}},"sequence_number":61}
+data: {"type":"response.completed","response":{"id":"resp_0dfed48e1052ad7f0069725ca39880819390fcc5b2eb8cf8c6","object":"response","created_at":1769102499,"status":"completed","background":false,"completed_at":1769102501,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","output":[{"id":"msg_0dfed48e1052ad7f0069725ca4c2488193a652eba330c51e5b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The workspace ID you provided ('non_existing_id') is not valid. Workspace IDs must be valid UUIDs (typically 36 characters long). Please provide a valid workspace ID to create a new workspace build. If you need help finding your workspace ID, let me know!"}],"role":"assistant"}],"parallel_tool_calls":false,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Create a task.","name":"bmcp_coder_coder_create_task","parameters":{"properties":{"input":{"description":"Input/prompt for the task.","type":"string"},"template_version_id":{"description":"ID of the template version to create the task from.","type":"string"},"template_version_preset_id":{"description":"Optional ID of the template version preset to create the task from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a task. Omit or use the `me` keyword to create a task for the authenticated user.","type":"string"}},"required":["input","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template in Coder. First, you must create a template version.","name":"bmcp_coder_coder_create_template","parameters":{"properties":{"description":{"type":"string"},"display_name":{"type":"string"},"icon":{"description":"A URL to an icon to use.","type":"string"},"name":{"type":"string"},"version_id":{"description":"The ID of the version to use.","type":"string"}},"required":["name","display_name","description","version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new template version. This is a precursor to creating a template, or you can update an existing template.\n\nTemplates are Terraform defining a development environment. The provisioned infrastructure must run\nan Agent that connects to the Coder Control Plane to provide a rich experience.\n\nHere are some strict rules for creating a template version:\n- YOU MUST NOT use \"variable\" or \"output\" blocks in the Terraform code.\n- YOU MUST ALWAYS check template version logs after creation to ensure the template was imported successfully.\n\nWhen a template version is created, a Terraform Plan occurs that ensures the infrastructure\n_could_ be provisioned, but actual provisioning occurs when a workspace is created.\n\n\nThe Coder Terraform Provider can be imported like:\n\n```hcl\nterraform {\n required_providers {\n coder = {\n source = \"coder/coder\"\n }\n }\n}\n```\n\nA destroy does not occur when a user stops a workspace, but rather the transition changes:\n\n```hcl\ndata \"coder_workspace\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace.\n- name: The name of the workspace.\n- transition: Either \"start\" or \"stop\".\n- start_count: A computed count based on the transition field. If \"start\", this will be 1.\n\nAccess workspace owner information with:\n\n```hcl\ndata \"coder_workspace_owner\" \"me\" {}\n```\n\nThis data source provides the following fields:\n- id: The UUID of the workspace owner.\n- name: The name of the workspace owner.\n- full_name: The full name of the workspace owner.\n- email: The email of the workspace owner.\n- session_token: A token that can be used to authenticate the workspace owner. It is regenerated every time the workspace is started.\n- oidc_access_token: A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.\n\nParameters are defined in the template version. They are rendered in the UI on the workspace creation page:\n\n```hcl\nresource \"coder_parameter\" \"region\" {\n name = \"region\"\n type = \"string\"\n default = \"us-east-1\"\n}\n```\n\nThis resource accepts the following properties:\n- name: The name of the parameter.\n- default: The default value of the parameter.\n- type: The type of the parameter. Must be one of: \"string\", \"number\", \"bool\", or \"list(string)\".\n- display_name: The displayed name of the parameter as it will appear in the UI.\n- description: The description of the parameter as it will appear in the UI.\n- ephemeral: The value of an ephemeral parameter will not be preserved between consecutive workspace builds.\n- form_type: The type of this parameter. Must be one of: [radio, slider, input, dropdown, checkbox, switch, multi-select, tag-select, textarea, error].\n- icon: A URL to an icon to display in the UI.\n- mutable: Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution!\n- option: Each option block defines a value for a user to select from. (see below for nested schema)\n Required:\n - name: The name of the option.\n - value: The value of the option.\n Optional:\n - description: The description of the option as it will appear in the UI.\n - icon: A URL to an icon to display in the UI.\n\nA Workspace Agent runs on provisioned infrastructure to provide access to the workspace:\n\n```hcl\nresource \"coder_agent\" \"dev\" {\n arch = \"amd64\"\n os = \"linux\"\n}\n```\n\nThis resource accepts the following properties:\n- arch: The architecture of the agent. Must be one of: \"amd64\", \"arm64\", or \"armv7\".\n- os: The operating system of the agent. Must be one of: \"linux\", \"windows\", or \"darwin\".\n- auth: The authentication method for the agent. Must be one of: \"token\", \"google-instance-identity\", \"aws-instance-identity\", or \"azure-instance-identity\". It is insecure to pass the agent token via exposed variables to Virtual Machines. Instance Identity enables provisioned VMs to authenticate by instance ID on start.\n- dir: The starting directory when a user creates a shell session. Defaults to \"$HOME\".\n- env: A map of environment variables to set for the agent.\n- startup_script: A script to run after the agent starts. This script MUST exit eventually to signal that startup has completed. Use \"&\" or \"screen\" to run processes in the background.\n\nThis resource provides the following fields:\n- id: The UUID of the agent.\n- init_script: The script to run on provisioned infrastructure to fetch and start the agent.\n- token: Set the environment variable CODER_AGENT_TOKEN to this value to authenticate the agent.\n\nThe agent MUST be installed and started using the init_script. A utility like curl or wget to fetch the agent binary must exist in the provisioned infrastructure.\n\nExpose terminal or HTTP applications running in a workspace with:\n\n```hcl\nresource \"coder_app\" \"dev\" {\n agent_id = coder_agent.dev.id\n slug = \"my-app-name\"\n display_name = \"My App\"\n icon = \"https://my-app.com/icon.svg\"\n url = \"http://127.0.0.1:3000\"\n}\n```\n\nThis resource accepts the following properties:\n- agent_id: The ID of the agent to attach the app to.\n- slug: The slug of the app.\n- display_name: The displayed name of the app as it will appear in the UI.\n- icon: A URL to an icon to display in the UI.\n- url: An external url if external=true or a URL to be proxied to from inside the workspace. This should be of the form http://localhost:PORT[/SUBPATH]. Either command or url may be specified, but not both.\n- command: A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either command or url may be specified, but not both.\n- external: Whether this app is an external app. If true, the url will be opened in a new tab.\n\n\nThe Coder Server may not be authenticated with the infrastructure provider a user requests. In this scenario,\nthe user will need to provide credentials to the Coder Server before the workspace can be provisioned.\n\nHere are examples of provisioning the Coder Agent on specific infrastructure providers:\n\n\n// The agent is configured with \"aws-instance-identity\" auth.\nterraform {\n required_providers {\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n aws = {\n source = \"hashicorp/aws\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = false\n boundary = \"//\"\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${linux_user}\n\t// sudo: ALL=(ALL) NOPASSWD:ALL\n\t// shell: /bin/bash\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n hostname = local.hostname\n linux_user = local.linux_user\n })\n }\n\n part {\n filename = \"userdata.sh\"\n content_type = \"text/x-shellscript\"\n\n\t// Here is the content of the userdata.sh.tftpl file:\n\t// #!/bin/bash\n\t// sudo -u '${linux_user}' sh -c '${init_script}'\n content = templatefile(\"${path.module}/cloud-init/userdata.sh.tftpl\", {\n linux_user = local.linux_user\n\n init_script = try(coder_agent.dev[0].init_script, \"\")\n })\n }\n}\n\nresource \"aws_instance\" \"dev\" {\n ami = data.aws_ami.ubuntu.id\n availability_zone = \"${data.coder_parameter.region.value}a\"\n instance_type = data.coder_parameter.instance_type.value\n\n user_data = data.cloudinit_config.user_data.rendered\n tags = {\n Name = \"coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}\"\n }\n lifecycle {\n ignore_changes = [ami]\n }\n}\n\n\n\n// The agent is configured with \"google-instance-identity\" auth.\nterraform {\n required_providers {\n google = {\n source = \"hashicorp/google\"\n }\n }\n}\n\nresource \"google_compute_instance\" \"dev\" {\n zone = module.gcp_region.value\n count = data.coder_workspace.me.start_count\n name = \"coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-root\"\n machine_type = \"e2-medium\"\n network_interface {\n network = \"default\"\n access_config {\n // Ephemeral public IP\n }\n }\n boot_disk {\n auto_delete = false\n source = google_compute_disk.root.name\n }\n // In order to use google-instance-identity, a service account *must* be provided.\n service_account {\n email = data.google_compute_default_service_account.default.email\n scopes = [\"cloud-platform\"]\n }\n # ONLY FOR WINDOWS:\n # metadata = {\n # windows-startup-script-ps1 = coder_agent.main.init_script\n # }\n # The startup script runs as root with no $HOME environment set up, so instead of directly\n # running the agent init script, create a user (with a homedir, default shell and sudo\n # permissions) and execute the init script as that user.\n #\n # The agent MUST be started in here.\n metadata_startup_script = </dev/null 2>&1; then\n useradd -m -s /bin/bash \"${local.linux_user}\"\n echo \"${local.linux_user} ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/coder-user\nfi\n\nexec sudo -u \"${local.linux_user}\" sh -c '${coder_agent.main.init_script}'\nEOMETA\n}\n\n\n\n// The agent is configured with \"azure-instance-identity\" auth.\nterraform {\n required_providers {\n azurerm = {\n source = \"hashicorp/azurerm\"\n }\n cloudinit = {\n source = \"hashicorp/cloudinit\"\n }\n }\n}\n\ndata \"cloudinit_config\" \"user_data\" {\n gzip = false\n base64_encode = true\n\n boundary = \"//\"\n\n part {\n filename = \"cloud-config.yaml\"\n content_type = \"text/cloud-config\"\n\n\t// Here is the content of the cloud-config.yaml.tftpl file:\n\t// #cloud-config\n\t// cloud_final_modules:\n\t// - [scripts-user, always]\n\t// bootcmd:\n\t// # work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117\n\t// - until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done\n\t// device_aliases:\n\t// homedir: /dev/disk/azure/scsi1/lun10\n\t// disk_setup:\n\t// homedir:\n\t// table_type: gpt\n\t// layout: true\n\t// fs_setup:\n\t// - label: coder_home\n\t// filesystem: ext4\n\t// device: homedir.1\n\t// mounts:\n\t// - [\"LABEL=coder_home\", \"/home/${username}\"]\n\t// hostname: ${hostname}\n\t// users:\n\t// - name: ${username}\n\t// sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n\t// groups: sudo\n\t// shell: /bin/bash\n\t// packages:\n\t// - git\n\t// write_files:\n\t// - path: /opt/coder/init\n\t// permissions: \"0755\"\n\t// encoding: b64\n\t// content: ${init_script}\n\t// - path: /etc/systemd/system/coder-agent.service\n\t// permissions: \"0644\"\n\t// content: |\n\t// [Unit]\n\t// Description=Coder Agent\n\t// After=network-online.target\n\t// Wants=network-online.target\n\n\t// [Service]\n\t// User=${username}\n\t// ExecStart=/opt/coder/init\n\t// Restart=always\n\t// RestartSec=10\n\t// TimeoutStopSec=90\n\t// KillMode=process\n\n\t// OOMScoreAdjust=-900\n\t// SyslogIdentifier=coder-agent\n\n\t// [Install]\n\t// WantedBy=multi-user.target\n\t// runcmd:\n\t// - chown ${username}:${username} /home/${username}\n\t// - systemctl enable coder-agent\n\t// - systemctl start coder-agent\n content = templatefile(\"${path.module}/cloud-init/cloud-config.yaml.tftpl\", {\n username = \"coder\" # Ensure this user/group does not exist in your VM image\n init_script = base64encode(coder_agent.main.init_script)\n hostname = lower(data.coder_workspace.me.name)\n })\n }\n}\n\nresource \"azurerm_linux_virtual_machine\" \"main\" {\n count = data.coder_workspace.me.start_count\n name = \"vm\"\n resource_group_name = azurerm_resource_group.main.name\n location = azurerm_resource_group.main.location\n size = data.coder_parameter.instance_type.value\n // cloud-init overwrites this, so the value here doesn't matter\n admin_username = \"adminuser\"\n admin_ssh_key {\n public_key = tls_private_key.dummy.public_key_openssh\n username = \"adminuser\"\n }\n\n network_interface_ids = [\n azurerm_network_interface.main.id,\n ]\n computer_name = lower(data.coder_workspace.me.name)\n os_disk {\n caching = \"ReadWrite\"\n storage_account_type = \"Standard_LRS\"\n }\n source_image_reference {\n publisher = \"Canonical\"\n offer = \"0001-com-ubuntu-server-focal\"\n sku = \"20_04-lts-gen2\"\n version = \"latest\"\n }\n user_data = data.cloudinit_config.user_data.rendered\n}\n\n\n\nterraform {\n required_providers {\n coder = {\n source = \"kreuzwerker/docker\"\n }\n }\n}\n\n// The agent is configured with \"token\" auth.\n\nresource \"docker_container\" \"workspace\" {\n count = data.coder_workspace.me.start_count\n image = \"codercom/enterprise-base:ubuntu\"\n # Uses lower() to avoid Docker restriction on container names.\n name = \"coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}\"\n # Hostname makes the shell more user friendly: coder@my-workspace:~$\n hostname = data.coder_workspace.me.name\n # Use the docker gateway if the access URL is 127.0.0.1.\n entrypoint = [\"sh\", \"-c\", replace(coder_agent.main.init_script, \"/localhost|127\\\\.0\\\\.0\\\\.1/\", \"host.docker.internal\")]\n env = [\"CODER_AGENT_TOKEN=${coder_agent.main.token}\"]\n host {\n host = \"host.docker.internal\"\n ip = \"host-gateway\"\n }\n volumes {\n container_path = \"/home/coder\"\n volume_name = docker_volume.home_volume.name\n read_only = false\n }\n}\n\n\n\n// The agent is configured with \"token\" auth.\n\nresource \"kubernetes_deployment\" \"main\" {\n count = data.coder_workspace.me.start_count\n depends_on = [\n kubernetes_persistent_volume_claim.home\n ]\n wait_for_rollout = false\n metadata {\n name = \"coder-${data.coder_workspace.me.id}\"\n }\n\n spec {\n replicas = 1\n strategy {\n type = \"Recreate\"\n }\n\n template {\n spec {\n security_context {\n run_as_user = 1000\n fs_group = 1000\n run_as_non_root = true\n }\n\n container {\n name = \"dev\"\n image = \"codercom/enterprise-base:ubuntu\"\n image_pull_policy = \"Always\"\n command = [\"sh\", \"-c\", coder_agent.main.init_script]\n security_context {\n run_as_user = \"1000\"\n }\n env {\n name = \"CODER_AGENT_TOKEN\"\n value = coder_agent.main.token\n }\n }\n }\n }\n }\n}\n\n\nThe file_id provided is a reference to a tar file you have uploaded containing the Terraform.\n","name":"bmcp_coder_coder_create_template_version","parameters":{"properties":{"file_id":{"type":"string"},"template_id":{"type":"string"}},"required":["file_id"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace in Coder.\n\nIf a user is asking to \"test a template\", they are typically referring\nto creating a workspace from a template to ensure the infrastructure\nis provisioned correctly and the agent can connect to the control plane.\n\nBefore creating a workspace, always confirm the template choice with the user by:\n\n\t1. Listing the available templates that match their request.\n\t2. Recommending the most relevant option.\n\t2. Asking the user to confirm which template to use.\n\nIt is important to not create a workspace without confirming the template\nchoice with the user.\n\nAfter creating a workspace, watch the build logs and wait for the workspace to\nbe ready before trying to use or connect to the workspace.\n","name":"bmcp_coder_coder_create_workspace","parameters":{"properties":{"name":{"description":"Name of the workspace to create.","type":"string"},"rich_parameters":{"description":"Key/value pairs of rich parameters to pass to the template version to create the workspace.","type":"object"},"template_version_id":{"description":"ID of the template version to create the workspace from.","type":"string"},"user":{"description":"Username or ID of the user for which to create a workspace. Omit or use the `me` keyword to create a workspace for the authenticated user.","type":"string"}},"required":["user","template_version_id","name","rich_parameters"],"type":"object"},"strict":false},{"type":"function","description":"Create a new workspace build for an existing workspace. Use this to start, stop, or delete.\n\nAfter creating a workspace build, watch the build logs and wait for the\nworkspace build to complete before trying to start another build or use or\nconnect to the workspace.\n","name":"bmcp_coder_coder_create_workspace_build","parameters":{"properties":{"template_version_id":{"description":"(Optional) The template version ID to use for the workspace build. If not provided, the previously built version will be used.","type":"string"},"transition":{"description":"The transition to perform. Must be one of: start, stop, delete","enum":["start","stop","delete"],"type":"string"},"workspace_id":{"type":"string"}},"required":["workspace_id","transition"],"type":"object"},"strict":false},{"type":"function","description":"Delete a task.","name":"bmcp_coder_coder_delete_task","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to delete. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Delete a template. This is irreversible.","name":"bmcp_coder_coder_delete_template","parameters":{"properties":{"template_id":{"type":"string"}},"required":["template_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the currently authenticated user, similar to the `whoami` command.","name":"bmcp_coder_coder_get_authenticated_user","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a task.","name":"bmcp_coder_coder_get_task_logs","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to query. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the status of a task.","name":"bmcp_coder_coder_get_task_status","parameters":{"properties":{"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to get. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a template version. This is useful to check whether a template version successfully imports or not.","name":"bmcp_coder_coder_get_template_version_logs","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Get a workspace by name or ID.\n\nThis returns more data than list_workspaces to reduce token usage.","name":"bmcp_coder_coder_get_workspace","parameters":{"properties":{"workspace_id":{"description":"The workspace ID or name in the format [owner/]workspace. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace agent.\n\n\t\tMore logs may appear after this call. It does not wait for the agent to finish.","name":"bmcp_coder_coder_get_workspace_agent_logs","parameters":{"properties":{"workspace_agent_id":{"type":"string"}},"required":["workspace_agent_id"],"type":"object"},"strict":false},{"type":"function","description":"Get the logs of a workspace build.\n\n\t\tUseful for checking whether a workspace builds successfully or not.","name":"bmcp_coder_coder_get_workspace_build_logs","parameters":{"properties":{"workspace_build_id":{"type":"string"}},"required":["workspace_build_id"],"type":"object"},"strict":false},{"type":"function","description":"List tasks.","name":"bmcp_coder_coder_list_tasks","parameters":{"properties":{"status":{"description":"Optional filter by task status.","type":"string"},"user":{"description":"Username or ID of the user for which to list tasks. Omit or use the `me` keyword to list tasks for the authenticated user.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Lists templates for the authenticated user.","name":"bmcp_coder_coder_list_templates","parameters":{"properties":{},"type":"object"},"strict":false},{"type":"function","description":"Lists workspaces for the authenticated user.","name":"bmcp_coder_coder_list_workspaces","parameters":{"properties":{"owner":{"description":"The owner of the workspaces to list. Use \"me\" to list workspaces for the authenticated user. If you do not specify an owner, \"me\" will be assumed by default.","type":"string"}},"type":"object"},"strict":false},{"type":"function","description":"Send input to a running task.","name":"bmcp_coder_coder_send_task_input","parameters":{"properties":{"input":{"description":"The input to send to the task.","type":"string"},"task_id":{"description":"ID or workspace identifier in the format [owner/]workspace[.agent] for the task to prompt. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["task_id","input"],"type":"object"},"strict":false},{"type":"function","description":"Get the parameters for a template version. You can refer to these as workspace parameters to the user, as they are typically important for creating a workspace.","name":"bmcp_coder_coder_template_version_parameters","parameters":{"properties":{"template_version_id":{"type":"string"}},"required":["template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Update the active version of a template. This is helpful when iterating on templates.","name":"bmcp_coder_coder_update_template_active_version","parameters":{"properties":{"template_id":{"type":"string"},"template_version_id":{"type":"string"}},"required":["template_id","template_version_id"],"type":"object"},"strict":false},{"type":"function","description":"Create and upload a tar file by key/value mapping of file names to file contents. Use this to create template versions. Reference the tool description of \"create_template_version\" to understand template requirements.","name":"bmcp_coder_coder_upload_tar_file","parameters":{"properties":{"files":{"description":"A map of file names to file contents.","type":"object"}},"required":["files"],"type":"object"},"strict":false},{"type":"function","description":"Execute a bash command in a Coder workspace.\n\nThis tool provides the same functionality as the 'coder ssh ' CLI command.\nIt automatically starts the workspace if it's stopped and waits for the agent to be ready.\nThe output is trimmed of leading and trailing whitespace.\n\nThe workspace parameter supports various formats:\n- workspace (uses current user)\n- owner/workspace\n- owner--workspace\n- workspace.agent (specific agent)\n- owner/workspace.agent\n\nThe timeout_ms parameter specifies the command timeout in milliseconds (defaults to 60000ms, maximum of 300000ms).\nIf the command times out, all output captured up to that point is returned with a cancellation message.\n\nFor background commands (background: true), output is captured until the timeout is reached, then the command\ncontinues running in the background. The captured output is returned as the result.\n\nFor file operations (list, write, edit), always prefer the dedicated file tools.\nDo not use bash commands (ls, cat, echo, heredoc, etc.) to list, write, or read\nfiles when the file tools are available. The bash tool should be used for:\n\n\t- Running commands and scripts\n\t- Installing packages\n\t- Starting services\n\t- Executing programs\n\nExamples:\n- workspace: \"john/dev-env\", command: \"git status\", timeout_ms: 30000\n- workspace: \"my-workspace\", command: \"npm run dev\", background: true, timeout_ms: 10000\n- workspace: \"my-workspace.main\", command: \"docker ps\"","name":"bmcp_coder_coder_workspace_bash","parameters":{"properties":{"background":{"description":"Whether to run the command in the background. Output is captured until timeout, then the command continues running in the background.","type":"boolean"},"command":{"description":"The bash command to execute in the workspace.","type":"string"},"timeout_ms":{"default":60000,"description":"Command timeout in milliseconds. Defaults to 60000ms (60 seconds) if not specified.","minimum":1,"type":"integer"},"workspace":{"description":"The workspace name in format [owner/]workspace[.agent]. If owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","command"],"type":"object"},"strict":false},{"type":"function","description":"Edit a file in a workspace.","name":"bmcp_coder_coder_workspace_edit_file","parameters":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","edits"],"type":"object"},"strict":false},{"type":"function","description":"Edit one or more files in a workspace.","name":"bmcp_coder_coder_workspace_edit_files","parameters":{"properties":{"files":{"description":"An array of files to edit.","items":{"properties":{"edits":{"description":"An array of edit operations.","items":{"properties":{"replace":{"description":"The new string that replaces the old string.","type":"string"},"search":{"description":"The old string to replace.","type":"string"}},"required":["search","replace"],"type":"object"},"type":"array"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"}},"required":["path","edits"],"type":"object"},"type":"array"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","files"],"type":"object"},"strict":false},{"type":"function","description":"List the URLs of Coder apps running in a workspace for a single agent.","name":"bmcp_coder_coder_workspace_list_apps","parameters":{"properties":{"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace"],"type":"object"},"strict":false},{"type":"function","description":"List directories in a workspace.","name":"bmcp_coder_coder_workspace_ls","parameters":{"properties":{"path":{"description":"The absolute path of the directory in the workspace to list.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Fetch URLs that forward to the specified port.","name":"bmcp_coder_coder_workspace_port_forward","parameters":{"properties":{"port":{"description":"The port to forward.","type":"number"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["workspace","port"],"type":"object"},"strict":false},{"type":"function","description":"Read from a file in a workspace.","name":"bmcp_coder_coder_workspace_read_file","parameters":{"properties":{"limit":{"description":"The number of bytes to read. Cannot exceed 1 MiB. Defaults to the full size of the file or 1 MiB, whichever is lower.","type":"integer"},"offset":{"description":"A byte offset indicating where in the file to start reading. Defaults to zero. An empty string indicates the end of the file has been reached.","type":"integer"},"path":{"description":"The absolute path of the file to read in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace"],"type":"object"},"strict":false},{"type":"function","description":"Write a file in a workspace.\n\nIf a file write fails due to syntax errors or encoding issues, do NOT switch\nto using bash commands as a workaround. Instead:\n\n\t1. Read the error message carefully to identify the issue\n\t2. Fix the content encoding/syntax\n\t3. Retry with this tool\n\nThe content parameter expects base64-encoded bytes. Ensure your source content\nis correct before encoding it. If you encounter errors, decode and verify the\ncontent you are trying to write, then re-encode it properly.\n","name":"bmcp_coder_coder_workspace_write_file","parameters":{"properties":{"content":{"description":"The base64-encoded bytes to write to the file.","type":"string"},"path":{"description":"The absolute path of the file to write in the workspace.","type":"string"},"workspace":{"description":"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.","type":"string"}},"required":["path","workspace","content"],"type":"object"},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":6346,"input_tokens_details":{"cached_tokens":100,"cache_write_tokens":9},"output_tokens":56,"output_tokens_details":{"reasoning_tokens":5},"total_tokens":6402},"user":null,"metadata":{}},"sequence_number":61}
diff --git a/aibridge/intercept/chatcompletions/base.go b/aibridge/intercept/chatcompletions/base.go
index 4e6db3abe60..415b4d46bc3 100644
--- a/aibridge/intercept/chatcompletions/base.go
+++ b/aibridge/intercept/chatcompletions/base.go
@@ -227,14 +227,15 @@ func (i *interceptionBase) hasInjectableTools() bool {
}
// recordTokenUsage records the token usage for a single completion, accounting
-// for cached tokens included in the prompt token count.
+// for cache read and write tokens included in the prompt token count.
func (i *interceptionBase) recordTokenUsage(ctx context.Context, msgID string, usage openai.CompletionUsage) {
_ = i.recorder.RecordTokenUsage(ctx, &recorder.TokenUsageRecord{
- InterceptionID: i.ID().String(),
- MsgID: msgID,
- Input: calculateActualInputTokenUsage(usage),
- Output: usage.CompletionTokens,
- CacheReadInputTokens: usage.PromptTokensDetails.CachedTokens,
+ InterceptionID: i.ID().String(),
+ MsgID: msgID,
+ Input: calculateActualInputTokenUsage(usage),
+ Output: usage.CompletionTokens,
+ CacheReadInputTokens: usage.PromptTokensDetails.CachedTokens,
+ CacheWriteInputTokens: usage.PromptTokensDetails.CacheWriteTokens,
ExtraTokenTypes: map[string]int64{
"prompt_audio": usage.PromptTokensDetails.AudioTokens,
"completion_accepted_prediction": usage.CompletionTokensDetails.AcceptedPredictionTokens,
@@ -245,7 +246,7 @@ func (i *interceptionBase) recordTokenUsage(ctx context.Context, msgID string, u
})
}
-func sumUsage(ref, in openai.CompletionUsage) openai.CompletionUsage {
+func sumUsage(ref openai.CompletionUsage, in openai.CompletionUsage) openai.CompletionUsage {
return openai.CompletionUsage{
CompletionTokens: ref.CompletionTokens + in.CompletionTokens,
PromptTokens: ref.PromptTokens + in.PromptTokens,
@@ -257,17 +258,18 @@ func sumUsage(ref, in openai.CompletionUsage) openai.CompletionUsage {
RejectedPredictionTokens: ref.CompletionTokensDetails.RejectedPredictionTokens + in.CompletionTokensDetails.RejectedPredictionTokens,
},
PromptTokensDetails: openai.CompletionUsagePromptTokensDetails{
- AudioTokens: ref.PromptTokensDetails.AudioTokens + in.PromptTokensDetails.AudioTokens,
- CachedTokens: ref.PromptTokensDetails.CachedTokens + in.PromptTokensDetails.CachedTokens,
+ AudioTokens: ref.PromptTokensDetails.AudioTokens + in.PromptTokensDetails.AudioTokens,
+ CachedTokens: ref.PromptTokensDetails.CachedTokens + in.PromptTokensDetails.CachedTokens,
+ CacheWriteTokens: ref.PromptTokensDetails.CacheWriteTokens + in.PromptTokensDetails.CacheWriteTokens,
},
}
}
-// calculateActualInputTokenUsage accounts for cached tokens which are included in [openai.CompletionUsage].PromptTokens.
+// calculateActualInputTokenUsage calculates ordinary input tokens.
+// in.PromptTokens contains sum of all prompt tokens including
+// cache read and write tokens which are priced differently.
func calculateActualInputTokenUsage(in openai.CompletionUsage) int64 {
- // Input *includes* the cached tokens, so we subtract them here to reflect actual input token usage.
- // The original value can be reconstructed by adding CachedTokens back to Input.
- // See https://platform.openai.com/docs/api-reference/usage/completions_object#usage/completions_object-input_tokens.
- return max(0, in.PromptTokens /* The aggregated number of text input tokens used, including cached tokens. */ -
- in.PromptTokensDetails.CachedTokens /* The aggregated number of text input tokens that has been cached from previous requests. */)
+ return max(0, in.PromptTokens-
+ in.PromptTokensDetails.CachedTokens-
+ in.PromptTokensDetails.CacheWriteTokens)
}
diff --git a/aibridge/intercept/chatcompletions/base_internal_test.go b/aibridge/intercept/chatcompletions/base_internal_test.go
index faf53fd7610..ca16191abee 100644
--- a/aibridge/intercept/chatcompletions/base_internal_test.go
+++ b/aibridge/intercept/chatcompletions/base_internal_test.go
@@ -41,8 +41,9 @@ func TestRecordTokenUsage(t *testing.T) {
CompletionTokens: 50,
TotalTokens: 150,
PromptTokensDetails: openai.CompletionUsagePromptTokensDetails{
- CachedTokens: 40,
- AudioTokens: 3,
+ CachedTokens: 40,
+ CacheWriteTokens: 25,
+ AudioTokens: 3,
},
CompletionTokensDetails: openai.CompletionUsageCompletionTokensDetails{
AcceptedPredictionTokens: 7,
@@ -52,11 +53,12 @@ func TestRecordTokenUsage(t *testing.T) {
},
},
expected: &recorder.TokenUsageRecord{
- InterceptionID: id.String(),
- MsgID: "cmpl_full",
- Input: 60, // 100 prompt - 40 cached
- Output: 50,
- CacheReadInputTokens: 40,
+ InterceptionID: id.String(),
+ MsgID: "cmpl_full",
+ Input: 35, // 100 prompt - 40 cache read - 25 cache write
+ Output: 50,
+ CacheReadInputTokens: 40,
+ CacheWriteInputTokens: 25,
ExtraTokenTypes: map[string]int64{
"prompt_audio": 3,
"completion_accepted_prediction": 7,
@@ -96,20 +98,22 @@ func TestRecordTokenUsage(t *testing.T) {
// CachedTokens. Input must clamp to 0 so it never panics a
// Prometheus counter when used as an increment.
name: "cached_tokens_exceed_prompt_tokens_clamps_to_zero",
- msgID: "cmpl_clamp",
+ msgID: "cmpl_cached_exceed",
usage: openai.CompletionUsage{
PromptTokens: 40,
CompletionTokens: 20,
PromptTokensDetails: openai.CompletionUsagePromptTokensDetails{
- CachedTokens: 100,
+ CachedTokens: 30,
+ CacheWriteTokens: 30,
},
},
expected: &recorder.TokenUsageRecord{
- InterceptionID: id.String(),
- MsgID: "cmpl_clamp",
- Input: 0, // max(0, 40 prompt - 100 cached)
- Output: 20,
- CacheReadInputTokens: 100,
+ InterceptionID: id.String(),
+ MsgID: "cmpl_cached_exceed",
+ Input: 0, // max(0, 40 prompt - 60 cached)
+ Output: 20,
+ CacheReadInputTokens: 30,
+ CacheWriteInputTokens: 30,
ExtraTokenTypes: map[string]int64{
"prompt_audio": 0,
"completion_accepted_prediction": 0,
@@ -144,6 +148,60 @@ func TestRecordTokenUsage(t *testing.T) {
}
}
+func TestSumUsage(t *testing.T) {
+ t.Parallel()
+
+ first := openai.CompletionUsage{
+ PromptTokens: 100,
+ CompletionTokens: 50,
+ TotalTokens: 150,
+ PromptTokensDetails: openai.CompletionUsagePromptTokensDetails{
+ CachedTokens: 10,
+ CacheWriteTokens: 20,
+ AudioTokens: 30,
+ },
+ CompletionTokensDetails: openai.CompletionUsageCompletionTokensDetails{
+ AcceptedPredictionTokens: 40,
+ RejectedPredictionTokens: 50,
+ AudioTokens: 60,
+ ReasoningTokens: 70,
+ },
+ }
+ second := openai.CompletionUsage{
+ PromptTokens: 200,
+ CompletionTokens: 100,
+ TotalTokens: 300,
+ PromptTokensDetails: openai.CompletionUsagePromptTokensDetails{
+ CachedTokens: 1,
+ CacheWriteTokens: 2,
+ AudioTokens: 3,
+ },
+ CompletionTokensDetails: openai.CompletionUsageCompletionTokensDetails{
+ AcceptedPredictionTokens: 4,
+ RejectedPredictionTokens: 5,
+ AudioTokens: 6,
+ ReasoningTokens: 7,
+ },
+ }
+
+ require.Equal(t, openai.CompletionUsage{
+ PromptTokens: 300,
+ CompletionTokens: 150,
+ TotalTokens: 450,
+ PromptTokensDetails: openai.CompletionUsagePromptTokensDetails{
+ CachedTokens: 11,
+ CacheWriteTokens: 22,
+ AudioTokens: 33,
+ },
+ CompletionTokensDetails: openai.CompletionUsageCompletionTokensDetails{
+ AcceptedPredictionTokens: 44,
+ RejectedPredictionTokens: 55,
+ AudioTokens: 66,
+ ReasoningTokens: 77,
+ },
+ }, sumUsage(first, second))
+}
+
func TestScanForCorrelatingToolCallID(t *testing.T) {
t.Parallel()
diff --git a/aibridge/intercept/chatcompletions/streaming.go b/aibridge/intercept/chatcompletions/streaming.go
index b9fdf0715fd..8b0c0672ef3 100644
--- a/aibridge/intercept/chatcompletions/streaming.go
+++ b/aibridge/intercept/chatcompletions/streaming.go
@@ -121,6 +121,10 @@ func (i *StreamingInterception) ProcessRequest(w http.ResponseWriter, r *http.Re
stream *ssestream.Stream[openai.ChatCompletionChunk]
lastErr error
interceptionErr error
+
+ // Cumulative token usage across all previous iterations.
+ // Added to the usage of chunk before relaying to the client.
+ cumulativeUsage openai.CompletionUsage
)
// Sum the key attempts across all iterations and record once when the
@@ -224,7 +228,7 @@ func (i *StreamingInterception) ProcessRequest(w http.ResponseWriter, r *http.Re
}
// Marshal and relay chunk to client.
- payload, err := i.marshalChunk(&chunk, i.ID(), processor)
+ payload, err := i.marshalChunk(&chunk, i.ID(), processor, cumulativeUsage)
if err != nil {
logger.Warn(ctx, "failed to marshal chunk", slog.Error(err), slog.F("chunk", chunk.RawJSON()))
lastErr = xerrors.Errorf("marshal chunk: %w", err)
@@ -270,8 +274,10 @@ func (i *StreamingInterception) ProcessRequest(w http.ResponseWriter, r *http.Re
prompt = nil
}
- if lastUsage := processor.lastUsage; lastUsage.CompletionTokens > 0 {
+ if processor.hasUsage {
+ lastUsage := processor.lastUsage
i.recordTokenUsage(streamCtx, processor.getMsgID(), lastUsage)
+ cumulativeUsage = sumUsage(cumulativeUsage, lastUsage)
}
if iterationStarted {
@@ -414,7 +420,7 @@ func (i *StreamingInterception) getInjectedToolByName(name string) *mcp.Tool {
// is as close to the original as possible. Structs from openai library lack
// `omitzero/omitempty` annotations which adds additional empty fields
// when marshaling structs. Those additional empty fields can break Codex client.
-func (i *StreamingInterception) marshalChunk(chunk *openai.ChatCompletionChunk, id uuid.UUID, prc *streamProcessor) ([]byte, error) {
+func (i *StreamingInterception) marshalChunk(chunk *openai.ChatCompletionChunk, id uuid.UUID, prc *streamProcessor, previousUsage openai.CompletionUsage) ([]byte, error) {
// Normalize the response ID because injected tool calls span multiple upstream invocations.
sj, err := sjson.Set(chunk.RawJSON(), "id", id.String())
if err != nil {
@@ -422,7 +428,7 @@ func (i *StreamingInterception) marshalChunk(chunk *openai.ChatCompletionChunk,
}
if chunk.JSON.Usage.Valid() {
- sj, err = sjson.Set(sj, "usage", prc.lastUsage)
+ sj, err = sjson.Set(sj, "usage", sumUsage(previousUsage, prc.lastUsage))
if err != nil {
return nil, xerrors.Errorf("marshal chunk usage failed: %w", err)
}
@@ -497,6 +503,7 @@ type streamProcessor struct {
getInjectedToolFunc func(string) *mcp.Tool
lastUsage openai.CompletionUsage
+ hasUsage bool
}
func newStreamProcessor(ctx context.Context, logger slog.Logger, isToolInjectedFunc func(string) *mcp.Tool) *streamProcessor {
@@ -520,6 +527,7 @@ func (s *streamProcessor) process(chunk openai.ChatCompletionChunk) bool {
// latest valid usage is authoritative. Never sum across chunks.
if chunk.JSON.Usage.Valid() {
s.lastUsage = chunk.Usage
+ s.hasUsage = true
}
// If the stream has reached a terminal state (i.e. call a tool), and this tool is injected,
diff --git a/aibridge/intercept/chatcompletions/streaming_internal_test.go b/aibridge/intercept/chatcompletions/streaming_internal_test.go
index 6abf862e90f..b6f36173af1 100644
--- a/aibridge/intercept/chatcompletions/streaming_internal_test.go
+++ b/aibridge/intercept/chatcompletions/streaming_internal_test.go
@@ -17,6 +17,7 @@ import (
"cdr.dev/slog/v3/sloggers/slogtest"
"github.com/coder/coder/v2/aibridge/intercept"
"github.com/coder/coder/v2/aibridge/internal/testutil"
+ "github.com/coder/coder/v2/aibridge/recorder"
)
func TestStreamProcessorUsage(t *testing.T) {
@@ -69,7 +70,7 @@ func TestStreamProcessorUsage(t *testing.T) {
require.True(t, processor.process(chunk))
if chunk.JSON.Usage.Valid() {
var err error
- relayed, err = interceptor.marshalChunk(&chunk, interceptor.ID(), processor)
+ relayed, err = interceptor.marshalChunk(&chunk, interceptor.ID(), processor, openai.CompletionUsage{})
require.NoError(t, err)
}
}
@@ -91,6 +92,62 @@ func TestStreamProcessorUsage(t *testing.T) {
}
}
+func TestStreamingInterceptionRecordsLatestUsageWithZeroCompletionTokens(t *testing.T) {
+ t.Parallel()
+
+ upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.Header().Set("Content-Type", "text/event-stream")
+ _, _ = w.Write([]byte("data: {\"id\":\"chatcmpl-zero-completion\",\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"one\"}}],\"usage\":{\"prompt_tokens\":40,\"completion_tokens\":0,\"total_tokens\":40,\"prompt_tokens_details\":{\"cached_tokens\":5,\"cache_write_tokens\":3}}}\n\n"))
+ _, _ = w.Write([]byte("data: {\"id\":\"chatcmpl-zero-completion\",\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":50,\"completion_tokens\":0,\"total_tokens\":50,\"prompt_tokens_details\":{\"cached_tokens\":10,\"cache_write_tokens\":5}}}\n\n"))
+ _, _ = w.Write([]byte("data: [DONE]\n\n"))
+ }))
+ t.Cleanup(upstream.Close)
+
+ req := &ChatCompletionNewParamsWrapper{
+ ChatCompletionNewParams: openai.ChatCompletionNewParams{
+ Model: "gpt-4",
+ Messages: []openai.ChatCompletionMessageParamUnion{
+ openai.UserMessage("hello"),
+ },
+ },
+ Stream: true,
+ }
+ httpReq := httptest.NewRequest(http.MethodPost, "/chat/completions", nil)
+ interceptor := NewStreamingInterceptor(
+ uuid.New(),
+ req,
+ intercept.Config{BaseURL: upstream.URL},
+ intercept.BYOK{Secret: "test-key", Header: intercept.AuthHeaderAuthorization},
+ httpReq.Header,
+ otel.Tracer("test"),
+ )
+ logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug)
+ rec := &testutil.MockRecorder{}
+ interceptor.Setup(logger, rec, nil)
+
+ w := httptest.NewRecorder()
+ require.NoError(t, interceptor.ProcessRequest(w, httpReq))
+
+ usages := rec.RecordedTokenUsages()
+ require.Len(t, usages, 1)
+ require.Equal(t, &recorder.TokenUsageRecord{
+ InterceptionID: interceptor.ID().String(),
+ MsgID: "chatcmpl-zero-completion",
+ Input: 35,
+ Output: 0,
+ CacheReadInputTokens: 10,
+ CacheWriteInputTokens: 5,
+ CreatedAt: usages[0].CreatedAt,
+ ExtraTokenTypes: map[string]int64{
+ "prompt_audio": 0,
+ "completion_accepted_prediction": 0,
+ "completion_rejected_prediction": 0,
+ "completion_audio": 0,
+ "completion_reasoning": 0,
+ },
+ }, usages[0])
+}
+
// Test that when the upstream provider returns an error before streaming starts,
// the error status code and body are correctly relayed to the client.
func TestStreamingInterception_RelaysUpstreamErrorToClient(t *testing.T) {
diff --git a/aibridge/intercept/responses/base.go b/aibridge/intercept/responses/base.go
index e048f95a074..ec6e128cf0b 100644
--- a/aibridge/intercept/responses/base.go
+++ b/aibridge/intercept/responses/base.go
@@ -57,6 +57,21 @@ type responsesInterceptionBase struct {
mcpProxy mcp.ServerProxier
}
+func sumUsage(ref responses.ResponseUsage, in responses.ResponseUsage) responses.ResponseUsage {
+ return responses.ResponseUsage{
+ InputTokens: ref.InputTokens + in.InputTokens,
+ OutputTokens: ref.OutputTokens + in.OutputTokens,
+ TotalTokens: ref.TotalTokens + in.TotalTokens,
+ InputTokensDetails: responses.ResponseUsageInputTokensDetails{
+ CachedTokens: ref.InputTokensDetails.CachedTokens + in.InputTokensDetails.CachedTokens,
+ CacheWriteTokens: ref.InputTokensDetails.CacheWriteTokens + in.InputTokensDetails.CacheWriteTokens,
+ },
+ OutputTokensDetails: responses.ResponseUsageOutputTokensDetails{
+ ReasoningTokens: ref.OutputTokensDetails.ReasoningTokens + in.OutputTokensDetails.ReasoningTokens,
+ },
+ }
+}
+
// newResponsesService builds the SDK service used for upstream calls.
func (i *responsesInterceptionBase) newResponsesService(ctx context.Context) responses.ResponseService {
var opts []option.RequestOption
@@ -327,16 +342,19 @@ func (i *responsesInterceptionBase) recordTokenUsage(ctx context.Context, respon
usage := response.Usage
- // Keeping logic consistent with chat completions
- // Input *includes* the cached tokens, so we subtract them here to reflect actual input token usage.
- inputNonCacheTokens := max(0, usage.InputTokens-usage.InputTokensDetails.CachedTokens)
+ // InputTokens include cache read and write tokens, see OpenAI spending controller cookbook for reference:
+ // https://github.com/openai/openai-cookbook/blob/51c769595490f7513d4bd7c6e7700a7ab8dedbd4/articles/per_run_spending_controller_responses_api.md?plain=1#L197
+ inputNonCacheTokens := max(0, usage.InputTokens-
+ usage.InputTokensDetails.CachedTokens-
+ usage.InputTokensDetails.CacheWriteTokens)
if err := i.recorder.RecordTokenUsage(ctx, &recorder.TokenUsageRecord{
- InterceptionID: i.ID().String(),
- MsgID: response.ID,
- Input: inputNonCacheTokens,
- Output: usage.OutputTokens,
- CacheReadInputTokens: usage.InputTokensDetails.CachedTokens,
+ InterceptionID: i.ID().String(),
+ MsgID: response.ID,
+ Input: inputNonCacheTokens,
+ Output: usage.OutputTokens,
+ CacheReadInputTokens: usage.InputTokensDetails.CachedTokens,
+ CacheWriteInputTokens: usage.InputTokensDetails.CacheWriteTokens,
ExtraTokenTypes: map[string]int64{
"output_reasoning": usage.OutputTokensDetails.ReasoningTokens,
"total_tokens": usage.TotalTokens,
@@ -448,6 +466,14 @@ func (r *responseCopier) readAll() ([]byte, error) {
// forwardResp writes whole response as received to ResponseWriter
func (r *responseCopier) forwardResp(w http.ResponseWriter) error {
+ b, err := r.readAll()
+ if err != nil {
+ return xerrors.Errorf("failed to read response body: %w", err)
+ }
+ return r.forwardBytes(w, b)
+}
+
+func (r *responseCopier) forwardBytes(w http.ResponseWriter, b []byte) error {
// no response was received, nothing to forward
if !r.responseReceived.Load() {
return nil
@@ -461,11 +487,6 @@ func (r *responseCopier) forwardResp(w http.ResponseWriter) error {
}
w.WriteHeader(r.responseStatus)
- b, err := r.readAll()
- if err != nil {
- return xerrors.Errorf("failed to read response body: %w", err)
- }
-
if _, err := w.Write(b); err != nil {
return xerrors.Errorf("failed to write response body: %w", err)
}
diff --git a/aibridge/intercept/responses/base_internal_test.go b/aibridge/intercept/responses/base_internal_test.go
index 8b003631147..dcc8e107e91 100644
--- a/aibridge/intercept/responses/base_internal_test.go
+++ b/aibridge/intercept/responses/base_internal_test.go
@@ -358,6 +358,48 @@ func TestParseJSONArgs(t *testing.T) {
}
}
+func TestSumUsage(t *testing.T) {
+ t.Parallel()
+
+ first := oairesponses.ResponseUsage{
+ InputTokens: 100,
+ OutputTokens: 50,
+ TotalTokens: 150,
+ InputTokensDetails: oairesponses.ResponseUsageInputTokensDetails{
+ CachedTokens: 10,
+ CacheWriteTokens: 20,
+ },
+ OutputTokensDetails: oairesponses.ResponseUsageOutputTokensDetails{
+ ReasoningTokens: 30,
+ },
+ }
+ second := oairesponses.ResponseUsage{
+ InputTokens: 200,
+ OutputTokens: 100,
+ TotalTokens: 300,
+ InputTokensDetails: oairesponses.ResponseUsageInputTokensDetails{
+ CachedTokens: 1,
+ CacheWriteTokens: 2,
+ },
+ OutputTokensDetails: oairesponses.ResponseUsageOutputTokensDetails{
+ ReasoningTokens: 3,
+ },
+ }
+
+ require.Equal(t, oairesponses.ResponseUsage{
+ InputTokens: 300,
+ OutputTokens: 150,
+ TotalTokens: 450,
+ InputTokensDetails: oairesponses.ResponseUsageInputTokensDetails{
+ CachedTokens: 11,
+ CacheWriteTokens: 22,
+ },
+ OutputTokensDetails: oairesponses.ResponseUsageOutputTokensDetails{
+ ReasoningTokens: 33,
+ },
+ }, sumUsage(first, second))
+}
+
func TestRecordTokenUsage(t *testing.T) {
t.Parallel()
@@ -382,7 +424,8 @@ func TestRecordTokenUsage(t *testing.T) {
OutputTokens: 20,
TotalTokens: 30,
InputTokensDetails: oairesponses.ResponseUsageInputTokensDetails{
- CachedTokens: 5,
+ CachedTokens: 5,
+ CacheWriteTokens: 3,
},
OutputTokensDetails: oairesponses.ResponseUsageOutputTokensDetails{
ReasoningTokens: 5,
@@ -390,11 +433,12 @@ func TestRecordTokenUsage(t *testing.T) {
},
},
expected: &recorder.TokenUsageRecord{
- InterceptionID: id.String(),
- MsgID: "resp_full",
- Input: 5, // 10 input - 5 cached
- Output: 20,
- CacheReadInputTokens: 5,
+ InterceptionID: id.String(),
+ MsgID: "resp_full",
+ Input: 2, // 10 input - 5 cache read - 3 cache write
+ Output: 20,
+ CacheReadInputTokens: 5,
+ CacheWriteInputTokens: 3,
ExtraTokenTypes: map[string]int64{
"output_reasoning": 5,
"total_tokens": 30,
@@ -413,16 +457,18 @@ func TestRecordTokenUsage(t *testing.T) {
OutputTokens: 20,
TotalTokens: 30,
InputTokensDetails: oairesponses.ResponseUsageInputTokensDetails{
- CachedTokens: 40,
+ CachedTokens: 20,
+ CacheWriteTokens: 20,
},
},
},
expected: &recorder.TokenUsageRecord{
- InterceptionID: id.String(),
- MsgID: "resp_clamp",
- Input: 0, // max(0, 10 input - 40 cached)
- Output: 20,
- CacheReadInputTokens: 40,
+ InterceptionID: id.String(),
+ MsgID: "resp_clamp",
+ Input: 0, // max(0, 10 input - 20 cache read - 20 cache write)
+ Output: 20,
+ CacheReadInputTokens: 20,
+ CacheWriteInputTokens: 20,
ExtraTokenTypes: map[string]int64{
"output_reasoning": 0,
"total_tokens": 30,
diff --git a/aibridge/intercept/responses/blocking.go b/aibridge/intercept/responses/blocking.go
index 9bc4a183b55..1a4157a5102 100644
--- a/aibridge/intercept/responses/blocking.go
+++ b/aibridge/intercept/responses/blocking.go
@@ -9,6 +9,7 @@ import (
"github.com/google/uuid"
"github.com/openai/openai-go/v3/option"
"github.com/openai/openai-go/v3/responses"
+ "github.com/tidwall/sjson"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"golang.org/x/xerrors"
@@ -69,10 +70,13 @@ func (i *BlockingResponsesInterceptor) ProcessRequest(w http.ResponseWriter, r *
i.injectTools()
var (
- response *responses.Response
- upstreamErr error
- respCopy responseCopier
- firstResponseID string
+ response *responses.Response
+ upstreamErr error
+ innerLoopErr error
+ respCopy responseCopier
+ firstResponseID string
+ cumulativeUsage responses.ResponseUsage
+ innerLoopIterations int
)
prompt, promptFound, err := i.reqPayload.lastUserPrompt(ctx, i.logger)
@@ -126,13 +130,15 @@ func (i *BlockingResponsesInterceptor) ProcessRequest(w http.ResponseWriter, r *
}
i.recordTokenUsage(ctx, response)
+ cumulativeUsage = sumUsage(cumulativeUsage, response.Usage)
+ innerLoopIterations++
i.recordModelThoughts(ctx, response)
// Check if there any injected tools to invoke.
pending := i.getPendingInjectedToolCalls(response)
- shouldLoop, err = i.handleInnerAgenticLoop(ctx, pending, response)
- if err != nil {
- i.sendCustomErr(ctx, w, http.StatusInternalServerError, err)
+ shouldLoop, innerLoopErr = i.handleInnerAgenticLoop(ctx, pending, response)
+ if innerLoopErr != nil {
+ i.sendCustomErr(ctx, w, http.StatusInternalServerError, innerLoopErr)
shouldLoop = false
}
}
@@ -142,16 +148,44 @@ func (i *BlockingResponsesInterceptor) ProcessRequest(w http.ResponseWriter, r *
}
i.recordNonInjectedToolUsage(ctx, response)
+ // The inner-loop error was already sent to the client. Do not append the
+ // buffered upstream response to it.
+ if innerLoopErr != nil {
+ return innerLoopErr
+ }
+
if upstreamErr != nil && !respCopy.responseReceived.Load() {
// no response received from upstream, return custom error
i.sendCustomErr(ctx, w, http.StatusInternalServerError, upstreamErr)
return xerrors.Errorf("failed to connect to upstream: %w", upstreamErr)
}
- err = respCopy.forwardResp(w)
+ if innerLoopIterations > 1 && upstreamErr == nil && response != nil {
+ b, readErr := respCopy.readAll()
+ if readErr != nil {
+ forwardErr := respCopy.forwardBytes(w, b)
+ return errors.Join(xerrors.Errorf("failed to read response body: %w", readErr), forwardErr)
+ }
+ updated, setErr := i.setUsage(b, cumulativeUsage)
+ if setErr != nil {
+ forwardErr := respCopy.forwardBytes(w, b)
+ return errors.Join(setErr, forwardErr)
+ }
+ err = respCopy.forwardBytes(w, updated)
+ } else {
+ err = respCopy.forwardResp(w)
+ }
return errors.Join(upstreamErr, err)
}
+func (*BlockingResponsesInterceptor) setUsage(raw []byte, usage responses.ResponseUsage) ([]byte, error) {
+ raw, err := sjson.SetBytes(raw, "usage", usage)
+ if err != nil {
+ return nil, xerrors.Errorf("set response usage: %w", err)
+ }
+ return raw, nil
+}
+
// newResponse routes by credential type, returning the upstream response, the
// number of key attempts made for this call, and any error. A centralized key
// pool fails over across keys, while BYOK authenticates with a single, fixed
diff --git a/aibridge/internal/integrationtest/bridge_internal_test.go b/aibridge/internal/integrationtest/bridge_internal_test.go
index b3b7b9d3c29..a30637c8245 100644
--- a/aibridge/internal/integrationtest/bridge_internal_test.go
+++ b/aibridge/internal/integrationtest/bridge_internal_test.go
@@ -862,11 +862,57 @@ func TestOpenAIChatCompletions(t *testing.T) {
}
require.True(t, finalUsage.Exists())
- require.EqualValues(t, 6000, finalUsage.Get("prompt_tokens").Int())
- require.EqualValues(t, 30, finalUsage.Get("completion_tokens").Int())
- require.EqualValues(t, 6030, finalUsage.Get("total_tokens").Int())
- require.EqualValues(t, 12000, bridgeServer.Recorder.TotalInputTokens())
- require.EqualValues(t, 60, bridgeServer.Recorder.TotalOutputTokens())
+ require.EqualValues(t, 12000, finalUsage.Get("prompt_tokens").Int())
+ require.EqualValues(t, 60, finalUsage.Get("completion_tokens").Int())
+ require.EqualValues(t, 12060, finalUsage.Get("total_tokens").Int())
+ require.EqualValues(t, 300, finalUsage.Get("prompt_tokens_details.cached_tokens").Int())
+ require.EqualValues(t, 30, finalUsage.Get("prompt_tokens_details.cache_write_tokens").Int())
+ require.EqualValues(t, 12, finalUsage.Get("prompt_tokens_details.audio_tokens").Int())
+ require.EqualValues(t, 14, finalUsage.Get("completion_tokens_details.reasoning_tokens").Int())
+ require.EqualValues(t, 16, finalUsage.Get("completion_tokens_details.audio_tokens").Int())
+ require.EqualValues(t, 18, finalUsage.Get("completion_tokens_details.accepted_prediction_tokens").Int())
+ require.EqualValues(t, 20, finalUsage.Get("completion_tokens_details.rejected_prediction_tokens").Int())
+
+ tokenUsages := bridgeServer.Recorder.RecordedTokenUsages()
+ for i := range tokenUsages {
+ tokenUsages[i].InterceptionID = ""
+ tokenUsages[i].CreatedAt = time.Time{}
+ }
+
+ // Each upstream stream reports cumulative snapshots on every chunk. The
+ // recorded iteration usage must use only its latest snapshot, while the
+ // client response above sums those two latest snapshots across iterations.
+ expectedTokenUsages := []*recorder.TokenUsageRecord{
+ {
+ MsgID: "chatcmpl-cumulative-tool",
+ Input: 5890,
+ Output: 30,
+ CacheReadInputTokens: 100,
+ CacheWriteInputTokens: 10,
+ ExtraTokenTypes: map[string]int64{
+ "prompt_audio": 3,
+ "completion_reasoning": 4,
+ "completion_audio": 5,
+ "completion_accepted_prediction": 6,
+ "completion_rejected_prediction": 7,
+ },
+ },
+ {
+ MsgID: "chatcmpl-cumulative-final",
+ Input: 5780,
+ Output: 30,
+ CacheReadInputTokens: 200,
+ CacheWriteInputTokens: 20,
+ ExtraTokenTypes: map[string]int64{
+ "prompt_audio": 9,
+ "completion_reasoning": 10,
+ "completion_audio": 11,
+ "completion_accepted_prediction": 12,
+ "completion_rejected_prediction": 13,
+ },
+ },
+ }
+ require.ElementsMatch(t, expectedTokenUsages, tokenUsages)
require.Len(t, mockMCP.getCallsByTool(mockToolName), 1)
bridgeServer.Recorder.VerifyAllInterceptionsEnded(t)
})
@@ -1445,8 +1491,9 @@ func TestOpenAIInjectedTools(t *testing.T) {
require.EqualValues(t, expected, actual)
var (
- content *openai.ChatCompletionChoice
- message openai.ChatCompletion
+ content *openai.ChatCompletionChoice
+ message openai.ChatCompletion
+ clientUsage openai.CompletionUsage
)
if streaming {
// Parse the response stream.
@@ -1457,6 +1504,9 @@ func TestOpenAIInjectedTools(t *testing.T) {
for stream.Next() {
chunk := stream.Current()
acc.AddChunk(chunk)
+ if chunk.JSON.Usage.Valid() {
+ clientUsage = chunk.Usage
+ }
if len(chunk.Choices) == 0 {
continue
@@ -1487,6 +1537,7 @@ func TestOpenAIInjectedTools(t *testing.T) {
body, err := io.ReadAll(resp.Body)
require.NoError(t, err, "read response body")
require.NoError(t, json.Unmarshal(body, &message), "unmarshal response")
+ clientUsage = message.Usage
// Verify that no injected tools were sent to the client.
require.GreaterOrEqual(t, len(message.Choices), 1)
@@ -1500,15 +1551,60 @@ func TestOpenAIInjectedTools(t *testing.T) {
require.NotNil(t, content)
require.Contains(t, content.Message.Content, "dd711d5c-83c6-4c08-a0af-b73055906e8c") // The ID of the workspace to be returned.
- // Check the token usage from the client's perspective.
- // This *should* work but the openai SDK doesn't accumulate the prompt token details :(.
- // See https://github.com/openai/openai-go/blob/v2.7.0/streamaccumulator.go#L145-L147.
- // assert.EqualValues(t, 5047, message.Usage.PromptTokens-message.Usage.PromptTokensDetails.CachedTokens)
- assert.EqualValues(t, 105, message.Usage.CompletionTokens)
-
- // Ensure tokens used during injected tool invocation are accounted for.
- require.EqualValues(t, 5047, bridgeServer.Recorder.TotalInputTokens())
- require.EqualValues(t, 105, bridgeServer.Recorder.TotalOutputTokens())
+ // Check the cumulative token usage from the client's perspective.
+ require.EqualValues(t, 9911, clientUsage.PromptTokens)
+ require.EqualValues(t, 105, clientUsage.CompletionTokens)
+ require.EqualValues(t, 10016, clientUsage.TotalTokens)
+ require.EqualValues(t, 4964, clientUsage.PromptTokensDetails.CachedTokens)
+ require.EqualValues(t, 30, clientUsage.PromptTokensDetails.CacheWriteTokens)
+ require.EqualValues(t, 12, clientUsage.PromptTokensDetails.AudioTokens)
+ require.EqualValues(t, 14, clientUsage.CompletionTokensDetails.ReasoningTokens)
+ require.EqualValues(t, 16, clientUsage.CompletionTokensDetails.AudioTokens)
+ require.EqualValues(t, 18, clientUsage.CompletionTokensDetails.AcceptedPredictionTokens)
+ require.EqualValues(t, 20, clientUsage.CompletionTokensDetails.RejectedPredictionTokens)
+
+ // Ensure both upstream iterations were recorded exactly.
+ tokenUsages := bridgeServer.Recorder.RecordedTokenUsages()
+ for _, usage := range tokenUsages {
+ usage.InterceptionID = ""
+ usage.CreatedAt = time.Time{}
+ }
+ firstMsgID := "chatcmpl-C1XAKDTVYnmWS7tgvg7vPje00PIiy"
+ secondMsgID := "chatcmpl-C1XANLwdflVxAjKOjbMP3LJxSlXsS"
+ if streaming {
+ firstMsgID = "chatcmpl-C1WTooFaxeQgtyLB1kg53t41aB0NV"
+ secondMsgID = "chatcmpl-C1WTqhYgK7bV01bW98Lww3zqaf8ZF"
+ }
+ require.ElementsMatch(t, []*recorder.TokenUsageRecord{
+ {
+ MsgID: firstMsgID,
+ Input: 4742,
+ Output: 45,
+ CacheReadInputTokens: 100,
+ CacheWriteInputTokens: 20,
+ ExtraTokenTypes: map[string]int64{
+ "prompt_audio": 3,
+ "completion_accepted_prediction": 6,
+ "completion_rejected_prediction": 7,
+ "completion_audio": 5,
+ "completion_reasoning": 4,
+ },
+ },
+ {
+ MsgID: secondMsgID,
+ Input: 175,
+ Output: 60,
+ CacheReadInputTokens: 4864,
+ CacheWriteInputTokens: 10,
+ ExtraTokenTypes: map[string]int64{
+ "prompt_audio": 9,
+ "completion_accepted_prediction": 12,
+ "completion_rejected_prediction": 13,
+ "completion_audio": 11,
+ "completion_reasoning": 10,
+ },
+ },
+ }, tokenUsages)
// Ensure we received exactly one prompt.
promptUsages := bridgeServer.Recorder.RecordedPromptUsages()
diff --git a/aibridge/internal/integrationtest/metrics_internal_test.go b/aibridge/internal/integrationtest/metrics_internal_test.go
index 314c2d97c4a..a9bcf93e477 100644
--- a/aibridge/internal/integrationtest/metrics_internal_test.go
+++ b/aibridge/internal/integrationtest/metrics_internal_test.go
@@ -294,10 +294,10 @@ func TestMetrics_TokenUseCount(t *testing.T) {
expectProvider: config.ProviderOpenAI,
expectModel: "gpt-4.1",
expectedLabels: map[string]float64{
- "input": 129, // 12033 - 11904 cached
+ "input": 114, // 12033 - 11904 cached - 15 cache write
"output": 44,
"cache_read_input_tokens": 11904,
- "cache_write_input_tokens": 0,
+ "cache_write_input_tokens": 15,
"output_reasoning": 0,
"total_tokens": 12077,
},
@@ -323,10 +323,10 @@ func TestMetrics_TokenUseCount(t *testing.T) {
expectProvider: config.ProviderOpenAI,
expectModel: "gpt-4.1",
expectedLabels: map[string]float64{
- "input": 19,
+ "input": 14, // 19 prompt - 5 cache write
"output": 200,
"cache_read_input_tokens": 0,
- "cache_write_input_tokens": 0,
+ "cache_write_input_tokens": 5,
"completion_reasoning": 0,
"completion_accepted_prediction": 0,
"completion_rejected_prediction": 0,
diff --git a/aibridge/internal/integrationtest/responses_internal_test.go b/aibridge/internal/integrationtest/responses_internal_test.go
index 05dbaaaea71..d13cf2b84db 100644
--- a/aibridge/internal/integrationtest/responses_internal_test.go
+++ b/aibridge/internal/integrationtest/responses_internal_test.go
@@ -94,10 +94,11 @@ func TestResponsesOutputMatchesUpstream(t *testing.T) {
expectModel: "gpt-4.1",
expectPromptRecorded: "This was a large input...",
expectTokenUsage: &recorder.TokenUsageRecord{
- MsgID: "resp_0cd5d6b8310055d600696a1776b42c81a199fbb02248a8bfa0",
- Input: 129, // 12033 input - 11904 cached
- Output: 44,
- CacheReadInputTokens: 11904,
+ MsgID: "resp_0cd5d6b8310055d600696a1776b42c81a199fbb02248a8bfa0",
+ Input: 114, // 12033 input - 11904 cached - 15 cache write
+ Output: 44,
+ CacheReadInputTokens: 11904,
+ CacheWriteInputTokens: 15,
ExtraTokenTypes: map[string]int64{
"output_reasoning": 0,
"total_tokens": 12077,
@@ -256,10 +257,11 @@ func TestResponsesOutputMatchesUpstream(t *testing.T) {
expectModel: "gpt-5.2-codex",
expectPromptRecorded: "Test cached input tokens.",
expectTokenUsage: &recorder.TokenUsageRecord{
- MsgID: "resp_05080461b406f3f501696a1409d34c8195a40ff4b092145c35",
- Input: 1165, // 16909 input - 15744 cached
- Output: 54,
- CacheReadInputTokens: 15744,
+ MsgID: "resp_05080461b406f3f501696a1409d34c8195a40ff4b092145c35",
+ Input: 1135, // 16909 input - 15744 cached - 30 cache write
+ Output: 54,
+ CacheReadInputTokens: 15744,
+ CacheWriteInputTokens: 30,
ExtraTokenTypes: map[string]int64{
"output_reasoning": 0,
"total_tokens": 16963,
@@ -803,7 +805,8 @@ func TestResponsesInjectedTool(t *testing.T) {
expectToolArgs map[string]any
expectToolError string // If non-empty, MCP tool returns this error.
expectPrompt string
- expectTokenUsages []recorder.TokenUsageRecord
+ expectTokenUsages []*recorder.TokenUsageRecord
+ expectClientUsage responses.ResponseUsage
}{
{
name: "blocking_success",
@@ -813,28 +816,42 @@ func TestResponsesInjectedTool(t *testing.T) {
"template_version_id": "aa4e30e4-a086-4df6-a364-1343f1458104",
},
expectPrompt: "list the template params for version aa4e30e4-a086-4df6-a364-1343f1458104",
- expectTokenUsages: []recorder.TokenUsageRecord{
+ expectTokenUsages: []*recorder.TokenUsageRecord{
{
- MsgID: "resp_012db006225b0ec700696b5de8a01481a28182ea6885448f93",
- Input: 227, // 6371 input - 6144 cached
- Output: 75,
- CacheReadInputTokens: 6144,
+ MsgID: "resp_012db006225b0ec700696b5de8a01481a28182ea6885448f93",
+ Input: 220, // 6371 input - 6144 cached - 7 cache write
+ Output: 75,
+ CacheReadInputTokens: 6144,
+ CacheWriteInputTokens: 7,
ExtraTokenTypes: map[string]int64{
"output_reasoning": 25,
"total_tokens": 6446,
},
},
{
- MsgID: "resp_012db006225b0ec700696b5dec1d4c81a2a6a416e31af39b90",
- Input: 612, // 6756 input - 6144 cached
- Output: 231,
- CacheReadInputTokens: 6144,
+ MsgID: "resp_012db006225b0ec700696b5dec1d4c81a2a6a416e31af39b90",
+ Input: 601, // 6756 input - 6144 cached - 11 cache write
+ Output: 231,
+ CacheReadInputTokens: 6144,
+ CacheWriteInputTokens: 11,
ExtraTokenTypes: map[string]int64{
"output_reasoning": 43,
"total_tokens": 6987,
},
},
},
+ expectClientUsage: responses.ResponseUsage{
+ InputTokens: 13127,
+ OutputTokens: 306,
+ TotalTokens: 13433,
+ InputTokensDetails: responses.ResponseUsageInputTokensDetails{
+ CachedTokens: 12288,
+ CacheWriteTokens: 18,
+ },
+ OutputTokensDetails: responses.ResponseUsageOutputTokensDetails{
+ ReasoningTokens: 68,
+ },
+ },
},
{
name: "blocking_tool_error",
@@ -845,28 +862,42 @@ func TestResponsesInjectedTool(t *testing.T) {
},
expectPrompt: "delete the template with ID 03cb4fdd-8109-4a22-8e22-bb4975171395, don't ask for confirmation",
expectToolError: "500 Internal error deleting template: unauthorized: rbac: forbidden",
- expectTokenUsages: []recorder.TokenUsageRecord{
+ expectTokenUsages: []*recorder.TokenUsageRecord{
{
- MsgID: "resp_06e2afba24b6b2ad00696b774d1df0819eaf1ec802bc8a2ca9",
- Input: 233, // 6377 input - 6144 cached
- Output: 119,
- CacheReadInputTokens: 6144,
+ MsgID: "resp_06e2afba24b6b2ad00696b774d1df0819eaf1ec802bc8a2ca9",
+ Input: 228, // 6377 input - 6144 cached - 5 cache write
+ Output: 119,
+ CacheReadInputTokens: 6144,
+ CacheWriteInputTokens: 5,
ExtraTokenTypes: map[string]int64{
"output_reasoning": 70,
"total_tokens": 6496,
},
},
{
- MsgID: "resp_06e2afba24b6b2ad00696b775044e8819ea14840698ef966e2",
- Input: 395, // 6539 input - 6144 cached
- Output: 144,
- CacheReadInputTokens: 6144,
+ MsgID: "resp_06e2afba24b6b2ad00696b775044e8819ea14840698ef966e2",
+ Input: 386, // 6539 input - 6144 cached - 9 cache write
+ Output: 144,
+ CacheReadInputTokens: 6144,
+ CacheWriteInputTokens: 9,
ExtraTokenTypes: map[string]int64{
"output_reasoning": 28,
"total_tokens": 6683,
},
},
},
+ expectClientUsage: responses.ResponseUsage{
+ InputTokens: 12916,
+ OutputTokens: 263,
+ TotalTokens: 13179,
+ InputTokensDetails: responses.ResponseUsageInputTokensDetails{
+ CachedTokens: 12288,
+ CacheWriteTokens: 14,
+ },
+ OutputTokensDetails: responses.ResponseUsageOutputTokensDetails{
+ ReasoningTokens: 98,
+ },
+ },
},
{
name: "streaming_success",
@@ -875,23 +906,26 @@ func TestResponsesInjectedTool(t *testing.T) {
mcpToolName: "coder_list_templates",
expectToolArgs: map[string]any{},
expectPrompt: "List my coder templates.",
- expectTokenUsages: []recorder.TokenUsageRecord{
+ expectTokenUsages: []*recorder.TokenUsageRecord{
{
- MsgID: "resp_016595fe42aa62ca0069724419c52081a0b7eb479c6bc8109f",
- Input: 6269, // 6269 input - 0 cached
- Output: 18,
+ MsgID: "resp_016595fe42aa62ca0069724419c52081a0b7eb479c6bc8109f",
+ Input: 6162, // 6269 input - 100 cached - 7 cache write
+ Output: 18,
+ CacheReadInputTokens: 100,
+ CacheWriteInputTokens: 7,
ExtraTokenTypes: map[string]int64{
- "output_reasoning": 0,
+ "output_reasoning": 3,
"total_tokens": 6287,
},
},
{
- MsgID: "resp_0bc5f54fce6df69a006972442175908194bb81d31f576e6ca6",
- Input: 319, // 6463 input - 6144 cached
- Output: 182,
- CacheReadInputTokens: 6144,
+ MsgID: "resp_0bc5f54fce6df69a006972442175908194bb81d31f576e6ca6",
+ Input: 308, // 6463 input - 6144 cached - 11 cache write
+ Output: 182,
+ CacheReadInputTokens: 6144,
+ CacheWriteInputTokens: 11,
ExtraTokenTypes: map[string]int64{
- "output_reasoning": 0,
+ "output_reasoning": 5,
"total_tokens": 6645,
},
},
@@ -908,22 +942,26 @@ func TestResponsesInjectedTool(t *testing.T) {
},
expectPrompt: "Create a new workspace build for an workspace with id: 'non_existing_id'",
expectToolError: "workspace_id must be a valid UUID: invalid UUID length: 15",
- expectTokenUsages: []recorder.TokenUsageRecord{
+ expectTokenUsages: []*recorder.TokenUsageRecord{
{
- MsgID: "resp_0dfed48e1052ad7f0069725ca129f88193b97d6deff1760524",
- Input: 6280, // 6280 input - 0 cached
- Output: 30,
+ MsgID: "resp_0dfed48e1052ad7f0069725ca129f88193b97d6deff1760524",
+ Input: 6175, // 6280 input - 100 cached - 5 cache write
+ Output: 30,
+ CacheReadInputTokens: 100,
+ CacheWriteInputTokens: 5,
ExtraTokenTypes: map[string]int64{
- "output_reasoning": 0,
+ "output_reasoning": 3,
"total_tokens": 6310,
},
},
{
- MsgID: "resp_0dfed48e1052ad7f0069725ca39880819390fcc5b2eb8cf8c6",
- Input: 6346, // 6346 input - 0 cached
- Output: 56,
+ MsgID: "resp_0dfed48e1052ad7f0069725ca39880819390fcc5b2eb8cf8c6",
+ Input: 6237, // 6346 input - 100 cached - 9 cache write
+ Output: 56,
+ CacheReadInputTokens: 100,
+ CacheWriteInputTokens: 9,
ExtraTokenTypes: map[string]int64{
- "output_reasoning": 0,
+ "output_reasoning": 5,
"total_tokens": 6402,
},
},
@@ -983,24 +1021,23 @@ func TestResponsesInjectedTool(t *testing.T) {
require.Len(t, prompts, 1)
require.Equal(t, tc.expectPrompt, prompts[0].Prompt)
+ // Verify both upstream iterations were recorded exactly. Match by
+ // content because AsyncRecorder does not guarantee record ordering.
tokenUsages := bridgeServer.Recorder.RecordedTokenUsages()
- require.Len(t, tokenUsages, len(tc.expectTokenUsages))
for i := range tokenUsages {
- tokenUsages[i].InterceptionID = "" // ignore interception ID and time creation when comparing
+ tokenUsages[i].InterceptionID = ""
tokenUsages[i].CreatedAt = time.Time{}
}
+ require.ElementsMatch(t, tc.expectTokenUsages, tokenUsages)
- // Match by content, not position, AsyncRecorder may flake.
- // See https://github.com/coder/internal/issues/1544.
- for _, expected := range tc.expectTokenUsages {
- require.Contains(t, tokenUsages, &expected)
- }
-
- // Verify the response is the final tool response (after agentic loop).
+ // Streaming forwards the final upstream response unchanged. Blocking
+ // rewrites its usage with the sum from both agentic-loop iterations.
if tc.streaming {
require.Equal(t, string(fix.StreamingToolCall()), string(body))
} else {
- require.Equal(t, string(fix.NonStreamingToolCall()), string(body))
+ expectedBody, err := sjson.SetBytes(fix.NonStreamingToolCall(), "usage", tc.expectClientUsage)
+ require.NoError(t, err)
+ require.JSONEq(t, string(expectedBody), string(body))
}
})
}