Skip to content

Commit be32d21

Browse files
ericdalloeca-agent
andcommitted
Add streamIdleTimeoutSeconds config option
Make the LLM stream idle timeout configurable instead of hardcoded at 2 minutes. Threads the config value from config through llm_api into the Anthropic provider's stream watchdog. Default remains 120s. 🤖 Generated with [eca](https://eca.dev) Co-Authored-By: eca <git@eca.dev>
1 parent c27d742 commit be32d21

File tree

5 files changed

+22
-7
lines changed

5 files changed

+22
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Fix Z-AI provider config using wrong API type and URL.
88
- Improve explorer subagent prompt, remove role based approach.
99
- Remove model/variant list from spawn_agent tool description so it doesn't use models when it is not asked to. #369
10+
- Add `streamIdleTimeoutSeconds` config option to make the LLM stream idle timeout configurable (default: 120s).
1011

1112
## 0.120.1
1213

docs/config.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@
139139
"markdownDescription": "Timeout in seconds for LSP operations.",
140140
"default": 30
141141
},
142+
"streamIdleTimeoutSeconds": {
143+
"type": "integer",
144+
"description": "Timeout in seconds to wait without receiving data before considering a LLM stream stalled.",
145+
"markdownDescription": "Timeout in seconds to wait without receiving data before considering a LLM stream stalled.",
146+
"default": 120
147+
},
142148
"mcpServers": {
143149
"type": "object",
144150
"description": "MCP (Model Context Protocol) server configurations.",

src/eca/config.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
:excludeProviders ["github-copilot"]}}
176176
:mcpTimeoutSeconds 60
177177
:lspTimeoutSeconds 30
178+
:streamIdleTimeoutSeconds 120
178179
:mcpServers {}
179180
:welcomeMessage (multi-str "# Welcome to ECA!"
180181
""

src/eca/llm_api.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,8 @@
233233
:api-url api-url
234234
:api-key api-key
235235
:auth-type auth-type
236-
:cancelled? cancelled?}
236+
:cancelled? cancelled?
237+
:stream-idle-timeout-seconds (:streamIdleTimeoutSeconds config)}
237238
callbacks)
238239

239240
(= "github-copilot" provider)

src/eca/llm_providers/anthropic.clj

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
:max_uses 10
7070
:cache_control {:type "ephemeral"}})))
7171

72-
(defn ^:private base-request! [{:keys [rid body api-url api-key auth-type url-relative-path content-block* on-error on-stream http-client extra-headers cancelled?]}]
72+
(defn ^:private base-request! [{:keys [rid body api-url api-key auth-type url-relative-path content-block* on-error on-stream http-client extra-headers cancelled? stream-idle-timeout-seconds]}]
7373
(let [url (join-api-url api-url (or url-relative-path messages-path))
7474
reason-id* (atom (str (random-uuid)))
7575
oauth? (= :auth/oauth auth-type)
@@ -105,7 +105,9 @@
105105
:body body-str}))
106106
(if on-stream
107107
(let [{:keys [touch-fn set-reading-fn stop-fn reason*]}
108-
(llm-util/start-stream-watchdog! body cancelled? {})
108+
(llm-util/start-stream-watchdog! body cancelled?
109+
(when stream-idle-timeout-seconds
110+
{:idle-timeout-ms (* 1000 stream-idle-timeout-seconds)}))
109111
completed?* (atom false)]
110112
(try
111113
(with-open [rdr (io/reader body)]
@@ -134,7 +136,8 @@
134136
(throw (ex-info "Stream cancelled" {:silent? true}))
135137

136138
(= :idle-timeout reason)
137-
(on-error {:message "Stream idle timeout: no data received for 2 minutes"
139+
(on-error {:message (format "Stream idle timeout: no data received for %d seconds"
140+
(or stream-idle-timeout-seconds 120))
138141
:exception e})
139142

140143
:else
@@ -291,7 +294,8 @@
291294
(defn chat!
292295
[{:keys [model user-messages instructions max-output-tokens
293296
api-url api-key auth-type url-relative-path reason? past-messages
294-
tools web-search extra-payload extra-headers supports-image? http-client cancelled?]}
297+
tools web-search extra-payload extra-headers supports-image? http-client cancelled?
298+
stream-idle-timeout-seconds]}
295299
{:keys [on-message-received on-error on-reason on-prepare-tool-call on-tools-called on-usage-updated on-server-web-search] :as callbacks}]
296300
(let [messages (-> (concat past-messages (fix-non-thinking-assistant-messages user-messages))
297301
group-parallel-tool-calls
@@ -438,7 +442,8 @@
438442
:content-block* (atom nil)
439443
:cancelled? cancelled?
440444
:on-error on-error
441-
:on-stream handle-stream}))))
445+
:on-stream handle-stream
446+
:stream-idle-timeout-seconds stream-idle-timeout-seconds}))))
442447
"end_turn" (if @has-content?*
443448
(do
444449
(reset! content-block* {})
@@ -470,7 +475,8 @@
470475
:content-block* (atom nil)
471476
:cancelled? cancelled?
472477
:on-error on-error
473-
:on-stream on-stream-fn})))
478+
:on-stream on-stream-fn
479+
:stream-idle-timeout-seconds stream-idle-timeout-seconds})))
474480

475481
(def ^:private client-id "9d1c250a-e61b-44d9-88ed-5944d1962f5e")
476482

0 commit comments

Comments
 (0)