Skip to content

Commit 3e1cc0f

Browse files
authored
Merge branch 'main' into chore/release-lg-py-0.0.18
2 parents 5b2116f + bbf63c6 commit 3e1cc0f

File tree

224 files changed

+17393
-9929
lines changed

Some content is hidden

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

224 files changed

+17393
-9929
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: test
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
paths:
7+
- "crates/**"
8+
- ".github/workflows/rust.yml"
9+
- "tests/**"
10+
- "Cargo.toml"
11+
- ".cargo/**"
12+
pull_request:
13+
branches: [ "main" ]
14+
paths:
15+
- "sdks/community/rust/crates/**"
16+
- "sdks/community/rust/**/tests/**"
17+
- "sdks/community/rust/Cargo.toml"
18+
- "sdks/community/rust/.cargo/**"
19+
- ".github/workflows/rust-lint-test.yml"
20+
21+
defaults:
22+
run:
23+
working-directory: ./rust
24+
25+
jobs:
26+
rust:
27+
strategy:
28+
fail-fast: false
29+
matrix:
30+
os: [ ubuntu-latest, macos-latest, windows-latest ]
31+
32+
name: Rust SDK Tests [${{ matrix.os }}]
33+
runs-on: ${{ matrix.os }}
34+
35+
env:
36+
CARGO_TERM_COLOR: always
37+
38+
steps:
39+
- uses: actions/checkout@v4
40+
41+
- uses: Swatinem/rust-cache@v2
42+
43+
- name: Build
44+
run: cargo build --verbose
45+
46+
- name: Check formatting
47+
run: cargo fmt -- --check
48+
49+
- name: Check clippy
50+
run: cargo clippy -- -D warnings
51+
52+
- name: Publish ag-ui-core dry-run
53+
run: cargo publish -p ag-ui-core --dry-run
54+
55+
- name: Publish ag-ui-client dry-run
56+
run: cargo publish -p ag-ui-client --dry-run
57+
58+
- name: Run tests
59+
run: cargo test --verbose

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ mastra.db*
55
**/.DS_Store
66

77
test-results/
8+
9+
**/target
810
.turbo
911

1012
node_modules

docs/sdk/kotlin/client/overview.mdx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,17 @@ Real-time event streaming using Kotlin Flows:
6565
- Server-sent events (SSE) parsing
6666
- Automatic reconnection handling
6767
- Backpressure management
68+
- Automatic expansion of `TEXT_MESSAGE_CHUNK` / `TOOL_CALL_CHUNK` events into start/content/end triads
69+
- Thinking telemetry exposed through `AgentState.thinking`
6870

6971
### State Management
7072
Comprehensive state synchronization:
7173
- JSON Patch-based state updates
7274
- Automatic state validation
7375
- Error state handling
76+
- Tool call results surfaced as `ToolMessage` entries without additional wiring
77+
- Access to raw/custom protocol events via `AgentState.rawEvents` and `AgentState.customEvents`
78+
- Thinking streams exposed through `AgentState.thinking`
7479

7580
### Tool Integration
7681
Client-side tool execution framework:
@@ -108,6 +113,21 @@ agent.sendMessage("Hello!").collect { state ->
108113
}
109114
```
110115

116+
### Reading Thinking Telemetry
117+
118+
```kotlin
119+
agent.sendMessage("Plan the next steps").collect { state ->
120+
state.thinking?.let { thinking ->
121+
if (thinking.isThinking) {
122+
val thought = thinking.messages.lastOrNull().orEmpty()
123+
println("🤔 Agent thinking: $thought")
124+
} else if (thinking.messages.isNotEmpty()) {
125+
println("💡 Agent finished thinking: ${thinking.messages.joinToString()}")
126+
}
127+
}
128+
}
129+
```
130+
111131
### Convenience Builders
112132

113133
The SDK provides convenience builders for common configurations:
@@ -139,6 +159,12 @@ val chatAgent = StatefulAgUiAgent("https://api.example.com/agent") {
139159
chatAgent.chat("My name is Alice").collect { }
140160
chatAgent.chat("What's my name?").collect { state ->
141161
// Agent knows the name from previous message
162+
state.customEvents?.forEach { custom ->
163+
println("Custom event ${custom.name}: ${custom.value}")
164+
}
165+
state.rawEvents?.forEach { raw ->
166+
println("Raw payload: ${raw.event}")
167+
}
142168
}
143169
```
144170

