Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ codeforce:
cosmos:
url: https://cosmos.example.com
api_key: YOUR_JWT_BEARER_TOKEN

agent-compose:
url: https://agent-compose.example.com
api_token: YOUR_API_TOKEN
default_project: YOUR_PROJECT
```
You can also put the same keys into environment variables or a local `.env` file. Variable names follow `<PRODUCT>_<FIELD>`:

Expand Down Expand Up @@ -171,6 +176,10 @@ safeline-3.url -> SAFELINE_3_URL
safeline-3.api_token -> SAFELINE_3_API_TOKEN
safeline.url -> SAFELINE_URL
safeline.api_key -> SAFELINE_API_KEY
agent-compose.url -> AGENT_COMPOSE_URL
agent-compose.api_token -> AGENT_COMPOSE_API_TOKEN
agent-compose.api_key -> AGENT_COMPOSE_API_KEY (alias of api_token)
agent-compose.default_project -> AGENT_COMPOSE_DEFAULT_PROJECT
```

### T-Answer Quick Start
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ cosmos:
monkeyscan:
url: https://monkeyscan-ai.com
api_key: YOUR_API_KEY

agent-compose:
url: https://agent-compose.example.com
api_token: YOUR_API_TOKEN
default_project: YOUR_PROJECT
```
也可以把同样的配置放到环境变量或本地 `.env` 文件中。变量命名规则为 `<PRODUCT>_<FIELD>`:

Expand Down Expand Up @@ -200,6 +205,10 @@ safeline.url -> SAFELINE_URL
safeline.api_key -> SAFELINE_API_KEY
monkeyscan.url -> MONKEYSCAN_URL
monkeyscan.api_key -> MONKEYSCAN_API_KEY
agent-compose.url -> AGENT_COMPOSE_URL
agent-compose.api_token -> AGENT_COMPOSE_API_TOKEN
agent-compose.api_key -> AGENT_COMPOSE_API_KEY (alias of api_token)
agent-compose.default_project -> AGENT_COMPOSE_DEFAULT_PROJECT
```

### 全悉快速开始
Expand Down
6 changes: 6 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ monkeyscan:
url: ""
api_key: ""

agent-compose:
url: ""
api_token: ""
# api_key is accepted as an alias for api_token
default_project: ""

# TODO: Replace with real insight API key and URL
insight:
url: "http://192.168.1.100"
Expand Down
4 changes: 3 additions & 1 deletion products/agentcompose/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func newAuthCommand(state *commandState) *cobra.Command {
}
stored.URL = state.options.URL
stored.APIToken = token
stored.APIKey = ""
if stored.Timeout == "" {
stored.Timeout = state.options.timeoutText
}
Expand Down Expand Up @@ -66,6 +67,7 @@ func newAuthCommand(state *commandState) *cobra.Command {
return err
}
stored.APIToken = ""
stored.APIKey = ""
if err := config.SetProduct(state.options.ConfigPath, productName, stored); err != nil {
return err
}
Expand All @@ -75,7 +77,7 @@ func newAuthCommand(state *commandState) *cobra.Command {
}
fmt.Fprintln(cmd.OutOrStdout(), "Local Agent Compose Token cleared")
if warning {
fmt.Fprintln(cmd.ErrOrStderr(), "Warning: AGENT_COMPOSE_API_TOKEN is still active")
fmt.Fprintln(cmd.ErrOrStderr(), "Warning: AGENT_COMPOSE_API_TOKEN or AGENT_COMPOSE_API_KEY is still active")
}
return nil
}}
Expand Down
2 changes: 1 addition & 1 deletion products/agentcompose/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func NewCommand() *cobra.Command {
},
}
cmd.SetContext(context.WithValue(context.Background(), stateKey{}, state))
cmd.PersistentFlags().StringVar(&state.options.URL, "url", "", "Token/RBAC API base URL")
cmd.PersistentFlags().StringVar(&state.options.URL, "url", "", "Token/RBAC API base URL, including an optional path prefix")
cmd.PersistentFlags().StringVar(&state.options.Project, "project", "", "Default project name or ID")
cmd.PersistentFlags().StringVar(&state.options.timeoutText, "timeout", "30s", "Unary request timeout")
cmd.PersistentFlags().BoolVar(&state.options.Insecure, "insecure", false, "Skip TLS certificate verification")
Expand Down
45 changes: 43 additions & 2 deletions products/agentcompose/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ func TestNormalizeBaseURL(t *testing.T) {
}{
{"https", "https://example.com/", "https://example.com", false},
{"http", "http://127.0.0.1:8081", "http://127.0.0.1:8081", false},
{"path prefix", "https://example.com/agent-compose", "https://example.com/agent-compose", false},
{"path prefix slash", "https://example.com/agent-compose/", "https://example.com/agent-compose", false},
{"missing scheme", "example.com", "", true},
{"path", "https://example.com/rpc", "", true},
{"query", "https://example.com?token=secret", "", true},
{"fragment", "https://example.com/#x", "", true},
{"userinfo", "https://user:secret@example.com", "", true},
Expand Down Expand Up @@ -207,6 +208,46 @@ func TestApplyRuntimeConfigFlagPrecedence(t *testing.T) {
}
}

