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
38 changes: 21 additions & 17 deletions internal/tool/credential_gate.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,32 +42,36 @@ func (RequestCredentialTool) Description() string {
"available inside the sandbox. Use this when a command fails due to missing credentials."
}

func (RequestCredentialTool) Parameters() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"credential": map[string]interface{}{
"type": "string",
"description": "Credential ID to request. One of: gitconfig, kube, aws, gh, docker, gnupg, terraform.",
},
"reason": map[string]interface{}{
"type": "string",
"description": "Why this credential is needed (e.g. 'run kubectl get pods').",
},
// Schema returns the typed input schema. Parameters() delegates to it so the
// two cannot diverge.
func (RequestCredentialTool) Schema() ToolSchema {
return ToolSchema{
Type: "object",
Properties: map[string]SchemaProperty{
"credential": {Type: "string", Description: "Credential ID to request. One of: gitconfig, kube, aws, gh, docker, gnupg, terraform."},
"reason": {Type: "string", Description: "Why this credential is needed (e.g. 'run kubectl get pods')."},
},
"required": []string{"credential", "reason"},
Required: []string{"credential", "reason"},
}
}

type credentialInput struct {
func (RequestCredentialTool) Parameters() map[string]interface{} {
return requestCredentialSchema.ToJSONSchema()
}

// requestCredentialSchema is the single source of truth for RequestCredential's input schema.
var requestCredentialSchema = RequestCredentialTool{}.Schema()

// RequestCredentialInput is the typed input for RequestCredentialTool.
type RequestCredentialInput struct {
Credential string `json:"credential"`
Reason string `json:"reason"`
}

func (t RequestCredentialTool) Execute(ctx context.Context, input json.RawMessage) (string, error) {
var p credentialInput
if err := json.Unmarshal(input, &p); err != nil {
return "", fmt.Errorf("invalid RequestCredential input: %w", err)
p, err := DecodeInput[RequestCredentialInput]("RequestCredential", input)
if err != nil {
return "", err
}
if p.Credential == "" {
return "", fmt.Errorf("credential is required")
Expand Down
58 changes: 27 additions & 31 deletions internal/tool/debugger.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,40 @@ func (DebuggerTool) Description() string {
Prefer this over adding print statements when you need to understand runtime state.`
}

func (DebuggerTool) Parameters() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"action": map[string]interface{}{
"type": "string",
"description": "Action: breakpoint, run, inspect, step, continue, stack",
"enum": []string{"breakpoint", "run", "inspect", "step", "continue", "stack"},
},
"file": map[string]interface{}{
"type": "string",
"description": "File path (for breakpoint action)",
},
"line": map[string]interface{}{
"type": "integer",
"description": "Line number (for breakpoint action)",
},
"expression": map[string]interface{}{
"type": "string",
"description": "Expression to evaluate (for inspect action)",
},
// Schema returns the typed input schema. Parameters() delegates to it so the
// two cannot diverge.
func (DebuggerTool) Schema() ToolSchema {
return ToolSchema{
Type: "object",
Properties: map[string]SchemaProperty{
"action": {Type: "string", Enum: []interface{}{"breakpoint", "run", "inspect", "step", "continue", "stack"}, Description: "Action: breakpoint, run, inspect, step, continue, stack"},
"file": {Type: "string", Description: "File path (for breakpoint action)"},
"line": {Type: "integer", Description: "Line number (for breakpoint action)"},
"expression": {Type: "string", Description: "Expression to evaluate (for inspect action)"},
},
"required": []string{"action"},
Required: []string{"action"},
}
}

// debugParams holds the parsed input parameters.
type debugParams struct {
func (DebuggerTool) Parameters() map[string]interface{} {
return debuggerSchema.ToJSONSchema()
}

// debuggerSchema is the single source of truth for Debugger's input schema.
var debuggerSchema = DebuggerTool{}.Schema()

// DebuggerInput is the typed input for DebuggerTool.
type DebuggerInput struct {
Action string `json:"action"`
File string `json:"file"`
Line int `json:"line"`
Expression string `json:"expression"`
}

func (DebuggerTool) Execute(ctx context.Context, input json.RawMessage) (string, error) {
var p debugParams
if err := json.Unmarshal(input, &p); err != nil {
return "", fmt.Errorf("invalid input: %w", err)
p, err := DecodeInput[DebuggerInput]("Debug", input)
if err != nil {
return "", err
}

if err := validateDebugParams(p); err != nil {
Expand All @@ -88,7 +84,7 @@ func (DebuggerTool) Execute(ctx context.Context, input json.RawMessage) (string,
}

// validateDebugParams ensures required fields are present for each action.
func validateDebugParams(p debugParams) error {
func validateDebugParams(p DebuggerInput) error {
switch p.Action {
case "":
return fmt.Errorf("action is required")
Expand Down Expand Up @@ -126,7 +122,7 @@ func detectDebugLanguage(file string) string {
}
}

func debugBreakpoint(ctx context.Context, p debugParams) (string, error) {
func debugBreakpoint(ctx context.Context, p DebuggerInput) (string, error) {
lang := detectDebugLanguage(p.File)
switch lang {
case "go":
Expand All @@ -148,7 +144,7 @@ func debugBreakpoint(ctx context.Context, p debugParams) (string, error) {
}
}

func debugRun(ctx context.Context, p debugParams) (string, error) {
func debugRun(ctx context.Context, p DebuggerInput) (string, error) {
file := p.File
if file == "" {
file = "."
Expand Down Expand Up @@ -184,7 +180,7 @@ func debugRun(ctx context.Context, p debugParams) (string, error) {
}
}

func debugInspect(ctx context.Context, p debugParams) (string, error) {
func debugInspect(ctx context.Context, p DebuggerInput) (string, error) {
// For Go, use dlv eval.
cmd := exec.CommandContext(ctx, "dlv", "eval", p.Expression) // #nosec G204 -- debugger/interpreter invocation with file path or expression from tool params
out, err := cmd.CombinedOutput()
Expand Down
26 changes: 13 additions & 13 deletions internal/tool/debugger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,73 +42,73 @@ func TestDebuggerTool_Metadata(t *testing.T) {
func TestDebuggerTool_ValidateParams(t *testing.T) {
tests := []struct {
name string
params debugParams
params DebuggerInput
wantErr bool
errMsg string
}{
{
name: "empty action",
params: debugParams{},
params: DebuggerInput{},
wantErr: true,
errMsg: "action is required",
},
{
name: "breakpoint without file",
params: debugParams{Action: "breakpoint", Line: 10},
params: DebuggerInput{Action: "breakpoint", Line: 10},
wantErr: true,
errMsg: "file is required",
},
{
name: "breakpoint without line",
params: debugParams{Action: "breakpoint", File: "main.go"},
params: DebuggerInput{Action: "breakpoint", File: "main.go"},
wantErr: true,
errMsg: "line must be a positive integer",
},
{
name: "breakpoint with negative line",
params: debugParams{Action: "breakpoint", File: "main.go", Line: -1},
params: DebuggerInput{Action: "breakpoint", File: "main.go", Line: -1},
wantErr: true,
errMsg: "line must be a positive integer",
},
{
name: "inspect without expression",
params: debugParams{Action: "inspect"},
params: DebuggerInput{Action: "inspect"},
wantErr: true,
errMsg: "expression is required",
},
{
name: "valid breakpoint",
params: debugParams{Action: "breakpoint", File: "main.go", Line: 10},
params: DebuggerInput{Action: "breakpoint", File: "main.go", Line: 10},
wantErr: false,
},
{
name: "valid inspect",
params: debugParams{Action: "inspect", Expression: "x + 1"},
params: DebuggerInput{Action: "inspect", Expression: "x + 1"},
wantErr: false,
},
{
name: "valid run",
params: debugParams{Action: "run"},
params: DebuggerInput{Action: "run"},
wantErr: false,
},
{
name: "valid step",
params: debugParams{Action: "step"},
params: DebuggerInput{Action: "step"},
wantErr: false,
},
{
name: "valid continue",
params: debugParams{Action: "continue"},
params: DebuggerInput{Action: "continue"},
wantErr: false,
},
{
name: "valid stack",
params: debugParams{Action: "stack"},
params: DebuggerInput{Action: "stack"},
wantErr: false,
},
{
name: "unknown action",
params: debugParams{Action: "dance"},
params: DebuggerInput{Action: "dance"},
wantErr: true,
errMsg: "unknown action",
},
Expand Down
48 changes: 28 additions & 20 deletions internal/tool/dependency_audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,38 @@ func (DependencyAuditTool) Description() string {
return "Audit dependency integrity and report outdated packages without installing or changing anything. Supports Go, npm, Python, and Cargo projects with structured results."
}

func (DependencyAuditTool) Parameters() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"action": map[string]interface{}{
"type": "string",
"enum": []string{"check", "outdated", "all"},
"description": "check validates dependency integrity; outdated reports available updates; all runs both.",
},
"path": map[string]interface{}{"type": "string", "description": "Project directory (default: session working directory)."},
"timeout_seconds": map[string]interface{}{"type": "integer", "minimum": 1, "maximum": 300, "description": "Per-command timeout (default 60 seconds)."},
// Schema returns the typed input schema. Parameters() delegates to it so the
// two cannot diverge.
func (DependencyAuditTool) Schema() ToolSchema {
return ToolSchema{
Type: "object",
Properties: map[string]SchemaProperty{
"action": {Type: "string", Enum: []interface{}{"check", "outdated", "all"}, Description: "check validates dependency integrity; outdated reports available updates; all runs both."},
"path": {Type: "string", Description: "Project directory (default: session working directory)."},
"timeout_seconds": {Type: "integer", Minimum: 1, Maximum: 300, Description: "Per-command timeout (default 60 seconds)."},
},
"required": []string{"action"},
Required: []string{"action"},
}
}

func (DependencyAuditTool) Parameters() map[string]interface{} {
return dependencyAuditSchema.ToJSONSchema()
}

// dependencyAuditSchema is the single source of truth for DependencyAudit's input schema.
var dependencyAuditSchema = DependencyAuditTool{}.Schema()

// DependencyAuditInput is the typed input for DependencyAuditTool.
type DependencyAuditInput struct {
Action string `json:"action"`
Path string `json:"path"`
TimeoutSeconds int `json:"timeout_seconds"`
}

func (DependencyAuditTool) Execute(ctx context.Context, input json.RawMessage) (string, error) {
var params struct {
Action string `json:"action"`
Path string `json:"path"`
TimeoutSeconds int `json:"timeout_seconds"`
}
if err := json.Unmarshal(input, &params); err != nil {
return "", fmt.Errorf("invalid input: %w", err)
params, err := DecodeInput[DependencyAuditInput]("DependencyAudit", input)
if err != nil {
return "", err
}
params.Action = strings.ToLower(strings.TrimSpace(params.Action))
if params.Action != "check" && params.Action != "outdated" && params.Action != "all" {
Expand All @@ -67,7 +75,7 @@ func (DependencyAuditTool) Execute(ctx context.Context, input json.RawMessage) (
root, _ = os.Getwd()
}
}
root, err := filepath.Abs(root)
root, err = filepath.Abs(root)
if err != nil {
return "", fmt.Errorf("resolve project path: %w", err)
}
Expand Down
43 changes: 24 additions & 19 deletions internal/tool/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,35 @@ func (GitTool) Description() string {
return "Run git commands in the project worktree. Supports: status, diff, log, show, branch, checkout, add, commit, pull, push, fetch, stash, rebase, merge, reset, tag."
}

func (GitTool) Parameters() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"subcommand": map[string]interface{}{
"type": "string",
"description": "Git subcommand to run (e.g. status, diff, add, commit)",
},
"args": map[string]interface{}{
"type": "array",
"items": map[string]interface{}{"type": "string"},
"description": "Arguments for the subcommand (e.g. [\"-m\", \"fix bug\"])",
},
// Schema returns the typed input schema. Parameters() delegates to it so the
// two cannot diverge.
func (GitTool) Schema() ToolSchema {
return ToolSchema{
Type: "object",
Properties: map[string]SchemaProperty{
"subcommand": {Type: "string", Description: "Git subcommand to run (e.g. status, diff, add, commit)"},
"args": {Type: "array", Items: &SchemaProperty{Type: "string"}, Description: "Arguments for the subcommand (e.g. [\"-m\", \"fix bug\"])"},
},
"required": []string{"subcommand"},
Required: []string{"subcommand"},
}
}

func (GitTool) Parameters() map[string]interface{} {
return gitSchema.ToJSONSchema()
}

// gitSchema is the single source of truth for Git's input schema.
var gitSchema = GitTool{}.Schema()

// GitInput is the typed input for GitTool.
type GitInput struct {
Subcommand string `json:"subcommand"`
Args []string `json:"args,omitempty"`
}

func (t GitTool) Execute(ctx context.Context, input json.RawMessage) (string, error) {
var in struct {
Subcommand string `json:"subcommand"`
Args []string `json:"args,omitempty"`
}
if err := json.Unmarshal(input, &in); err != nil {
in, err := DecodeInput[GitInput]("Git", input)
if err != nil {
return "", err
}

Expand Down
Loading
Loading