Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

## [Unreleased]

## [0.1.0] - 2026-10-21

First stable release of the Java SDK for the Forward API, using `com.qoder.sdk:qoder-cloud-agents-sdk:0.1.0`.

### Features

- Java 8 compatible synchronous and asynchronous clients, checked on Java 8, 11, 17, 21 and 25.
- Shared PAT, static SAT and custom credentials, plus `ServiceAccountCredential` for automatic SA Key exchange, SAT caching and refresh. Forward administrator/Identity SAT issuance and revocation use explicit per-call SA Keys.
- Typed Forward resource APIs for Models, Templates, Identities and configuration, Sessions and Threads, Memory Stores, Environments, Files, Skills, Vaults and Credentials, Schedules, Batches, Channels and Pairing, Usage and Drive.
- Session/Thread SSE subscriptions with cancellation and explicit resumption; Realtime conversations with text/PCM audio, typed events, acknowledged close and History.
- Shared request options, timeouts, retries, pagination, response metadata and typed errors, with resource-lifecycle examples and API guides. Managed API support is planned for a future release and is not included in 0.1.0.

### Changed

- Promote the RC's API and runtime behavior without implementation changes; update installation, example and maintenance documentation for Maven Central consumption.

### Migration

- From `0.1.0-rc.1`, change only the dependency version to `0.1.0`.
- From the earlier internal `com.qoder:qca-sdk:0.1.0-SNAPSHOT`, use `com.qoder.sdk:qoder-cloud-agents-sdk:0.1.0`. Java imports remain under `com.qoder.cloudagents`.

## [0.1.0-rc.1] - 2026-10-21

First public release candidate for installation and integration testing ahead of `0.1.0`.
Expand Down Expand Up @@ -76,4 +97,4 @@ First public release candidate for installation and integration testing ahead of
- Usage, Token management and Drive round-trip examples with installed-JAR consumer checks, scoped cleanup and storage credential isolation.
- HTTP error request ID fallback from top-level and nested error bodies when response headers omit it.

This version can be built from source; Maven Central publication remains pending. See the [maintenance guide](docs/maintaining.md) for current checks and delivery boundaries.
This release candidate is available from Maven Central. See the [maintenance guide](docs/maintaining.md) for checks and delivery boundaries.
274 changes: 181 additions & 93 deletions README.md

Large diffs are not rendered by default.

111 changes: 111 additions & 0 deletions docs/client-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Client configuration

Shared configuration for the synchronous and asynchronous Forward clients. See the [README](../README.md) for installation and quick starts.

## PAT

```java
import com.qoder.cloudagents.ForwardClient;
import com.qoder.cloudagents.forward.Template;

try (ForwardClient client = ForwardClient.builder()
.baseUrl("https://api.qoder.com/api/v1/forward")
.pat(System.getenv("QODER_PAT"))
.build()) {
Template template = client.templates().retrieve("tmpl_example");
System.out.println(template.id());
System.out.println(template.requestId());
}
```

The default BaseURL is Global. For CN, use `https://api.qoder.com.cn/api/v1/forward`. `QODER_FORWARD_BASE_URL` provides the default when `.baseUrl(...)` is omitted.

With no explicit credentials, PAT mode reads `QODER_ACCESS_TOKEN`, then `QODER_PAT`. Explicit `.pat(...)`, `.accessToken(...)` or `.credential(...)` takes precedence over environment tokens; configure only one explicit source.

## Service Account

```java
import com.qoder.cloudagents.ForwardClient;
import com.qoder.cloudagents.auth.Region;
import com.qoder.cloudagents.auth.ServiceAccountCredential;

try (ServiceAccountCredential credential = ServiceAccountCredential.builder()
.saKey(System.getenv("QODER_SA_KEY"))
.region(Region.GLOBAL)
.scopes("qca.access", "forward.access")
.build();
ForwardClient client = ForwardClient.builder().credential(credential).build()) {
System.out.println(client.models().list().data().size());
}
```

The credential lazily exchanges the SA Key for a platform SAT, caches and refreshes the SAT before expiry, and shares concurrent refreshes. Business requests use the SAT; the Key only goes to the fixed regional exchange endpoint. Set both `Region.CN` and the CN BaseURL for CN requests. No SA credential is inferred merely because `QODER_SA_KEY` exists.

The caller owns the credential. Keep it open until all clients using it are closed. Closing a client does not close its credential.

For a SAT managed by your application:

```java
ForwardClient client = ForwardClient.builder()
.authMode(com.qoder.cloudagents.auth.AuthMode.SA)
.accessToken(System.getenv("QODER_ACCESS_TOKEN"))
.build();
```