func TestApplyRuntimeConfigAPIKeyAlias(t *testing.T) {
cmd := NewCommand()
var node yaml.Node
if err := node.Encode(map[string]string{"url": "https://example.com/agent-compose", "api_key": "legacy-key"}); err != nil {
t.Fatal(err)
}
ApplyRuntimeConfig(cmd, config.Raw{productName: node}, "config.yaml", false)
state := stateFromCommand(cmd)
if state.options.URL != "https://example.com/agent-compose" || state.options.Token != "legacy-key" || state.options.TokenSource != "config" {
t.Fatalf("options = %+v", state.options)
}
}

func TestApplyRuntimeConfigPrefersAPITokenOverAPIKey(t *testing.T) {
cmd := NewCommand()
var node yaml.Node
if err := node.Encode(productConfig{URL: "https://example.com", APIToken: "canonical-token", APIKey: "legacy-key"}); err != nil {
t.Fatal(err)
}
ApplyRuntimeConfig(cmd, config.Raw{productName: node}, "config.yaml", false)
state := stateFromCommand(cmd)
if state.options.Token != "canonical-token" || state.options.TokenSource != "config" {
t.Fatalf("options = %+v", state.options)
}
}

func TestApplyRuntimeConfigIgnoresUnrelatedAPIKeyEnv(t *testing.T) {
t.Setenv("AGENT_COMPOSE_API_KEY", "environment-key")
cmd := NewCommand()
var node yaml.Node
if err := node.Encode(productConfig{URL: "https://example.com", APIToken: "config-token"}); err != nil {
t.Fatal(err)
}
ApplyRuntimeConfig(cmd, config.Raw{productName: node}, "config.yaml", false)
state := stateFromCommand(cmd)
if state.options.Token != "config-token" || state.options.TokenSource != "config" {
t.Fatalf("options = %+v", state.options)
}
}

func TestApplyRuntimeConfigEnvironmentPrecedence(t *testing.T) {
t.Setenv("AGENT_COMPOSE_URL", "https://environment.example")
t.Setenv("AGENT_COMPOSE_API_TOKEN", "environment-token")
Expand All @@ -220,7 +261,7 @@ func TestApplyRuntimeConfigEnvironmentPrecedence(t *testing.T) {
}
ApplyRuntimeConfig(cmd, config.Raw{productName: node}, "config.yaml", false)
state := stateFromCommand(cmd)
if state.options.URL != "https://environment.example" || state.options.Token != "environment-token" || state.options.Project != "environment-project" || state.options.timeoutText != "7s" || !state.options.Insecure {
if state.options.URL != "https://environment.example" || state.options.Token != "environment-token" || state.options.Project != "environment-project" || state.options.timeoutText != "7s" || !state.options.Insecure || state.options.TokenSource != "environment" {
t.Fatalf("options = %+v", state.options)
}
}
Expand Down
4 changes: 2 additions & 2 deletions products/agentcompose/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func executeExec(cmd *cobra.Command, state *commandState, args []string, options
request := &agentcomposev2.ExecRequest{Target: &agentcomposev2.ExecRequest_SandboxId{SandboxId: sandbox.GetSandboxId()}, Command: &agentcomposev2.ExecCommand{Command: commandName, Args: commandArgs}, Cwd: options.CWD, Env: env, TimeoutMs: timeoutMS, MaxOutputBytes: options.MaxOutput}
ctx, cancel = streamContext(cmd)
defer cancel()
stream, err := state.clients().exec.ExecStream(ctx, connect.NewRequest(request))
stream, err := state.clients().exec.StreamExec(ctx, connect.NewRequest(request))
if err != nil {
return mapConnectError(err, state.options.URL, state.options.JSON)
}
Expand All @@ -107,7 +107,7 @@ func executeExec(cmd *cobra.Command, state *commandState, args []string, options
final = event.GetResult()
}
if state.options.JSON {
record := map[string]any{"event_type": enumText(event.GetEventType(), "EXEC_STREAM_EVENT_TYPE_"), "exec_id": event.GetExecId(), "sandbox_id": event.GetSandboxId(), "run_id": event.GetRunId(), "chunk": event.GetChunk(), "stream": enumText(event.GetStream(), "STDIO_STREAM_")}
record := map[string]any{"event_type": enumText(event.GetEventType(), "STREAM_EXEC_EVENT_TYPE_"), "exec_id": event.GetExecId(), "sandbox_id": event.GetSandboxId(), "run_id": event.GetRunId(), "chunk": event.GetChunk(), "stream": enumText(event.GetStream(), "STDIO_STREAM_")}
if final != nil {
addExecFields(record, execFromProto(final))
}
Expand Down
Loading
Loading