Skip to content

Commit c319019

Browse files
committed
configurable timeouts added to FetchUrlTool and LinkCheckerTool
1 parent b699ad8 commit c319019

3 files changed

Lines changed: 87 additions & 17 deletions

File tree

zsmith/src/main/java/airhacks/zsmith/tools/control/FetchUrlTool.java

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,52 @@
1111

1212
import org.json.JSONObject;
1313

14+
import airhacks.zsmith.configuration.control.HttpTimeouts;
15+
1416
public interface FetchUrlTool {
1517

16-
int CONNECT_TIMEOUT_SECONDS = 10;
17-
int REQUEST_TIMEOUT_SECONDS = 15;
18+
/// Deliberately shorter than the LLM transport timeouts: a page fetch should fail fast
19+
/// rather than block a tool turn. Overridable as ISO-8601 durations via `fetch.connect.timeout`
20+
/// and `fetch.request.timeout`. The errors — including timeouts — are returned as the tool
21+
/// result string, not thrown, so the model can react to them.
22+
Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(10);
23+
Duration DEFAULT_REQUEST_TIMEOUT = Duration.ofSeconds(15);
1824
int MAX_BODY_LENGTH = 20_000;
1925
String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
2026

21-
HttpClient HTTP_CLIENT = HttpClient.newBuilder()
22-
.connectTimeout(Duration.ofSeconds(CONNECT_TIMEOUT_SECONDS))
23-
.followRedirects(Redirect.NORMAL)
24-
.build();
27+
static Duration connectTimeout() {
28+
return HttpTimeouts.duration("fetch.connect.timeout", DEFAULT_CONNECT_TIMEOUT);
29+
}
30+
31+
static Duration requestTimeout() {
32+
return HttpTimeouts.duration("fetch.request.timeout", DEFAULT_REQUEST_TIMEOUT);
33+
}
34+
35+
/// Built lazily so the configured connect timeout is read on first fetch, after config is
36+
/// loaded — never at interface initialization (which `create()` would trigger too early).
37+
static HttpClient client() {
38+
return Holder.instance();
39+
}
40+
41+
final class Holder {
42+
private static volatile HttpClient instance;
43+
44+
static HttpClient instance() {
45+
var current = instance;
46+
if (current != null) {
47+
return current;
48+
}
49+
synchronized (Holder.class) {
50+
if (instance == null) {
51+
instance = HttpClient.newBuilder()
52+
.connectTimeout(connectTimeout())
53+
.followRedirects(Redirect.NORMAL)
54+
.build();
55+
}
56+
return instance;
57+
}
58+
}
59+
}
2560

2661
enum Field { url }
2762

@@ -54,13 +89,13 @@ private static String run(JSONObject input) {
5489
try {
5590
var request = HttpRequest.newBuilder()
5691
.uri(uri)
57-
.timeout(Duration.ofSeconds(REQUEST_TIMEOUT_SECONDS))
92+
.timeout(requestTimeout())
5893
.header("User-Agent", USER_AGENT)
5994
.header("Accept", "*/*")
6095
.GET()
6196
.build();
6297

63-
var response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
98+
var response = client().send(request, HttpResponse.BodyHandlers.ofString());
6499

65100
var statusCode = response.statusCode();
66101
var contentType = response.headers()

zsmith/src/main/java/airhacks/zsmith/tools/control/LinkCheckerTool.java

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,51 @@
1212

1313
import org.json.JSONObject;
1414

15+
import airhacks.zsmith.configuration.control.HttpTimeouts;
16+
1517
public interface LinkCheckerTool {
1618

17-
int CONNECT_TIMEOUT_SECONDS = 10;
18-
int REQUEST_TIMEOUT_SECONDS = 10;
19+
/// Deliberately shorter than the LLM transport timeouts: a reachability check should fail fast
20+
/// rather than block a tool turn. Overridable as ISO-8601 durations via `link.connect.timeout`
21+
/// and `link.request.timeout`. The errors — including timeouts — are returned as the tool
22+
/// result string, not thrown, so the model can react to them.
23+
Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(10);
24+
Duration DEFAULT_REQUEST_TIMEOUT = Duration.ofSeconds(10);
1925
String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
2026

21-
HttpClient HTTP_CLIENT = HttpClient.newBuilder()
22-
.connectTimeout(Duration.ofSeconds(CONNECT_TIMEOUT_SECONDS))
23-
.followRedirects(Redirect.NORMAL)
24-
.build();
27+
static Duration connectTimeout() {
28+
return HttpTimeouts.duration("link.connect.timeout", DEFAULT_CONNECT_TIMEOUT);
29+
}
30+
31+
static Duration requestTimeout() {
32+
return HttpTimeouts.duration("link.request.timeout", DEFAULT_REQUEST_TIMEOUT);
33+
}
34+
35+
/// Built lazily so the configured connect timeout is read on first check, after config is
36+
/// loaded — never at interface initialization (which `create()` would trigger too early).
37+
static HttpClient client() {
38+
return Holder.instance();
39+
}
40+
41+
final class Holder {
42+
private static volatile HttpClient instance;
43+
44+
static HttpClient instance() {
45+
var current = instance;
46+
if (current != null) {
47+
return current;
48+
}
49+
synchronized (Holder.class) {
50+
if (instance == null) {
51+
instance = HttpClient.newBuilder()
52+
.connectTimeout(connectTimeout())
53+
.followRedirects(Redirect.NORMAL)
54+
.build();
55+
}
56+
return instance;
57+
}
58+
}
59+
}
2560

2661
enum Field { url }
2762

@@ -72,12 +107,12 @@ private static String run(JSONObject input) {
72107
private static HttpResponse<Void> send(URI uri, String method) throws IOException, InterruptedException {
73108
var request = HttpRequest.newBuilder()
74109
.uri(uri)
75-
.timeout(Duration.ofSeconds(REQUEST_TIMEOUT_SECONDS))
110+
.timeout(requestTimeout())
76111
.header("User-Agent", USER_AGENT)
77112
.header("Accept", "*/*")
78113
.method(method, HttpRequest.BodyPublishers.noBody())
79114
.build();
80-
return HTTP_CLIENT.send(request, BodyHandlers.discarding());
115+
return client().send(request, BodyHandlers.discarding());
81116
}
82117

83118
private static String format(HttpResponse<?> response) {

zsmith/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2026.06.25.01
1+
2026.06.25.02

0 commit comments

Comments
 (0)