Skip to content

Commit 0e8490f

Browse files
claudeZhiXiao-Lin
authored andcommitted
feat(workspace): S3-compatible workspace backend
Adds S3WorkspaceBackend behind the new `s3` Cargo feature. The backend implements WorkspaceFileSystem against any S3-compatible endpoint (AWS S3, MinIO, RustFS, Cloudflare R2, Backblaze B2, ...) via the AWS Rust SDK. It deliberately does NOT implement WorkspaceCommandRunner, WorkspaceSearch, or any git provider trait — capability gating then prevents bash/grep/glob/git from being registered when the backend is in use, so the model never sees tools the backend cannot service. Includes: - core/src/workspace/s3.rs: S3WorkspaceBackend + S3BackendConfig builder (endpoint / region / session_token / force_path_style / request_timeout), with_client() injection for tests. - WorkspaceServices::s3(config) and WorkspaceServices::from_s3_backend() factories with a 60s default per-operation timeout. - 11 unit tests covering key/prefix derivation, list prefix handling, capability matrix, and config builder. - core/tests/test_s3_backend.rs: env-gated end-to-end integration test that exercises the full session executor (read/write/edit/patch/ls, asserting bash/git/grep/glob are not registered) against a real S3-compatible endpoint; auto-cleans the per-run UUID prefix. - Node and Python SDKs expose S3WorkspaceBackend alongside LocalWorkspaceBackend via the same workspaceBackend / workspace_backend option surface; both SDK Rust crates depend on a3s-code-core with the s3 feature enabled. - README documents the new backend with cross-language usage examples. The new feature is fully optional: building a3s-code-core without `--features s3` keeps the dependency tree slim and excludes the AWS SDK.
1 parent 9b0b58c commit 0e8490f

13 files changed

Lines changed: 2199 additions & 67 deletions

File tree

Cargo.lock

Lines changed: 536 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,108 @@ Intent-gated tools:
455455

456456
This follows the same direction as modern agent harnesses: remove routine tool clutter from the model's context and expose capabilities only when the task asks for them.
457457

458+
Workspace backends are capability providers behind the stable built-in tool
459+
contracts. By default, `read`, `write`, `edit`, `patch`, `ls`, `grep`, `glob`,
460+
`bash`, and `git` operate on the local workspace. Embedded hosts can supply
461+
`WorkspaceServices` through `SessionOptions::with_workspace_backend(...)` so
462+
those same tools target a DFS, browser workspace, remote container, or other
463+
host-managed environment.
464+
465+
```rust
466+
use a3s_code_core::{Agent, SessionOptions, WorkspaceServices};
467+
468+
# async fn run() -> anyhow::Result<()> {
469+
let agent = Agent::new("agent.acl").await?;
470+
let workspace = WorkspaceServices::local("/repo");
471+
let session = agent.session(
472+
"/repo",
473+
Some(SessionOptions::new().with_workspace_backend(workspace)),
474+
)?;
475+
# Ok(())
476+
# }
477+
```
478+
479+
For non-local backends, A3S Code exposes tools according to declared workspace
480+
capabilities. `bash` is exposed only when a command runner is available,
481+
`grep`/`glob` only when a search provider is available, and `git` only when a
482+
workspace Git provider is available. Browser hosts can pair a virtual file
483+
system with a browser Git implementation, while cloud hosts can route the same
484+
tool contract through DFS or RPC-backed providers.
485+
486+
#### S3-compatible storage backend
487+
488+
When the `s3` Cargo feature is enabled, `S3WorkspaceBackend` lets built-in
489+
file tools (`read`, `write`, `edit`, `patch`, `ls`) target any S3-compatible
490+
endpoint — AWS S3, MinIO, RustFS, Cloudflare R2, Backblaze B2, and so on.
491+
`bash`, `git`, `grep`, and `glob` are intentionally not registered because
492+
object storage cannot service them.
493+
494+
```toml
495+
# Cargo.toml
496+
[dependencies]
497+
a3s-code-core = { version = "2.6", features = ["s3"] }
498+
```
499+
500+
```rust
501+
use a3s_code_core::{Agent, S3BackendConfig, SessionOptions, WorkspaceServices};
502+
503+
# async fn run() -> anyhow::Result<()> {
504+
let agent = Agent::new("agent.acl").await?;
505+
let config = S3BackendConfig::new(
506+
"workspace", // bucket
507+
"users/u1/sessions/s1", // workspace prefix inside the bucket
508+
"AKIA...", // access key id
509+
"...", // secret access key
510+
)
511+
.endpoint("https://minio.local:9000") // omit for AWS S3
512+
.region("us-east-1")
513+
.force_path_style(true); // true for MinIO/RustFS, false for AWS
514+
let session = agent.session(
515+
"s3://workspace/users/u1/sessions/s1",
516+
Some(SessionOptions::new().with_workspace_backend(WorkspaceServices::s3(config))),
517+
)?;
518+
# Ok(())
519+
# }
520+
```
521+
522+
The Node and Python SDKs expose the same backend:
523+
524+
```js
525+
// Node
526+
import { Agent, S3WorkspaceBackend } from '@a3s-lab/code';
527+
const session = agent.session(workspaceUri, {
528+
workspaceBackend: new S3WorkspaceBackend({
529+
endpoint: 'https://minio.local:9000',
530+
region: 'us-east-1',
531+
accessKeyId: 'AKIA...',
532+
secretAccessKey: '...',
533+
bucket: 'workspace',
534+
prefix: 'users/u1/sessions/s1',
535+
forcePathStyle: true,
536+
}),
537+
});
538+
```
539+
540+
```python
541+
# Python
542+
from a3s_code import Agent, S3WorkspaceBackend, SessionOptions
543+
opts = SessionOptions()
544+
opts.workspace_backend = S3WorkspaceBackend(
545+
bucket="workspace",
546+
prefix="users/u1/sessions/s1",
547+
access_key_id="AKIA...",
548+
secret_access_key="...",
549+
endpoint="https://minio.local:9000",
550+
region="us-east-1",
551+
force_path_style=True,
552+
)
553+
session = agent.session(workspace_uri, opts)
554+
```
555+
556+
S3 has no atomic read-modify-write, so concurrent writers to the same key may
557+
overwrite each other. Partition workspaces per session/user via the `prefix`
558+
field when running multi-tenant.
559+
458560
### 4. Programmatic Tool Calling
459561

