Skip to content

Commit 584abe6

Browse files
authored
feat(unstable): expose typed session selector/capability fields for clients (#26)
* fix(codegen): merge changed shared unstable defs into stable surface * fix(codegen): include shared union metadata on typed variants * test: assert config option identity metadata survives json round-trip * Update ACP schema to v0.10.8 and regenerate types - Bump schema/version and version to 0.10.8 - Refresh unstable schema - Regenerate types_gen.go (includes PromptResponse usage support) * docs: regenerate README install version for v0.10.8 * chore: run release v0.10.8 and align stable interfaces
1 parent d5cd19f commit 584abe6

File tree

17 files changed

+1653
-615
lines changed

17 files changed

+1653
-615
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Learn more about the protocol itself at <https://agentclientprotocol.com>.
1414
<!-- `$ printf 'go get github.com/coder/acp-go-sdk@v%s\n' "$(cat schema/version)"` as bash -->
1515

1616
```bash
17-
go get github.com/coder/acp-go-sdk@v0.10.7
17+
go get github.com/coder/acp-go-sdk@v0.10.8
1818
```
1919

2020
## Get Started

acp_test.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ type agentFuncs struct {
118118
CancelFunc func(context.Context, CancelNotification) error
119119
SetSessionModeFunc func(ctx context.Context, params SetSessionModeRequest) (SetSessionModeResponse, error)
120120
// Unstable (schema/meta.unstable.json)
121-
UnstableForkSessionFunc func(context.Context, UnstableForkSessionRequest) (UnstableForkSessionResponse, error)
122-
UnstableListSessionsFunc func(context.Context, UnstableListSessionsRequest) (UnstableListSessionsResponse, error)
123-
UnstableResumeSessionFunc func(context.Context, UnstableResumeSessionRequest) (UnstableResumeSessionResponse, error)
124-
UnstableSetSessionConfigOptionFunc func(context.Context, UnstableSetSessionConfigOptionRequest) (UnstableSetSessionConfigOptionResponse, error)
125-
UnstableSetSessionModelFunc func(context.Context, UnstableSetSessionModelRequest) (UnstableSetSessionModelResponse, error)
121+
UnstableForkSessionFunc func(context.Context, UnstableForkSessionRequest) (UnstableForkSessionResponse, error)
122+
UnstableListSessionsFunc func(context.Context, UnstableListSessionsRequest) (UnstableListSessionsResponse, error)
123+
UnstableResumeSessionFunc func(context.Context, UnstableResumeSessionRequest) (UnstableResumeSessionResponse, error)
124+
SetSessionConfigOptionFunc func(context.Context, SetSessionConfigOptionRequest) (SetSessionConfigOptionResponse, error)
125+
UnstableSetSessionModelFunc func(context.Context, UnstableSetSessionModelRequest) (UnstableSetSessionModelResponse, error)
126126

127127
HandleExtensionMethodFunc func(context.Context, string, json.RawMessage) (any, error)
128128
}
@@ -208,12 +208,12 @@ func (a agentFuncs) UnstableResumeSession(ctx context.Context, params UnstableRe
208208
return UnstableResumeSessionResponse{}, nil
209209
}
210210

211-
// UnstableSetSessionConfigOption implements AgentExperimental.
212-
func (a agentFuncs) UnstableSetSessionConfigOption(ctx context.Context, params UnstableSetSessionConfigOptionRequest) (UnstableSetSessionConfigOptionResponse, error) {
213-
if a.UnstableSetSessionConfigOptionFunc != nil {
214-
return a.UnstableSetSessionConfigOptionFunc(ctx, params)
211+
// SetSessionConfigOption implements Agent.
212+
func (a agentFuncs) SetSessionConfigOption(ctx context.Context, params SetSessionConfigOptionRequest) (SetSessionConfigOptionResponse, error) {
213+
if a.SetSessionConfigOptionFunc != nil {
214+
return a.SetSessionConfigOptionFunc(ctx, params)
215215
}
216-
return UnstableSetSessionConfigOptionResponse{}, nil
216+
return SetSessionConfigOptionResponse{}, nil
217217
}
218218

219219
// UnstableSetSessionModel implements AgentExperimental.
@@ -259,6 +259,10 @@ func (a *forkOnlyUnstableAgent) SetSessionMode(context.Context, SetSessionModeRe
259259
return SetSessionModeResponse{}, nil
260260
}
261261

262+
func (a *forkOnlyUnstableAgent) SetSessionConfigOption(context.Context, SetSessionConfigOptionRequest) (SetSessionConfigOptionResponse, error) {
263+
return SetSessionConfigOptionResponse{}, nil
264+
}
265+
262266
func (a *forkOnlyUnstableAgent) UnstableForkSession(context.Context, UnstableForkSessionRequest) (UnstableForkSessionResponse, error) {
263267
a.called = true
264268
return UnstableForkSessionResponse{SessionId: "forked-session"}, nil
@@ -1137,6 +1141,10 @@ func (agentNoExtensions) SetSessionMode(ctx context.Context, params SetSessionMo
11371141
return SetSessionModeResponse{}, nil
11381142
}
11391143

1144+
func (agentNoExtensions) SetSessionConfigOption(ctx context.Context, params SetSessionConfigOptionRequest) (SetSessionConfigOptionResponse, error) {
1145+
return SetSessionConfigOptionResponse{}, nil
1146+
}
1147+
11401148
func TestExtensionMethods_ClientToAgentRequest(t *testing.T) {
11411149
c2aR, c2aW := io.Pipe()
11421150
a2cR, a2cW := io.Pipe()

agent_gen.go

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

client_gen.go

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

cmd/generate/internal/emit/types.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,17 @@ func emitUnion(f *File, name string, schema *load.Schema, parentDef *load.Defini
874874
}
875875
}
876876
}
877+
sharedProps := map[string]*load.Definition{}
878+
sharedRequired := map[string]struct{}{}
879+
if parentDef != nil {
880+
for k, p := range parentDef.Properties {
881+
sharedProps[k] = p
882+
}
883+
for _, r := range parentDef.Required {
884+
sharedRequired[r] = struct{}{}
885+
}
886+
}
887+
877888
for idx, v := range defs {
878889
if v == nil {
879890
continue
@@ -1004,16 +1015,26 @@ func emitUnion(f *File, name string, schema *load.Schema, parentDef *load.Defini
10041015
for _, r := range v.Required {
10051016
req[r] = struct{}{}
10061017
}
1007-
pkeys := make([]string, 0, len(v.Properties))
1008-
for pk := range v.Properties {
1018+
for r := range sharedRequired {
1019+
req[r] = struct{}{}
1020+
}
1021+
mergedProps := make(map[string]*load.Definition, len(sharedProps)+len(v.Properties))
1022+
for pk, pDef := range sharedProps {
1023+
mergedProps[pk] = pDef
1024+
}
1025+
for pk, pDef := range v.Properties {
1026+
mergedProps[pk] = pDef
1027+
}
1028+
pkeys := make([]string, 0, len(mergedProps))
1029+
for pk := range mergedProps {
10091030
pkeys = append(pkeys, pk)
10101031
}
10111032
sort.Strings(pkeys)
10121033
if v.Description != "" {
10131034
emitDocComment(f, v.Description)
10141035
}
10151036
for _, pk := range pkeys {
1016-
pDef := v.Properties[pk]
1037+
pDef := mergedProps[pk]
10171038
field := util.ToExportedField(pk)
10181039
if pDef.Description != "" {
10191040
st = appendDocComments(st, pDef.Description)

cmd/generate/internal/load/merge.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ func MergeStableAndUnstable(stableMeta *Meta, stableSchema *Schema, unstableMeta
7171
combinedSchema.Defs[name] = def
7272
}
7373

74+
promotedSharedDefs := promoteChangedSharedDefs(combinedSchema, stableSchema, unstableSchema, stableWires)
75+
ensureRefsAvailable(combinedSchema, unstableSchema, promotedSharedDefs)
76+
7477
for oldName, newName := range dupMap {
7578
if _, exists := combinedSchema.Defs[newName]; exists {
7679
return nil, nil, fmt.Errorf("cannot merge unstable schema: %q already exists in stable defs", newName)
@@ -241,6 +244,87 @@ func buildUnstableDuplicateSet(stableSchema *Schema, unstableSchema *Schema, uns
241244
return dupSet, nil
242245
}
243246

247+
func promoteChangedSharedDefs(combinedSchema *Schema, stableSchema *Schema, unstableSchema *Schema, stableWires map[string]struct{}) map[string]struct{} {
248+
promoted := map[string]struct{}{}
249+
queue := []string{}
250+
seen := map[string]struct{}{}
251+
252+
for name, sdef := range stableSchema.Defs {
253+
if sdef == nil || sdef.XMethod == "" || sdef.XSide == "" {
254+
continue
255+
}
256+
if _, ok := stableWires[sdef.XMethod]; !ok {
257+
continue
258+
}
259+
if !strings.HasSuffix(name, "Request") && !strings.HasSuffix(name, "Response") && !strings.HasSuffix(name, "Notification") {
260+
continue
261+
}
262+
queue = append(queue, name)
263+
seen[name] = struct{}{}
264+
}
265+
266+
for len(queue) > 0 {
267+
name := queue[0]
268+
queue = queue[1:]
269+
udef := unstableSchema.Defs[name]
270+
if udef == nil {
271+
continue
272+
}
273+
sdef := stableSchema.Defs[name]
274+
if sdef == nil || !reflect.DeepEqual(sdef, udef) {
275+
combinedSchema.Defs[name] = deepCopyDefinition(udef)
276+
promoted[name] = struct{}{}
277+
}
278+
for refName := range collectDefinitionRefs(udef) {
279+
if _, ok := seen[refName]; ok {
280+
continue
281+
}
282+
if unstableSchema.Defs[refName] == nil {
283+
continue
284+
}
285+
seen[refName] = struct{}{}
286+
queue = append(queue, refName)
287+
}
288+
}
289+
290+
return promoted
291+
}
292+
293+
func ensureRefsAvailable(combinedSchema *Schema, unstableSchema *Schema, roots map[string]struct{}) {
294+
if len(roots) == 0 {
295+
return
296+
}
297+
queue := make([]string, 0, len(roots))
298+
seen := make(map[string]struct{}, len(roots))
299+
for name := range roots {
300+
queue = append(queue, name)
301+
seen[name] = struct{}{}
302+
}
303+
for len(queue) > 0 {
304+
name := queue[0]
305+
queue = queue[1:]
306+
def := combinedSchema.Defs[name]
307+
if def == nil {
308+
def = unstableSchema.Defs[name]
309+
}
310+
if def == nil {
311+
continue
312+
}
313+
refs := collectDefinitionRefs(def)
314+
for refName := range refs {
315+
if _, ok := combinedSchema.Defs[refName]; !ok {
316+
if uref := unstableSchema.Defs[refName]; uref != nil {
317+
combinedSchema.Defs[refName] = deepCopyDefinition(uref)
318+
}
319+
}
320+
if _, ok := seen[refName]; !ok {
321+
seen[refName] = struct{}{}
322+
queue = append(queue, refName)
323+
}
324+
}
325+
}
326+
}
327+
244328
func collectDefinitionRefs(root *Definition) map[string]struct{} {
245329
refs := map[string]struct{}{}
246330
visitDefinition(root, func(d *Definition) {

cmd/generate/internal/load/merge_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,55 @@ func TestMergeStableAndUnstable(t *testing.T) {
266266
}
267267
})
268268

269+
t.Run("promotes changed shared defs and pulls in new dependencies", func(t *testing.T) {
270+
stableMeta := &Meta{Version: 1, AgentMethods: map[string]string{"session_new": "session/new"}}
271+
stableSchema := &Schema{Defs: map[string]*Definition{
272+
"NewSessionResponse": {
273+
Type: "object",
274+
XMethod: "session/new",
275+
XSide: "agent",
276+
Properties: map[string]*Definition{
277+
"sessionId": {Type: "string"},
278+
},
279+
},
280+
}}
281+
282+
unstableMeta := &Meta{Version: 1}
283+
unstableSchema := &Schema{Defs: map[string]*Definition{
284+
"NewSessionResponse": {
285+
Type: "object",
286+
XMethod: "session/new",
287+
XSide: "agent",
288+
Properties: map[string]*Definition{
289+
"sessionId": {Type: "string"},
290+
"models": ref("SessionModelState"),
291+
},
292+
},
293+
"SessionModelState": {
294+
Type: "object",
295+
Properties: map[string]*Definition{
296+
"currentModelId": {Type: "string"},
297+
},
298+
},
299+
}}
300+
301+
_, combinedSchema := mustMerge(t, stableMeta, stableSchema, unstableMeta, unstableSchema)
302+
303+
merged := combinedSchema.Defs["NewSessionResponse"]
304+
if merged == nil {
305+
t.Fatalf("expected NewSessionResponse to exist")
306+
}
307+
if merged.Properties == nil || merged.Properties["models"] == nil {
308+
t.Fatalf("expected promoted NewSessionResponse to contain models")
309+
}
310+
if got := merged.Properties["models"].Ref; got != "#/$defs/SessionModelState" {
311+
t.Fatalf("expected models ref to SessionModelState; got %q", got)
312+
}
313+
if combinedSchema.Defs["SessionModelState"] == nil {
314+
t.Fatalf("expected SessionModelState dependency to be copied from unstable")
315+
}
316+
})
317+
269318
t.Run("stable defs are not mutated", func(t *testing.T) {
270319
stableMeta := &Meta{Version: 1, AgentMethods: map[string]string{"stable": "stable/method"}}
271320
unstableMeta := &Meta{Version: 1, AgentMethods: map[string]string{"foo": "unstable/foo"}}

example/agent/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ func (a *exampleAgent) UnstableResumeSession(ctx context.Context, params acp.Uns
5656
return acp.UnstableResumeSessionResponse{}, acp.NewMethodNotFound(acp.AgentMethodSessionResume)
5757
}
5858

59-
// UnstableSetSessionConfigOption implements acp.AgentExperimental.
60-
func (a *exampleAgent) UnstableSetSessionConfigOption(ctx context.Context, params acp.UnstableSetSessionConfigOptionRequest) (acp.UnstableSetSessionConfigOptionResponse, error) {
61-
return acp.UnstableSetSessionConfigOptionResponse{}, acp.NewMethodNotFound(acp.AgentMethodSessionSetConfigOption)
59+
// SetSessionConfigOption implements acp.Agent.
60+
func (a *exampleAgent) SetSessionConfigOption(ctx context.Context, params acp.SetSessionConfigOptionRequest) (acp.SetSessionConfigOptionResponse, error) {
61+
return acp.SetSessionConfigOptionResponse{}, acp.NewMethodNotFound(acp.AgentMethodSessionSetConfigOption)
6262
}
6363

6464
// UnstableSetSessionModel implements acp.AgentExperimental.

example_agent_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ func (a *agentExample) SetSessionMode(ctx context.Context, params SetSessionMode
1717
return SetSessionModeResponse{}, nil
1818
}
1919

20+
// SetSessionConfigOption implements Agent.
21+
func (a *agentExample) SetSessionConfigOption(ctx context.Context, params SetSessionConfigOptionRequest) (SetSessionConfigOptionResponse, error) {
22+
return SetSessionConfigOptionResponse{}, nil
23+
}
24+
2025
func (a *agentExample) SetAgentConnection(c *AgentSideConnection) { a.conn = c }
2126

2227
func (agentExample) Authenticate(ctx context.Context, _ AuthenticateRequest) (AuthenticateResponse, error) {

helpers_gen.go

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

0 commit comments

Comments
 (0)