@@ -170,4 +196,4 @@ val agent = AgUiAgent("https://api.example.com/agent") {
170196
- Initial state setup
171197
- State validation rules
172198
- Update strategies
173-
- Persistence options
199+
- Persistence options

docs/sdk/kotlin/overview.mdx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ chatAgent.chat("What's my name?").collect { state ->
106106
}
107107
```
108108

109+
Chunked protocol events (`TEXT_MESSAGE_CHUNK`, `TOOL_CALL_CHUNK`) are automatically rewritten into
110+
their corresponding start/content/end sequences, so Kotlin clients see the same structured events
111+
as non-chunked streams.
112+
113+
Thinking telemetry (`THINKING_*` events) is surfaced alongside normal messages, allowing UIs to indicate
114+
when an agent is reasoning internally before responding.
115+
109116
### Client-Side Tool Integration
110117

111118
```kotlin
@@ -172,4 +179,22 @@ println("Messages: ${currentState.messages.size}")
172179
agent.sendMessage("Hello").collect { state ->
173180
println("Updated state: ${state.messages.last()}")
174181
}
175-
```
182+
183+
// RAW and CUSTOM protocol events are surfaced for inspection
184+
state.rawEvents?.forEach { raw ->
185+
println("Raw event from ${raw.source ?: "unknown"}: ${raw.event}")
186+
}
187+
state.customEvents?.forEach { custom ->
188+
println("Custom event ${custom.name}: ${custom.value}")
189+
}
190+
191+
// Thinking telemetry stream
192+
state.thinking?.let { thinking ->
193+
if (thinking.isThinking) {
194+
val latest = thinking.messages.lastOrNull().orEmpty()
195+
println("Agent is thinking: $latest")
196+
} else if (thinking.messages.isNotEmpty()) {
197+
println("Agent finished thinking: ${thinking.messages.joinToString()}")
198+
}
199+
}
200+
```
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
title: "Agent"
3+
description: "Agent trait with core event handling"
4+
---
5+
6+
# Agent trait
7+
8+
The Agent trait defines the contract for connecting any execution backend to AG‑UI. Concrete implementations (like HttpAgent) stream core events that are handled by subscribers to maintain messages and state.
9+
10+
```rust
11+
use ag_ui_client::Agent;
12+
```
13+
14+
## Generics
15+
16+
- StateT: the agent state type. Must implement ag_ui_client::core::AgentState (Serialize + Deserialize + Clone + Debug + Send + Sync).
17+
- FwdPropsT: forwarded properties type passed through to downstream systems. Must implement ag_ui_client::core::FwdProps.
18+
19+
Defaults for both are serde_json::Value, which works well for quick starts.
20+
21+
## Running
22+
23+
The core trait method you implement is run, which returns an asynchronous stream of core events:
24+
25+
```rust
26+
#[async_trait::async_trait]
27+
pub trait Agent<StateT = JsonValue, FwdPropsT = JsonValue>
28+
where
29+
StateT: AgentState,
30+
FwdPropsT: FwdProps,
31+
{
32+
async fn run(
33+
&self,
34+
input: &RunAgentInput<StateT, FwdPropsT>,
35+
) -> Result<EventStream<'async_trait, StateT>, AgentError>;
36+
}
37+
```
38+
39+
For most consumers, use the provided convenience wrapper run_agent which:
40+
41+
- constructs an internal RunAgentInput from RunAgentParams
42+
- initializes an event handler with your subscribers
43+
- consumes the event stream and applies mutations
44+
- returns RunAgentResult with final result, new messages and new state
45+
46+
```rust
47+
use ag_ui_client::{Agent};
48+
use ag_ui_client::agent::{RunAgentParams, RunAgentResult};
49+
50+
let params = RunAgentParams::new().user("Hello!");
51+
let result: RunAgentResult<_> = my_agent.run_agent(&params, ()).await?;
52+
```
53+
54+
## RunAgentParams
55+
56+
A builder for run inputs. Supports typed state and forwarded props via new_typed.
57+
58+
```rust
59+
use ag_ui_client::agent::RunAgentParams;
60+
use ag_ui_client::core::types::{Message, Tool, Context};
61+
62+
// JSON state (default)
63+
let params = RunAgentParams::new()
64+
.user("User message")
65+
.add_tool(Tool::new("search".into(), "Search".into(), serde_json::json!({"type":"object"})))
66+
.add_context(Context::new("trace_id".into(), "123".into()));
67+
68+
// Strongly-typed state/forwarded props
69+
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default)]
70+
struct MyState { count: u32 }
71+
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default)]
72+
struct MyProps { tenant: String }
73+
74+
let params_typed = RunAgentParams::<MyState, MyProps>::new_typed()
75+
.with_state(MyState { count: 1 })
76+
.with_forwarded_props(MyProps { tenant: "acme".into() })
77+
.user("Go!");
78+
```
79+
80+
Builder helpers include:
81+
82+
- with_run_id(run_id)
83+
- add_tool(tool)
84+
- add_context(ctx)
85+
- with_forwarded_props(props)
86+
- with_state(state)
87+
- add_message(message)
88+
- user(content: impl Into<String>)
89+
90+
## RunAgentResult
91+
92+
Returned by run_agent:
93+
94+
```rust
95+
pub struct RunAgentResult<StateT: AgentState> {
96+
pub result: serde_json::Value,
97+
pub new_messages: Vec<Message>,
98+
pub new_state: StateT,
99+
}
100+
```
101+
102+
## AgentStateMutation
103+
104+
Subscriber methods may return AgentStateMutation to update messages and/or state and optionally stop propagation to later subscribers.
105+
106+
```rust
107+
pub struct AgentStateMutation<StateT = JsonValue> {
108+
pub messages: Option<Vec<Message>>,
109+
pub state: Option<StateT>,
110+
pub stop_propagation: bool,
111+
}
112+
```
113+
114+
## Errors
115+
116+
Most Agent operations return Result<_, AgentError> where AgentError = ag_ui_client::error::AgUiClientError. See the client error page for details. Common categories include configuration errors, HTTP transport/status errors (for HttpAgent), JSON errors, and subscriber errors.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
title: "HttpAgent"
3+
description: "HTTP-based Agent implementation for connecting to remote AI agents"
4+
---
5+
6+
# HttpAgent
7+
8+
The HttpAgent implements the Agent trait to provide HTTP-based connectivity to remote AI agents. It sends a POST request with a RunAgentInput payload and consumes a Server-Sent Events (SSE) stream of core events.
9+
10+
```rust
11+
use ag_ui_client::HttpAgent
12+
```
13+
14+
## Installation
15+
16+
```bash
17+
cargo add ag-ui-client
18+
```
19+
20+
## Creating an HttpAgent
21+
22+
Use the builder to configure the base URL, headers, timeouts and optional Agent ID.
23+
24+
```rust
25+
use ag_ui_client::HttpAgent;
26+
use reqwest::Url;
27+
28+
let agent = HttpAgent::builder()
29+
.with_url(Url::parse("https://api.example.com/v1/agent")?)
30+
.with_bearer_token("your-api-key")?
31+
.with_timeout(30)
32+
.build()?;
33+
```
34+
35+
Alternatively, pass a string URL and let the builder validate it:
36+
37+
```rust
38+
let agent = HttpAgent::builder()
39+
.with_url_str("https://api.example.com/v1/agent")?
40+
.build()?;
41+
```
42+
43+
## Configuration
44+
45+
HttpAgent exposes a fluent builder with the following options:
46+
47+
- `with_url(url: Url)` – Set the endpoint URL
48+
- `with_url_str(url: &str) -> Result<Self, AgentError>` – Parse and validate a string URL
49+
- `with_headers(headers: HeaderMap)` – Replace all headers
50+
- `with_header(name: &str, value: &str) -> Result<Self, AgentError>` – Add a single header
51+
- `with_header_typed(name: HeaderName, value: HeaderValue)` – Add a typed header
52+
- `with_bearer_token(token: &str) -> Result<Self, AgentError>` – Add Authorization: Bearer …
53+
- `with_http_client(client: reqwest::Client)` – Provide a custom reqwest client
54+
- `with_timeout(seconds: u64)` – Configure a request timeout on an internal client
55+
- `with_agent_id(agent_id: AgentId)` – Attach an optional AgentId reported via Agent::agent_id()
56+
57+
Note: The builder enforces http/https schemes and returns an AgentError::Config for invalid inputs.
58+
59+
## Running an agent over HTTP
60+
61+
Use Agent::run_agent with your parameters (messages, tools, state, etc.). The HttpAgent takes care of sending the request and streaming events.
62+
63+
```rust
64+
use ag_ui_client::{Agent, HttpAgent};
65+
use ag_ui_client::agent::RunAgentParams;
66+
use ag_ui_client::core::types::Message;
67+
68+
let params = RunAgentParams::new()
69+
.user("Tell me a short joke about Rust.");
70+
71+
let result = agent.run_agent(&params, None).await?;
72+
println!("Final result: {}", result.result);
73+
println!("New messages: {}", result.new_messages.len());
74+
```
75+
76+
## Errors
77+
78+
HttpAgent surfaces a few structured error types through AgUiClientError (aliased as AgentError):
79+
80+
- HttpTransport(reqwest::Error) – network and transport errors
81+
- HttpStatus { status, context } – non-success HTTP status with a snippet of the response body
82+
- Config { message } – invalid configuration inputs (e.g., malformed URL/header)
83+
84+
Use AgUiClientError::is_retryable() to determine if an error can be retried (timeouts, 5xx, 429).
85+
86+
## Implementation notes
87+
88+
- Requests are sent as JSON with Content-Type: application/json; responses are handled as text/event-stream SSE.
89+
- The response stream is decoded into Event<StateT> items defined in ag-ui-core.
90+
- Agent::agent_id() returns the optional AgentId configured on the builder.

0 commit comments

Comments
 (0)