Forward SAT management uses explicit per-call SA Keys through `serviceAccountTokens()` and `identities().serviceAccountTokens()`; see the [usage guide](usage-sat-drive.md). A client without a business credential can call those methods; ordinary requests fail locally before network access until a business credential is configured.

Close the client after use. A static SAT is not automatically renewed. SA mode does not fall back to `QODER_PAT`. For application-managed rotation, implement `Credential.getToken()` returning a `CompletableFuture<String>`; it is called before each attempt and must be thread-safe and return promptly. Cancelling a request does not cancel a shared credential future.

## Asynchronous calls and cancellation

```java
java.util.concurrent.CompletableFuture<com.qoder.cloudagents.forward.Template> pending =
client.async().templates().retrieve("tmpl_example");
pending.cancel(true); // when the caller no longer needs the result
```

Cancellation propagates to the HTTP call and retry timer. The returned future follows Java's `CancellationException` contract. Interrupting a synchronous call cancels its request, preserves the interrupt flag and raises `ApiUserAbortException`.

`client.async()` shares its parent's resources; closing the view is a no-op. Closing the parent cancels active requests. `ForwardClient.builder().buildAsync()` creates an independently owned, closeable async client. Keep the owning client open until futures finish. Additional futures created with application-side `thenApply(...)` follow normal Java cancellation behavior; cancel the SDK-returned future to abort its request.

## Request options and errors

```java
com.qoder.cloudagents.core.RequestOptions options =
com.qoder.cloudagents.core.RequestOptions.builder()
.timeout(java.time.Duration.ofSeconds(30))
.totalTimeout(java.time.Duration.ofSeconds(60))
.maxRetries(1)
.build();
com.qoder.cloudagents.core.ApiResponse<com.qoder.cloudagents.forward.Template> response =
client.templates().withResponse().retrieve("tmpl_example", options);
```

- Default connection timeout: 5 seconds. Default response-header wait and read/write idle timeouts: 10 minutes. `.timeout(...)` overrides the latter limits for one attempt. Optional `.totalTimeout(...)` covers credentials, all attempts, backoff and body reads. Timeouts must be positive. SSE disables read-idle timing after accepted headers; an optional subscription-wide total deadline remains active, including reconnects. See [SSE lifecycle](session-streaming.md).
- Default maximum retries: 2, giving at most 3 attempts. The shared engine follows QCA safety rules: 409 is never retried; non-idempotent writes only qualify for 429 retries; non-repeatable bodies are not retried. Server retry hints remain subject to these checks.
- `ApiException` covers request failures. `ApiStatusException` exposes HTTP status, request ID, headers and parsed error body. Connection, timeout, cancellation and response parsing failures have corresponding subclasses. SA issuer failures use `ServiceAccountExchangeException`, exposing status/request ID without retaining the issuer body. Failed SA exchange does not switch to PAT.
- Models provide `requestId()`, `field(name)` for unknown fields and `rawJson()`. Unknown fields and numeric precision are preserved; invalid typed field access raises `ResponseValidationException`. Request metadata is separate from the JSON body.
- Template list parameters support `limit`, `status`, `afterId` or `beforeId`. Use `page.data()` for one page, `getNextPage()` / `getNextPageAsync()` for manual pagination, or `for (Template template : page)` for lazy synchronous iteration. Server order is retained; filters and request options follow subsequent pages. Check `hasNextPage()` before fetching; an exhausted page or repeated cursor raises `PaginationException`.

## Template configuration

```java
import com.qoder.cloudagents.forward.TemplateCreateParams;
import com.qoder.cloudagents.forward.TemplateUpdateParams;
import com.qoder.cloudagents.core.RequestOptions;

Template template = client.templates().create(
TemplateCreateParams.builder()
.name("Support assistant")
.modelId("auto")
.environmentId("env_example")
.build(),
RequestOptions.builder().idempotencyKey(java.util.UUID.randomUUID().toString()).build());

client.templates().update(template.id(), TemplateUpdateParams.builder()
.description("Updated description")
.multiagent(null) // explicitly clear the multi-agent configuration
.build());
```

