Skip to content

Commit da8839f

Browse files
committed
feat(sdk): align capabilities and complete documentation
1 parent 62ed0d9 commit da8839f

109 files changed

Lines changed: 12293 additions & 372 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.

Cargo.lock

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

sdk/go/README.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -138,19 +138,32 @@ report, err := session.VerifyCommands(ctx, "release", []code.VerificationCommand
138138
_, _, _, _ = content, result, matches, report
139139
```
140140

141-
The stable surface includes:
142-
143-
- Agent creation, session creation/resume/list/close, and MCP refresh.
144-
- `Send`, `Run`, `Stream`, history, save, cancellation, and close.
141+
The stable surface is aligned with the Node.js and Python SDKs for
142+
serializable Agent and Session capabilities, including:
143+
144+
- Agent creation, session creation/resume/replace/list/close, worker sessions,
145+
MCP refresh/idle disconnect, and agent-directory serving.
146+
- `Send`, `Run`, `Stream`, attachments, checkpoint resume, history, save,
147+
cancellation, and close.
148+
- Parallel, resumable parallel, workflow-step, and pipeline orchestration.
145149
- Generic `Tool` plus file, shell, search, Git, web, QuickJS program, and
146150
delegated-task conveniences.
147151
- Run snapshots and replay events, active tools, traces, artifacts, pending
148-
confirmations, and verification reports.
149-
- Live agent directories, skills, and MCP server add/remove/status.
150-
151-
Rust trait-object injections such as custom `LlmClient`, workspace backends,
152-
hook executors, and memory implementations are intentionally Rust-only. The Go
153-
bridge accepts their serializable configuration counterparts where one exists.
152+
confirmations, subagent tasks, and verification reports.
153+
- Memory recall/recording, lane queues, external tasks, dead letters, and
154+
queue metrics.
155+
- Go-backed lifecycle hooks, budget guards, and slash commands through the
156+
callback transport.
157+
- Live agent directories, workers, skills, dynamic workflows, and MCP server
158+
add/remove/status.
159+
- File memory/session persistence, default security, local or S3 workspaces,
160+
remote Git, permission/confirmation policies, retention, trajectory,
161+
deterministic ID/clock replay, delegation, and prompt-slot configuration.
162+
163+
Rust trait-object injections such as a custom `LlmClient`, arbitrary workspace
164+
implementation, or custom memory store implementation remain Rust-native
165+
extension points. The Go SDK exposes every corresponding serializable
166+
configuration and callback-backed extension point.
154167

155168
## Errors
156169

sdk/go/agent.go

Lines changed: 167 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,96 @@ func (agent *Agent) ResumeSession(
175175
return agent.newSession(ctx, op, params)
176176
}
177177

178+
func (agent *Agent) ReplaceSession(
179+
ctx context.Context,
180+
current *Session,
181+
options *SessionOptions,
182+
) (*Session, error) {
183+
const op = "agent_replace_session"
184+
if err := validateAgent(agent, ctx, op); err != nil {
185+
return nil, err
186+
}
187+
if current == nil || current.handle == "" {
188+
return nil, invalid(op, "current session is not initialized")
189+
}
190+
params := current.params()
191+
params["agent_id"] = agent.id
192+
if options != nil {
193+
params["options"] = options
194+
}
195+
return agent.newSession(ctx, op, params)
196+
}
197+
198+
func (agent *Agent) SessionForAgent(
199+
ctx context.Context,
200+
workspace string,
201+
agentName string,
202+
agentDirs []string,
203+
options *SessionOptions,
204+
) (*Session, error) {
205+
const op = "agent_session_for_agent"
206+
if err := validateAgent(agent, ctx, op); err != nil {
207+
return nil, err
208+
}
209+
if strings.TrimSpace(workspace) == "" {
210+
return nil, invalid(op, "workspace cannot be empty")
211+
}
212+
if strings.TrimSpace(agentName) == "" {
213+
return nil, invalid(op, "agent name cannot be empty")
214+
}
215+
params := map[string]any{
216+
"agent_id": agent.id,
217+
"workspace": workspace,
218+
"agent_name": agentName,
219+
"agent_dirs": agentDirs,
220+
}
221+
if options != nil {
222+
params["options"] = options
223+
}
224+
return agent.newSession(ctx, op, params)
225+
}
226+
227+
func (agent *Agent) SessionForWorker(
228+
ctx context.Context,
229+
workspace string,
230+
worker WorkerAgentSpec,
231+
options *SessionOptions,
232+
) (*Session, error) {
233+
const op = "agent_session_for_worker"
234+
if err := validateAgent(agent, ctx, op); err != nil {
235+
return nil, err
236+
}
237+
if strings.TrimSpace(workspace) == "" {
238+
return nil, invalid(op, "workspace cannot be empty")
239+
}
240+
if strings.TrimSpace(worker.Name) == "" || strings.TrimSpace(worker.Description) == "" {
241+
return nil, invalid(op, "worker name and description cannot be empty")
242+
}
243+
params := map[string]any{
244+
"agent_id": agent.id,
245+
"workspace": workspace,
246+
"worker": worker,
247+
}
248+
if options != nil {
249+
params["options"] = options
250+
}
251+
return agent.newSession(ctx, op, params)
252+
}
253+
178254
func (agent *Agent) newSession(
179255
ctx context.Context,
180256
operation string,
181257
params map[string]any,
182258
) (*Session, error) {
183259
var created struct {
184-
SessionHandle string `json:"session_handle"`
185-
SessionID string `json:"session_id"`
186-
Workspace string `json:"workspace"`
187-
InitWarning *string `json:"init_warning"`
260+
SessionHandle string `json:"session_handle"`
261+
SessionID string `json:"session_id"`
262+
Workspace string `json:"workspace"`
263+
InitWarning *string `json:"init_warning"`
264+
TenantID *string `json:"tenant_id"`
265+
Principal *string `json:"principal"`
266+
AgentTemplateID *string `json:"agent_template_id"`
267+
CorrelationID *string `json:"correlation_id"`
188268
}
189269
if err := agent.runtime.Request(ctx, operation, params, &created); err != nil {
190270
return nil, err
@@ -198,11 +278,15 @@ func (agent *Agent) newSession(
198278
)
199279
}
200280
return &Session{
201-
runtime: agent.runtime,
202-
handle: created.SessionHandle,
203-
id: created.SessionID,
204-
workspace: created.Workspace,
205-
initWarning: created.InitWarning,
281+
runtime: agent.runtime,
282+
handle: created.SessionHandle,
283+
id: created.SessionID,
284+
workspace: created.Workspace,
285+
initWarning: created.InitWarning,
286+
tenantID: created.TenantID,
287+
principal: created.Principal,
288+
agentTemplateID: created.AgentTemplateID,
289+
correlationID: created.CorrelationID,
206290
}, nil
207291
}
208292

@@ -251,6 +335,80 @@ func (agent *Agent) CloseSession(ctx context.Context, sessionID string) (bool, e
251335
return result.Closed, err
252336
}
253337

338+
func (agent *Agent) DisconnectIdleMCP(
339+
ctx context.Context,
340+
idleThresholdMS uint64,
341+
) ([]string, error) {
342+
const op = "agent_disconnect_idle_mcp"
343+
if err := validateAgent(agent, ctx, op); err != nil {
344+
return nil, err
345+
}
346+
var result struct {
347+
Names []string `json:"names"`
348+
}
349+
err := agent.runtime.Request(ctx, op, map[string]any{
350+
"agent_id": agent.id,
351+
"idle_threshold_ms": idleThresholdMS,
352+
}, &result)
353+
return result.Names, err
354+
}
355+
356+
type ServeHandle struct {
357+
runtime Runtime
358+
handle string
359+
stopOnce sync.Once
360+
stopErr error
361+
}
362+
363+
func (agent *Agent) ServeAgentDir(
364+
ctx context.Context,
365+
dir string,
366+
workspace string,
367+
options *SessionOptions,
368+
) (*ServeHandle, error) {
369+
const op = "agent_serve_agent_dir"
370+
if err := validateAgent(agent, ctx, op); err != nil {
371+
return nil, err
372+
}
373+
if strings.TrimSpace(dir) == "" || strings.TrimSpace(workspace) == "" {
374+
return nil, invalid(op, "agent directory and workspace cannot be empty")
375+
}
376+
params := map[string]any{
377+
"agent_id": agent.id,
378+
"dir": dir,
379+
"workspace": workspace,
380+
}
381+
if options != nil {
382+
params["options"] = options
383+
}
384+
var result struct {
385+
Handle string `json:"serve_handle"`
386+
}
387+
if err := agent.runtime.Request(ctx, op, params, &result); err != nil {
388+
return nil, err
389+
}
390+
if result.Handle == "" {
391+
return nil, sdkError(op, CodeProtocol, "bridge returned an empty serve handle", nil)
392+
}
393+
return &ServeHandle{runtime: agent.runtime, handle: result.Handle}, nil
394+
}
395+
396+
func (handle *ServeHandle) Stop(ctx context.Context) error {
397+
const op = "agent_stop_serve"
398+
if handle == nil {
399+
return nil
400+
}
401+
if ctx == nil {
402+
return invalid(op, "context cannot be nil")
403+
}
404+
handle.stopOnce.Do(func() {
405+
handle.stopErr = handle.runtime.Request(ctx, op, map[string]any{
406+
"serve_handle": handle.handle,
407+
}, nil)
408+
})
409+
return handle.stopErr
410+
}
411+
254412
func (agent *Agent) IsClosed(ctx context.Context) (bool, error) {
255413
const op = "agent_is_closed"
256414
if err := validateAgent(agent, ctx, op); err != nil {

sdk/go/agent_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ func TestCreateSessionAndCloseWithInjectedRuntime(t *testing.T) {
2121
}
2222
return map[string]any{"agent_id": "agent-1"}, nil
2323
case "session_create":
24+
options, ok := params["options"].(*SessionOptions)
25+
if !ok || options.HostEnv == nil ||
26+
options.HostEnv.SequentialIDPrefix == nil ||
27+
*options.HostEnv.SequentialIDPrefix != "replay" {
28+
t.Fatalf("deterministic host env was not forwarded: %#v", params)
29+
}
2430
return map[string]any{
2531
"session_handle": "handle-1",
2632
"session_id": "session-1",
@@ -45,8 +51,14 @@ func TestCreateSessionAndCloseWithInjectedRuntime(t *testing.T) {
4551
if err != nil {
4652
t.Fatal(err)
4753
}
54+
prefix := "replay"
55+
fixedTime := uint64(1_700_000_000_000)
4856
session, err := agent.Session(ctx, "C:/repo", &SessionOptions{
4957
SessionID: "session-1",
58+
HostEnv: &HostEnvConfig{
59+
SequentialIDPrefix: &prefix,
60+
FixedTimeMS: &fixedTime,
61+
},
5062
})
5163
if err != nil {
5264
t.Fatal(err)

sdk/go/bridge/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ name = "a3s-code-go-bridge"
1717
path = "src/main.rs"
1818

1919
[dependencies]
20-
a3s-code-core = { version = "6.5.2", path = "../../../core" }
20+
a3s-code-core = { version = "6.5.2", path = "../../../core", features = ["s3", "serve"] }
21+
async-trait = "0.1"
22+
anyhow = "1.0"
23+
base64 = "0.22"
2124
serde = { version = "1.0", features = ["derive"] }
2225
serde_json = "1.0"
2326
tokio = { version = "1.35", features = [
@@ -28,6 +31,7 @@ tokio = { version = "1.35", features = [
2831
"sync",
2932
"time",
3033
] }
34+
tokio-util = { version = "0.7" }
3135

3236
[dev-dependencies]
3337
tempfile = "3.10"

0 commit comments

Comments
 (0)