460562
High-frequency tool chains should move out of the LLM loop.

core/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ uuid = { version = "1.6", features = ["v4", "serde"] }
104104
chrono = { version = "0.4", features = ["serde"] }
105105
rquickjs = { version = "0.11.0", features = ["futures"] }
106106

107+
# S3-compatible workspace backend (optional, gated by `s3` feature)
108+
aws-sdk-s3 = { version = "1", default-features = false, features = ["rt-tokio", "rustls"], optional = true }
109+
aws-credential-types = { version = "1", optional = true }
110+
aws-smithy-types = { version = "1", optional = true }
111+
aws-smithy-runtime-api = { version = "1", optional = true }
112+
107113
[target.'cfg(unix)'.dependencies]
108114
a3s-ahp = { version = "2.4", path = "../../ahp", optional = true, features = ["http", "websocket", "unix-socket"] }
109115

@@ -122,6 +128,15 @@ telemetry = [
122128
]
123129
# Enable AHP (Agent Harness Protocol) integration for external supervision
124130
ahp = ["dep:a3s-ahp"]
131+
# Enable the S3-compatible workspace backend (`S3WorkspaceBackend`).
132+
# Brings in the AWS Rust SDK and is safe to point at any S3-compatible
133+
# endpoint (AWS S3, MinIO, RustFS, Cloudflare R2, ...).
134+
s3 = [
135+
"dep:aws-sdk-s3",
136+
"dep:aws-credential-types",
137+
"dep:aws-smithy-types",
138+
"dep:aws-smithy-runtime-api",
139+
]
125140

126141
[dev-dependencies]
127142
# AHP for integration tests

core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,5 @@ pub use workspace::{
147147
WorkspacePathResolver, WorkspaceRef, WorkspaceSearch, WorkspaceServices,
148148
WorkspaceServicesBuilder, WorkspaceWriteOutcome,
149149
};
150+
#[cfg(feature = "s3")]
151+
pub use workspace::{S3BackendConfig, S3WorkspaceBackend};

core/src/workspace/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88
//! [`WorkspaceServices`] through [`WorkspaceServicesBuilder`].
99
1010
mod local;
11+
#[cfg(feature = "s3")]
12+
mod s3;
1113

1214
pub use local::LocalWorkspaceBackend;
15+
#[cfg(feature = "s3")]
16+
pub use s3::{S3BackendConfig, S3WorkspaceBackend};
1317

1418
use anyhow::{anyhow, bail, Result};
1519
use async_trait::async_trait;

0 commit comments

Comments
 (0)