An unset builder field is omitted. A setter with `null` writes JSON null; empty collections write `[]` or `{}`. The server decides which fields accept null. `omit("wire_name")` removes a previously set field. Requests and responses preserve unknown JSON fields; raw JSON can contain secrets and should not be logged. See [Template operations and configuration](templates.md) and the [lifecycle example](../examples/README.md).
15 changes: 12 additions & 3 deletions docs/forward-api-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ HTTP BaseURL 为 CN `https://api.qoder.com.cn/api/v1/forward` 或 Global `https:

来源:TypeScript main `597ff047be4fb0afb72fc6bd024ce957cf3e1fc1` 的 `src/forward/api-inventory.json`(120 项),补充 `service-account-token.ts` 和 `identity-service-account-token.ts` 的 3 项;operation ID 集合与 Go main `735f13c8bf5888bf70b5a93b9ba89bd0e078d4b9` 的 `forward/testdata/api-contracts.json`(123 项)交叉核对一致。

共 **123 个 HTTP 操作**:121 个普通 HTTP 操作、2 个 SSE 订阅操作;另有 1 个 WebSocket 连接入口。Java 资源方法见下表;当前尚未发布 Maven Central 版本。相同方法的同步/异步形式只计一个协议操作。
共 **123 个 HTTP 操作**:121 个普通 HTTP 操作、2 个 SSE 订阅操作;另有 1 个 WebSocket 连接入口。Java 资源方法见下表;Maven 接入方式见 [README](../README.md#installation),已发布版本见 [Releases](https://github.com/QoderAI/qoder-cloud-agents-sdk-java/releases)。相同方法的同步/异步形式只计一个协议操作。

这里只盘点协议入口;参数、响应、分页、认证和重试行为由对应资源指南与契约测试说明。

## 使用指南

- [客户端认证、超时、重试与取消](client-configuration.md)
- [Template](templates.md)、[Identity / Config](identities.md)、[Session / Thread](sessions.md) 和 [SSE](session-streaming.md)
- [Memory Store](memory-stores.md)、[Environment / File](environments-files.md)、[Skill / Vault](skills-vaults.md)
- [Schedule / Batch](schedules-batches.md)、[Channel / Pairing / QR](channels.md)、[Usage / SAT / Drive](usage-sat-drive.md)
- [Realtime](realtime.md) 和 [可运行示例](../examples/README.md)

## HTTP 操作

| # | operation ID | Java 资源方法(省略 client 前缀) | HTTP | 相对路径 | 返回形式 | 状态 |
Expand Down Expand Up @@ -156,9 +164,10 @@ HTTP BaseURL 为 CN `https://api.qoder.com.cn/api/v1/forward` 或 Global `https:
| SA Credential | `ServiceAccountCredential` | 平台 Token 置换、缓存、按需续期,与 Forward SAT 管理 API 分开 | 已实现 |
| 同步/异步与响应元信息 | `ForwardClient / AsyncForwardClient / withResponse` | 同一协议核心;取消和资源所有权明确;保留 request ID | 已实现 |

## 范围外
## 当前版本未提供

- Managed 全部接口、资源 Search、Forward Dream。
- Managed API:计划在后续版本支持,当前 0.1.0 尚未提供。
- 资源 Search、Forward Dream。
- Realtime Template 专用配置 GET/POST;普通 Template 的 managed_tool_config 已实现。
- Agent 运行时内部工具调用和免 Token 的内部服务认证模式。
- 新增 Drive 本地文件同步框架、采集/播放音频设备实现。
Expand Down
13 changes: 11 additions & 2 deletions docs/live-testing.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
# Forward 真实环境验收

`ForwardLiveCheck` 位于独立的 `examples` Maven 工程,消费本地安装的 SDK JAR。普通 `test` / `verify` 只运行模拟服务测试,不执行线上请求。
`ForwardLiveCheck` 位于独立的 `examples` Maven 工程,消费 SDK JAR 依赖。普通 `test` / `verify` 只运行模拟服务测试,不执行线上请求。

先构建、安装并验证消费者:
验证已发布版本时,检出对应发布标签,从空缓存解析并验证消费者,不先执行 SDK 的本地 `install`:

```sh
SDK_ACCEPTANCE_CACHE=$(mktemp -d)
./mvnw -B -ntp -Dmaven.repo.local="$SDK_ACCEPTANCE_CACHE" -f examples/pom.xml clean verify
```

后续 `exec:java` 命令也传入 `-Dmaven.repo.local="$SDK_ACCEPTANCE_CACHE"`。记录实际使用的 Maven settings/镜像、下载来源及 JAR 摘要,避免把已有缓存或源码安装包当作公开发布包。

开发或验证尚未发布的版本时,先构建、安装并验证消费者:

