Skip to content

Commit 02430f1

Browse files
feat(agents): serve agents commands from the platform API by default
list/get/schemas call /api/agents/... and fall back to the classic API (one-time warning) when the tenant gate is off. run calls POST /api/agents/{agent_id}/runs with the agent_id folded into the --json body (the platform route takes it as a path parameter); because the run body is user-authored and shaped differently on the two surfaces, run never falls back automatically — gate-closed errors with GLEAN_LEGACY_APIS guidance, under which the classic messages/fragments body is expected. agentIDRequest's canonical tag becomes agent_id (camelCase agentId still accepted via cmdutil normalization). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent a6eb8fc commit 02430f1

3 files changed

Lines changed: 254 additions & 39 deletions

File tree

cmd/agents.go

Lines changed: 132 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package cmd
22

33
import (
44
"context"
5+
"encoding/json"
6+
"fmt"
57
"io"
68

79
glean "github.com/gleanwork/api-client-go"
@@ -11,10 +13,21 @@ import (
1113
"github.com/spf13/cobra"
1214
)
1315

14-
// agentIDRequest is a CLI-only request struct for commands that take an agent
15-
// ID as their only input. Using camelCase JSON tag for CLI consistency.
16+
// agentIDRequest is a CLI-only request struct for commands whose only input
17+
// is an agent ID. The snake_case tag matches the platform input shape;
18+
// cmdutil transparently normalizes camelCase agentId.
1619
type agentIDRequest struct {
17-
AgentID string `json:"agentId"`
20+
AgentID string `json:"agent_id"`
21+
}
22+
23+
// agentRunRequest is the CLI request for `agents run`. The platform route
24+
// takes the agent ID as a path parameter (POST /api/agents/{agent_id}/runs),
25+
// so the CLI folds it into the --json body alongside the run inputs.
26+
type agentRunRequest struct {
27+
AgentID string `json:"agent_id"`
28+
Input map[string]any `json:"input,omitempty"`
29+
Messages []components.PlatformMessage `json:"messages,omitempty"`
30+
Metadata map[string]any `json:"metadata,omitempty"`
1831
}
1932

2033
func NewCmdAgents() *cobra.Command {
@@ -25,11 +38,19 @@ func NewCmdAgents() *cobra.Command {
2538
2639
Agents are AI-powered workflows that can search, reason, and act on your company's knowledge.
2740
41+
Agent commands are served by the platform API (/api/agents/...) and responses
42+
use its snake_case shape. list/get/schemas fall back to the classic API with a
43+
warning when the platform API is not enabled; run does not fall back
44+
automatically because its request body differs between the two surfaces. Set
45+
GLEAN_LEGACY_APIS=1 to use the classic API directly (run then expects the
46+
classic messages/fragments body).
47+
2848
Example:
2949
glean agents list
30-
glean agents get --json '{"agentId":"<id>"}'
31-
glean agents schemas --json '{"agentId":"<id>"}'
32-
glean agents run --json '{"agentId":"<id>","messages":[{"author":"USER","fragments":[{"text":"summarize Q1 results"}]}]}'`,
50+
glean agents get --json '{"agent_id":"<id>"}'
51+
glean agents schemas --json '{"agent_id":"<id>"}'
52+
glean agents run --json '{"agent_id":"<id>","input":{"query":"summarize Q1 results"}}'
53+
glean agents run --json '{"agent_id":"<id>","messages":[{"role":"user","content":[{"type":"text","text":"summarize Q1 results"}]}]}'`,
3354
}
3455
cmd.AddCommand(
3556
newAgentsListCmd(),
@@ -40,26 +61,56 @@ Example:
4061
return cmd
4162
}
4263

64+
// agentsListTextFn renders whichever agents list shape the request produced:
65+
// the platform response (default) or the classic response (legacy fallback).
66+
func agentsListTextFn(w io.Writer, v any) error {
67+
header := []string{"ID", "NAME", "DESCRIPTION"}
68+
switch resp := v.(type) {
69+
case *components.PlatformAgentsSearchResponse:
70+
rows := make([][]string, len(resp.Agents))
71+
for i, a := range resp.Agents {
72+
desc := ""
73+
if a.Description != nil {
74+
desc = output.Truncate(*a.Description, 60)
75+
}
76+
rows[i] = []string{a.AgentID, a.Name, desc}
77+
}
78+
return output.WriteTable(w, header, rows)
79+
case *components.SearchAgentsResponse:
80+
rows := make([][]string, len(resp.Agents))
81+
for i, a := range resp.Agents {
82+
desc := ""
83+
if a.Description != nil {
84+
desc = output.Truncate(*a.Description, 60)
85+
}
86+
rows[i] = []string{a.AgentID, a.Name, desc}
87+
}
88+
return output.WriteTable(w, header, rows)
89+
default:
90+
return output.WriteJSON(w, v)
91+
}
92+
}
93+
4394
func newAgentsListCmd() *cobra.Command {
44-
return cmdutil.Build(cmdutil.Spec[components.SearchAgentsRequest]{
45-
Use: "list",
46-
Short: "List available agents",
47-
TextFn: func(w io.Writer, v any) error {
48-
resp, ok := v.(*components.SearchAgentsResponse)
49-
if !ok {
50-
return output.WriteJSON(w, v)
95+
return cmdutil.Build(cmdutil.Spec[components.PlatformAgentsSearchRequest]{
96+
Use: "list",
97+
Short: "List available agents",
98+
Endpoint: "/api/agents/search",
99+
TextFn: agentsListTextFn,
100+
Run: func(ctx context.Context, sdk *glean.Glean, req components.PlatformAgentsSearchRequest) (any, error) {
101+
resp, err := sdk.Agents.Search(ctx, req)
102+
if err != nil {
103+
return nil, err
51104
}
52-
rows := make([][]string, len(resp.Agents))
53-
for i, a := range resp.Agents {
54-
desc := ""
55-
if a.Description != nil {
56-
desc = output.Truncate(*a.Description, 60)
105+
return resp.PlatformAgentsSearchResponse, nil
106+
},
107+
LegacyRun: func(ctx context.Context, sdk *glean.Glean, rawJSON []byte) (any, error) {
108+
var req components.SearchAgentsRequest
109+
if len(rawJSON) > 0 {
110+
if err := json.Unmarshal(rawJSON, &req); err != nil {
111+
return nil, fmt.Errorf("invalid --json: %w", err)
57112
}
58-
rows[i] = []string{a.AgentID, a.Name, desc}
59113
}
60-
return output.WriteTable(w, []string{"ID", "NAME", "DESCRIPTION"}, rows)
61-
},
62-
Run: func(ctx context.Context, sdk *glean.Glean, req components.SearchAgentsRequest) (any, error) {
63114
resp, err := sdk.Client.Agents.List(ctx, req)
64115
if err != nil {
65116
return nil, err
@@ -69,12 +120,34 @@ func newAgentsListCmd() *cobra.Command {
69120
})
70121
}
71122

123+
// parseAgentIDRequest is the shared LegacyRun payload parse for get/schemas:
124+
// the input is a bare agent ID, identical on both surfaces.
125+
func parseAgentIDRequest(rawJSON []byte) (agentIDRequest, error) {
126+
var req agentIDRequest
127+
if err := json.Unmarshal(rawJSON, &req); err != nil {
128+
return req, fmt.Errorf("invalid --json: %w", err)
129+
}
130+
return req, nil
131+
}
132+
72133
func newAgentsGetCmd() *cobra.Command {
73134
return cmdutil.Build(cmdutil.Spec[agentIDRequest]{
74135
Use: "get",
75136
Short: "Get an agent by ID",
76137
JSONRequired: true,
138+
Endpoint: "/api/agents/{agent_id}",
77139
Run: func(ctx context.Context, sdk *glean.Glean, req agentIDRequest) (any, error) {
140+
resp, err := sdk.Agents.Get(ctx, req.AgentID)
141+
if err != nil {
142+
return nil, err
143+
}
144+
return resp.PlatformAgentGetResponse, nil
145+
},
146+
LegacyRun: func(ctx context.Context, sdk *glean.Glean, rawJSON []byte) (any, error) {
147+
req, err := parseAgentIDRequest(rawJSON)
148+
if err != nil {
149+
return nil, err
150+
}
78151
resp, err := sdk.Client.Agents.Retrieve(ctx, req.AgentID, nil, nil)
79152
if err != nil {
80153
return nil, err
@@ -89,7 +162,19 @@ func newAgentsSchemasCmd() *cobra.Command {
89162
Use: "schemas",
90163
Short: "Get the schemas for an agent",
91164
JSONRequired: true,
165+
Endpoint: "/api/agents/{agent_id}/schemas",
92166
Run: func(ctx context.Context, sdk *glean.Glean, req agentIDRequest) (any, error) {
167+
resp, err := sdk.Agents.GetSchemas(ctx, req.AgentID, nil)
168+
if err != nil {
169+
return nil, err
170+
}
171+
return resp.PlatformAgentSchemasResponse, nil
172+
},
173+
LegacyRun: func(ctx context.Context, sdk *glean.Glean, rawJSON []byte) (any, error) {
174+
req, err := parseAgentIDRequest(rawJSON)
175+
if err != nil {
176+
return nil, err
177+
}
93178
resp, err := sdk.Client.Agents.RetrieveSchemas(ctx, req.AgentID, nil, nil)
94179
if err != nil {
95180
return nil, err
@@ -100,11 +185,34 @@ func newAgentsSchemasCmd() *cobra.Command {
100185
}
101186

102187
func newAgentsRunCmd() *cobra.Command {
103-
return cmdutil.Build(cmdutil.Spec[components.AgentRunCreate]{
188+
return cmdutil.Build(cmdutil.Spec[agentRunRequest]{
104189
Use: "run",
105190
Short: "Run an agent (synchronous)",
106191
JSONRequired: true,
107-
Run: func(ctx context.Context, sdk *glean.Glean, req components.AgentRunCreate) (any, error) {
192+
Endpoint: "/api/agents/{agent_id}/runs",
193+
// The run body is user-authored and shaped differently on the two
194+
// surfaces (platform input/messages vs classic messages/fragments),
195+
// so gate-closed errors instead of silently replaying the payload.
196+
FallbackMode: cmdutil.FallbackEnvOnly,
197+
Run: func(ctx context.Context, sdk *glean.Glean, req agentRunRequest) (any, error) {
198+
if req.AgentID == "" {
199+
return nil, fmt.Errorf("agent_id is required in the --json payload")
200+
}
201+
resp, err := sdk.Agents.CreateRun(ctx, req.AgentID, components.PlatformAgentRunCreateRequest{
202+
Input: req.Input,
203+
Messages: req.Messages,
204+
Metadata: req.Metadata,
205+
})
206+
if err != nil {
207+
return nil, err
208+
}
209+
return resp.PlatformAgentRunWaitResponse, nil
210+
},
211+
LegacyRun: func(ctx context.Context, sdk *glean.Glean, rawJSON []byte) (any, error) {
212+
var req components.AgentRunCreate
213+
if err := json.Unmarshal(rawJSON, &req); err != nil {
214+
return nil, fmt.Errorf("invalid --json: %w", err)
215+
}
108216
resp, err := sdk.Client.Agents.Run(ctx, req)
109217
if err != nil {
110218
return nil, err

0 commit comments

Comments
 (0)