Skip to content

Commit c2d6d5e

Browse files
committed
sync: cherry-pick changes from feature/3.0.x
1 parent ac6f99e commit c2d6d5e

72 files changed

Lines changed: 3077 additions & 120 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/java/io/github/easy4j/opencode/HttpCallCancellation.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
package io.github.easy4j.opencode;
22

3-
/** 将业务层取消信号绑定到一次 OpenCode HTTP 调用。 */
3+
/**
4+
* Binds a business-layer cancellation signal to a single OpenCode HTTP call.
5+
*
6+
* <p>Implementations allow callers to register a cancellation callback that will be invoked
7+
* when the underlying HTTP call should be aborted. The {@link #onCancel(Runnable)} method
8+
* returns an {@link AutoCloseable} that, when closed, unregisters the callback.</p>
9+
*
10+
* @author [@Loong Wan](https://github.com/loong10k)
11+
* @since 3.0.0
12+
* @see io.github.easy4j.opencode.api.OpenCodeHttpClient
13+
*/
414
@FunctionalInterface
515
public interface HttpCallCancellation {
616

17+
/**
18+
* Registers a cancellation callback and returns a handle to unregister it.
19+
*
20+
* @param callback the runnable to invoke when cancellation is requested
21+
* @return an {@link AutoCloseable} that unregisters the callback when closed
22+
*/
723
AutoCloseable onCancel(Runnable callback);
824

25+
/**
26+
* Returns whether this cancellation has been triggered.
27+
*
28+
* @return {@code true} if cancellation has been requested, {@code false} otherwise
29+
*/
930
default boolean isCancelled() {
1031
return false;
1132
}

src/main/java/io/github/easy4j/opencode/OpenCodeCliConfig.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33
import lombok.Data;
44

55
/**
6-
* OpenCode 本地 CLI 客户端配置。
7-
* <p>
8-
* 涵盖本地 {@code opencode} 可执行文件路径、超时、工作目录等 CLI 运行时设置。
9-
* </p>
6+
* Configuration for the local OpenCode CLI subsystem.
7+
*
8+
* <p>Covers local {@code opencode} executable path, timeouts, working directory,
9+
* and other CLI runtime settings. Can be mapped from Spring
10+
* {@code @ConfigurationProperties}.</p>
11+
*
12+
* @author [@Loong Wan](https://github.com/loong10k)
13+
* @since 3.0.0
14+
* @see OpenCodeClient
15+
* @see OpenCodeClientConfig
1016
*/
1117
@Data
1218
public class OpenCodeCliConfig {

src/main/java/io/github/easy4j/opencode/OpenCodeClient.java

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,36 @@
2626
import java.util.function.Consumer;
2727

2828
/**
29-
* OpenCode 客户端门面:HTTP Server + SSE 事件流 + 本地 CLI
30-
* <p>
31-
* 三条通信通道相互独立,按各自子配置的 {@code enabled} 决定是否创建:
32-
* </p>
29+
* Facade client for OpenCode: HTTP Server + SSE event stream + local CLI.
30+
*
31+
* <p>Three communication channels are independent of each other and are created based on
32+
* the {@code enabled} flag in their respective sub-configurations:</p>
3333
* <ul>
34-
* <li>{@link OpenCodeHttpClientConfig#isEnabled()} = false HTTP / SSE 子客户端为 {@code null}</li>
35-
* <li>{@link OpenCodeCliConfig#isEnabled()} = false CLI 子客户端为 {@code null}</li>
34+
* <li>{@link OpenCodeHttpClientConfig#isEnabled()} = false -> HTTP/SSE sub-clients are {@code null}</li>
35+
* <li>{@link OpenCodeCliConfig#isEnabled()} = false -> CLI sub-client is {@code null}</li>
3636
* </ul>
3737
*
38-
* <h3>构造器选择</h3>
39-
* <p>提供 8 个重载覆盖三类场景:</p>
38+
* <h3>Constructor Selection</h3>
39+
* <p>Eight overloads cover three scenarios:</p>
4040
* <ul>
41-
* <li>HTTP / 仅 CLI:传入单个子配置,禁用另一子系统</li>
42-
* <li>HTTP + CLI:传入两个子配置,子系统都按各自 {@code enabled} 决定</li>
43-
* <li>组合配置:传入 {@link OpenCodeClientConfig},内部拆分为两个子配置</li>
41+
* <li>HTTP only / CLI only: pass a single sub-config; the other subsystem is disabled</li>
42+
* <li>HTTP + CLI: pass two sub-configs; each subsystem is enabled per its own {@code enabled} flag</li>
43+
* <li>Combined config: pass {@link OpenCodeClientConfig}; internally split into two sub-configs</li>
4444
* </ul>
45-
* <p>每种场景再分「自动 ObjectMapper/OkHttpClient」与「强制注入」两个变体。
46-
* 强制注入的版本对 {@code ObjectMapper}/{@code OkHttpClient} 进行 {@code requireNonNull} 校验。</p>
45+
* <p>Each scenario has a variant with auto-created ObjectMapper/OkHttpClient and a variant with
46+
* forced injection (the injected version applies {@code requireNonNull} validation).</p>
47+
*
48+
* <h3>Startup Health Checks</h3>
49+
* <p>After subsystem initialization, the primary constructor runs health probes based on
50+
* {@code startupCheckEnabled} and {@code failFastOnUnavailable} (HTTP: {@code GET /global/health};
51+
* CLI: {@code opencode --version}). If the probe fails but fail-fast is not enabled, only a
52+
* WARN log is emitted and construction continues.</p>
4753
*
48-
* <h3>启动自检</h3>
49-
* <p>主构造器在子系统初始化后按 {@code startupCheckEnabled} 与 {@code failFastOnUnavailable}
50-
* 执行健康探测(HTTP:{@code GET /global/health};CLI:{@code opencode --version})。
51-
* 探测失败但未开启 fail-fast 时仅 WARN,不中断构造。</p>
54+
* @author [@Loong Wan](https://github.com/loong10k)
55+
* @since 3.0.0
56+
* @see OpenCodeHttpClientConfig
57+
* @see OpenCodeCliConfig
58+
* @see OpenCodeClientConfig
5259
*/
5360
@Slf4j
5461
public class OpenCodeClient implements AutoCloseable {

src/main/java/io/github/easy4j/opencode/OpenCodeClientConfig.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
import lombok.Data;
44

55
/**
6-
* OpenCode 客户端统一配置(纯 POJO,可与 Spring {@code @ConfigurationProperties} 映射)。
7-
* <p>
8-
* 组合 {@link OpenCodeHttpClientConfig}(HTTP/Server 相关)与 {@link OpenCodeCliConfig}(本地 CLI 相关),
9-
* 作为 {@link OpenCodeClient} 等统一入口的配置载体。
10-
* </p>
6+
* Unified configuration POJO for the OpenCode client.
7+
*
8+
* <p>Composes {@link OpenCodeHttpClientConfig} (HTTP/Server settings) and
9+
* {@link OpenCodeCliConfig} (local CLI settings). Compatible with Spring
10+
* {@code @ConfigurationProperties} binding.</p>
11+
*
12+
* @author [@Loong Wan](https://github.com/loong10k)
13+
* @since 3.0.0
14+
* @see OpenCodeHttpClientConfig
15+
* @see OpenCodeCliConfig
16+
* @see OpenCodeClient
1117
*/
1218
@Data
1319
public class OpenCodeClientConfig {

src/main/java/io/github/easy4j/opencode/OpenCodeHttpClientConfig.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
import lombok.Data;
44

55
/**
6-
* OpenCode HTTP Server 客户端配置。
7-
* <p>
8-
* 涵盖 Server 根地址、Basic Auth、TLS、HTTP 超时及默认模型等网络相关设置。
9-
* </p>
6+
* Configuration for the OpenCode HTTP Server client.
7+
*
8+
* <p>Covers server base URL, Basic Auth, TLS, HTTP timeouts, connection pool sizing,
9+
* streaming thread pool, and default model/agent settings.</p>
10+
*
11+
* @author [@Loong Wan](https://github.com/loong10k)
12+
* @since 3.0.0
13+
* @see OpenCodeHttpClient
14+
* @see OpenCodeOkHttpClientFactory
1015
*/
1116
@Data
1217
public class OpenCodeHttpClientConfig {

src/main/java/io/github/easy4j/opencode/OpenCodeOkHttpClientFactory.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@
88
import java.util.concurrent.TimeUnit;
99

1010
/**
11-
* OpenCode 独立运行时使用的 OkHttpClient 工厂。
11+
* Factory for creating {@link OkHttpClient} instances used by the OpenCode SDK in standalone mode.
1212
*
13-
* <p>Spring 容器已经提供外部 {@link OkHttpClient} 时应使用注入构造器,本工厂不会参与。</p>
13+
* <p>When a Spring container already provides an external {@link OkHttpClient}, the injection
14+
* constructors should be used instead and this factory will not participate.</p>
15+
*
16+
* @author [@Loong Wan](https://github.com/loong10k)
17+
* @since 3.0.0
18+
* @see OpenCodeHttpClientConfig
1419
*/
1520
public final class OpenCodeOkHttpClientFactory {
1621

src/main/java/io/github/easy4j/opencode/api/OpenCodeHttpClient.java

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import com.fasterxml.jackson.databind.DeserializationFeature;
55
import com.fasterxml.jackson.databind.ObjectMapper;
66
import io.github.easy4j.opencode.OpenCodeHttpClientConfig;
7-
import io.github.easy4j.opencode.OpenCodeOkHttpClientFactory;
87
import io.github.easy4j.opencode.HttpCallCancellation;
8+
import io.github.easy4j.opencode.OpenCodeOkHttpClientFactory;
99
import io.github.easy4j.opencode.api.model.*;
1010
import io.github.easy4j.opencode.exception.OpenCodeHttpException;
1111
import lombok.extern.slf4j.Slf4j;
@@ -22,9 +22,16 @@
2222
import java.util.Optional;
2323

2424
/**
25-
* OpenCode Server HTTP 客户端,封装 REST API。
26-
* <p>基于 OkHttp,支持外部传入 {@link OkHttpClient}(复用别的插件实例)。</p>
25+
* HTTP client for the OpenCode Server REST API.
26+
*
27+
* <p>Built on OkHttp; supports externally provided {@link OkHttpClient} instances for
28+
* connection pooling across plugins. All methods throw {@link io.github.easy4j.opencode.exception.OpenCodeHttpException}
29+
* on HTTP errors.</p>
2730
*
31+
* @author [@Loong Wan](https://github.com/loong10k)
32+
* @since 3.0.0
33+
* @see OpenCodeHttpClientConfig
34+
* @see io.github.easy4j.opencode.exception.OpenCodeHttpException
2835
* @see <a href="https://opencode.ai/docs/server/">opencode server docs</a>
2936
*/
3037
@Slf4j
@@ -36,14 +43,12 @@ public class OpenCodeHttpClient implements AutoCloseable {
3643
private final OpenCodeHttpClientConfig config;
3744
private final OkHttpClient httpClient;
3845
private final ObjectMapper objectMapper;
39-
private final boolean ownsHttpClient;
4046

4147
public OpenCodeHttpClient(OpenCodeHttpClientConfig config, ObjectMapper objectMapper, OkHttpClient httpClient) {
4248
this.config = Objects.requireNonNull(config, "config");
4349
this.objectMapper = Objects.isNull(objectMapper) ? new ObjectMapper()
4450
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false): objectMapper;
45-
this.ownsHttpClient = Objects.isNull(httpClient);
46-
this.httpClient = this.ownsHttpClient ? buildOkHttpClient(config) : httpClient;
51+
this.httpClient = Objects.isNull(httpClient) ? buildOkHttpClient(config) : httpClient;
4752
}
4853

4954
private static OkHttpClient buildOkHttpClient(OpenCodeHttpClientConfig config) {
@@ -932,16 +937,6 @@ private String toJson(Object body) {
932937

933938
@Override
934939
public void close() {
935-
if (ownsHttpClient) {
936-
httpClient.dispatcher().executorService().shutdownNow();
937-
httpClient.connectionPool().evictAll();
938-
if (Objects.nonNull(httpClient.cache())) {
939-
try {
940-
httpClient.cache().close();
941-
} catch (IOException error) {
942-
log.debug("Failed to close OpenCode HTTP cache", error);
943-
}
944-
}
945-
}
940+
// 外部传入的 OkHttpClient 不关闭;自建的也不主动关闭(OkHttpClient 内部管理连接池)
946941
}
947942
}

src/main/java/io/github/easy4j/opencode/api/OpenCodeSseClient.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,16 @@
2525
import java.util.function.Consumer;
2626

2727
/**
28-
* OpenCode Server SSE 客户端,消费 {@code GET /event} 事件流。
29-
* <p>基于 OkHttp {@link EventSources},支持外部传入 {@link OkHttpClient}。</p>
28+
* SSE client for the OpenCode Server event stream ({@code GET /event}).
29+
*
30+
* <p>Built on OkHttp {@link EventSources}; supports externally provided {@link OkHttpClient}
31+
* instances. Provides both callback-based and blocking-queue-based subscription models,
32+
* as well as session-scoped and type-filtered subscriptions.</p>
33+
*
34+
* @author [@Loong Wan](https://github.com/loong10k)
35+
* @since 3.0.0
36+
* @see OpenCodeHttpClientConfig
37+
* @see io.github.easy4j.opencode.api.event.EventHandler
3038
*/
3139
@Slf4j
3240
public class OpenCodeSseClient implements AutoCloseable {

src/main/java/io/github/easy4j/opencode/api/event/EventHandler.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@
55
import java.util.Map;
66

77
/**
8-
* opencode SSE 事件回调处理器,提供类型化默认方法。
9-
* <p>使用方式:</p>
8+
* Typed callback handler for OpenCode SSE events.
9+
*
10+
* <p>All methods have no-op defaults; callers override only the callbacks they care about.</p>
11+
*
12+
* <p>Usage example:</p>
1013
* <pre>{@code
1114
* client.onSessionEvent(sessionId, new EventHandler() {
1215
* public void onTextDelta(String delta, Event event) {
1316
* System.out.print(delta);
1417
* }
1518
* });
1619
* }</pre>
17-
* 所有方法默认 no-op;调用方按需覆写关心的回调。
20+
*
21+
* @author [@Loong Wan](https://github.com/loong10k)
22+
* @since 3.0.0
23+
* @see io.github.easy4j.opencode.api.OpenCodeSseClient
1824
*/
1925
public interface EventHandler {
2026

src/main/java/io/github/easy4j/opencode/api/mapper/OpenCodeCallbackParser.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,23 @@
1212
import java.util.regex.Pattern;
1313

1414
/**
15-
* 从 OpenCode AI 响应中解析回调 JSON。
16-
* <p>
17-
* OpenCode 没有原生的 webhook/callback 机制。
18-
* 本解析器尝试从 AI 的文本响应中提取 JSON 块,
19-
* 其格式由 cloud-agents 的 prompt 模板约定(如 SKILL.md 中定义的 callback_url 输出格式)。
20-
* </p>
15+
* Parses callback JSON from OpenCode AI text responses.
16+
*
17+
* <p>OpenCode does not have a native webhook/callback mechanism. This parser attempts
18+
* to extract JSON blocks from the AI's text response, following the format conventions
19+
* defined by cloud-agents prompt templates (e.g., the callback_url output format
20+
* specified in SKILL.md).</p>
21+
*
22+
* <p>The parser tries three strategies in order:</p>
23+
* <ol>
24+
* <li>Extract JSON from {@code ```json ... ```} fenced code blocks</li>
25+
* <li>Parse the entire text as JSON directly</li>
26+
* <li>Extract bare JSON objects containing {@code task_id}, {@code taskId}, or {@code title}</li>
27+
* </ol>
28+
*
29+
* @author [@Loong Wan](https://github.com/loong10k)
30+
* @since 3.0.0
31+
* @see io.github.easy4j.opencode.api.model.PromptResult
2132
*/
2233
public class OpenCodeCallbackParser {
2334

0 commit comments

Comments
 (0)