```sh
./mvnw -B -ntp clean install
Expand Down
15 changes: 13 additions & 2 deletions docs/maintaining.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,21 @@ The SDK's embedded version is filtered from `${project.version}`; do not replace
```sh
python3 .github/scripts/release_notes.py check
# After adding the actual release entry:
python3 .github/scripts/release_notes.py extract --version 0.1.0-rc.1
python3 .github/scripts/release_notes.py extract --version 0.1.0
```

The current version is `0.1.0-rc.1`. Acceptance records must include the resolved version, source SHA and JAR digest. Release versions must not be overwritten; a successful test of an older build is not evidence for a newer one.
The release version in this checkout is `0.1.0`. Also update the README dependency and version-specific maintenance examples. Acceptance records must include the resolved version, source SHA and JAR digest. Release versions must not be overwritten; a successful test of an older build is not evidence for a newer one.

### RC qualification for 0.1.0

The `0.1.0-rc.1` package from source `7742942d6d9dc3f681cb974b45ecfffe5083ad4c` was downloaded from Maven Central and tested on Java 8, 11, 17, 21 and 25 on 2026-10-21. Its JAR SHA-256 was `4eb33d871d234fd80b0302b2bf03d90d9c121d87ad0d50eed098a28505afaa86`.

- All 608 offline checks per JDK passed: 545 SDK regression tests, 62 example-consumer tests and one independent published-JAR/hash/Java 8 bytecode check, for 3,040 executions.
- The CN/Global PAT/SA live matrix ran 38 groups per JDK. The first run passed 184 of 190 groups. Six CN failures covered three Schedule reply checks, one Batch output check, one Realtime `work.failed` event and one Realtime PCM connection exception.
- Each failed case passed one targeted retry with the same package, configuration, assertions and JDK. Original failures were retained; no assertion was weakened. This is passing coverage after retries, not a clean first run or proof that the production failures were fixed. No cleanup failures or retained execution resources were reported.
- Channel/QR/Pairing external-platform flows, audio-device quality, long-duration stability and production fault injection were not part of that live matrix; see [live testing](live-testing.md).

The `0.1.0` preparation changes version metadata and documentation, keeping the RC's implementation and test sources unchanged. The release workflow must still build, sign and independently verify the actual `0.1.0` package; RC acceptance does not replace those checks.

## Build candidates and releases

Expand Down
8 changes: 6 additions & 2 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Java SDK release setup

The current version is `com.qoder.sdk:qoder-cloud-agents-sdk:0.1.0-rc.1`. The manual [Release workflow](../.github/workflows/release.yml) implements publication using repository Actions secrets. Dispatching it from `main` authorizes publication after all automated checks pass; there is no separate environment approval. The Central account, namespace, signing key and repository settings below must be configured first. The GitHub repository and internal Maven access do not grant Maven Central publishing access. No public release has been made by adding this workflow.
The release version in this checkout is `com.qoder.sdk:qoder-cloud-agents-sdk:0.1.0`. The manual [Release workflow](../.github/workflows/release.yml) publishes using repository Actions secrets. Dispatching it from `main` authorizes publication after all automated checks pass; there is no separate environment approval. The Central account, namespace, signing key and repository settings below must be configured first. The GitHub repository and internal Maven access do not grant Maven Central publishing access.

`0.1.0-rc.1` was published to Maven Central on 2026-10-21; see its [Release](https://github.com/QoderAI/qoder-cloud-agents-sdk-java/releases/tag/v0.1.0-rc.1) and [successful publication workflow](https://github.com/QoderAI/qoder-cloud-agents-sdk-java/actions/runs/39572349619). A version committed to a POM is not yet a published package: check the workflow's public verification and matching GitHub Release for each version.

## Optional GitHub protection

Expand Down Expand Up @@ -68,11 +70,13 @@ The namespace variable is a maintainer's setup confirmation, not an automatic ow

Prepare a PR that updates the root POM, the example project's version and SDK dependency, plus the changelog version section. Keep an empty `Unreleased` section. Review any version-specific install examples. Merge only after `quality` and all five Java checks succeed. The workflow does not bump versions or merge the PR.

For promotion from `0.1.0-rc.1` to `0.1.0`, review the [RC qualification and its limitations](maintaining.md#rc-qualification-for-010). Use the merged `0.1.0` main SHA and a new batch ID; leave recovery inputs empty. This creates a new version and tag, with its own signed bundle and public-package checks. Do not reuse the RC deployment, change its tag or copy/rename its JAR as the final release.

Open **Actions → Release → Run workflow**, select **main**, and verify the following inputs before dispatch. Successful preflight automatically proceeds to signing and publication; it does not pause for an additional approval.

| Input | First publication |
| --- | --- |
| `version` | The reviewed version without `v` or `SNAPSHOT`, for example `0.1.0-rc.1` |
| `version` | The reviewed version without `v` or `SNAPSHOT`, for example `0.1.0` |
| `commit_sha` | Full 40-character lowercase SHA of the current GitHub main |
| `batch_id` | 1–64 letters/digits/dots/underscores/hyphens, starting with a letter or digit |
| `resume_run_id` | Empty |
Expand Down
Loading
Loading