| 1 | package tool |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | ) |
| 7 | |
| 8 | // ShellExecution is local host metadata for one shell invocation. It is never |
| 9 | // part of the provider-visible tool schema or request bytes; ModelMessages and |
| 10 | // provider serializers must strip it before a model request leaves the host. |
| 11 | // |
| 12 | // Kind is always "shell" for shell invocations so UIs can distinguish this |
| 13 | // optional payload from other future execution kinds without guessing. |
| 14 | type ShellExecution struct { |
| 15 | Kind string `json:"kind"` |
| 16 | Shell string `json:"shell,omitempty"` // bash | git-bash | powershell | pwsh |
| 17 | ShellVersion string `json:"shellVersion,omitempty"` // 5.1 | 7+ (PowerShell only) |
| 18 | Platform string `json:"platform,omitempty"` // windows | darwin | linux |
| 19 | // SupportsAndAnd is explicit even when false so UIs can show PowerShell 5.1 |
| 20 | // chaining limits without treating omission as "unknown". |
| 21 | SupportsAndAnd bool `json:"supportsAndAnd"` |
| 22 | State string `json:"state,omitempty"` // running | completed | failed | timed_out | cancelled | background_started | not_run |
| 23 | FailurePhase string `json:"failurePhase,omitempty"` // preflight | authorization | dependency | launch | execution | timeout | cancellation |
| 24 | // ExitCode is set only when a child process started and produced an exit |
| 25 | // status. Zero is a valid successful code (*int keeps 0 distinct from unset). |
| 26 | ExitCode *int `json:"exitCode,omitempty"` |
| 27 | // OutputTail is the bounded tail of combined stdout+stderr, set only for a |
| 28 | // run that did not succeed. Both streams share one pipe so model-visible |
| 29 | // interleaving stays in child-write order, which rules out a stderr-only |
| 30 | // tail. At most 16 KiB; never a shell executable absolute path. |
| 31 | OutputTail string `json:"outputTail,omitempty"` |
| 32 | MutationRisk string `json:"mutationRisk,omitempty"` // none | not_started | may_have_completed | may_be_partial | unknown |
| 33 | Verification string `json:"verification,omitempty"` // not_verification | not_run | passed | failed |
| 34 | DurationMs int64 `json:"durationMs,omitempty"` |
| 35 | } |
| 36 | |
| 37 | // Shell execution state values. |
| 38 | const ( |
| 39 | ShellStateRunning = "running" |
| 40 | ShellStateCompleted = "completed" |
| 41 | ShellStateFailed = "failed" |
| 42 | ShellStateTimedOut = "timed_out" |
| 43 | ShellStateCancelled = "cancelled" |
| 44 | ShellStateBackgroundStarted = "background_started" |
| 45 | ShellStateNotRun = "not_run" |
| 46 | ) |
| 47 | |
| 48 | // Shell failure phase values. |
| 49 | const ( |
| 50 | ShellPhasePreflight = "preflight" |
| 51 | ShellPhaseAuthorization = "authorization" |
| 52 | ShellPhaseDependency = "dependency" |
| 53 | ShellPhaseLaunch = "launch" |
| 54 | ShellPhaseExecution = "execution" |
| 55 | ShellPhaseTimeout = "timeout" |
| 56 | ShellPhaseCancellation = "cancellation" |
| 57 | ) |
| 58 | |
| 59 | // Shell mutation risk values. |
| 60 | const ( |
| 61 | ShellMutationNone = "none" |
| 62 | ShellMutationNotStarted = "not_started" |
| 63 | ShellMutationMayHaveCompleted = "may_have_completed" |
| 64 | ShellMutationMayBePartial = "may_be_partial" |
| 65 | ShellMutationUnknown = "unknown" |
| 66 | ) |
| 67 | |
| 68 | // Shell verification values. |
| 69 | const ( |
| 70 | ShellVerificationNotVerification = "not_verification" |
| 71 | ShellVerificationNotRun = "not_run" |
| 72 | ShellVerificationPassed = "passed" |
| 73 | ShellVerificationFailed = "failed" |
| 74 | ) |
| 75 | |
| 76 | // Shell name values for ShellExecution.Shell. |
| 77 | const ( |
| 78 | ShellNameBash = "bash" |
| 79 | ShellNameGitBash = "git-bash" |
| 80 | ShellNamePowerShell = "powershell" |
| 81 | ShellNamePwsh = "pwsh" |
| 82 | ) |
| 83 | |
| 84 | // PowerShell version labels. |
| 85 | const ( |
| 86 | ShellVersionPS51 = "5.1" |
| 87 | ShellVersionPS7 = "7+" |
| 88 | ) |
| 89 | |
| 90 | // OutputTailMaxBytes bounds the output tail retained on ShellExecution. |
| 91 | const OutputTailMaxBytes = 16 << 10 |
| 92 | |
| 93 | // DetailedResult is the structured outcome of a DetailedExecutor call. |
| 94 | // Output remains the model-visible text; Execution is host/UI metadata only. |
| 95 | type DetailedResult struct { |
| 96 | Output string |
| 97 | Images []string |
| 98 | Execution *ShellExecution |
| 99 | } |
| 100 | |
| 101 | // DetailedExecutor is an optional Tool capability that returns structured |
| 102 | // execution metadata alongside the model-visible result text. Tools that do |
| 103 | // not implement it continue to use ImageTool/Tool.Execute. |
| 104 | type DetailedExecutor interface { |
| 105 | // ExecutionDescriptor returns a descriptor for the would-be execution |
| 106 | // before the process starts (shell identity, platform, chaining support). |
| 107 | // It must not launch a process. Args may be empty or invalid — return a |
| 108 | // best-effort descriptor from the bound shell configuration. |
| 109 | ExecutionDescriptor(args json.RawMessage) *ShellExecution |
| 110 | // ExecuteDetailed runs the tool and returns structured metadata. On |
| 111 | // policy/preflight blocks, Execution must still be populated (state=not_run). |
| 112 | ExecuteDetailed(ctx context.Context, args json.RawMessage) (DetailedResult, error) |
| 113 | } |
| 114 | |
| 115 | // CloneShellExecution returns a deep copy suitable for attaching to events or |
| 116 | // session messages without sharing mutable pointers (e.g. ExitCode). |
| 117 | func CloneShellExecution(in *ShellExecution) *ShellExecution { |
| 118 | if in == nil { |
| 119 | return nil |
| 120 | } |
| 121 | out := *in |
| 122 | if in.ExitCode != nil { |
| 123 | code := *in.ExitCode |
| 124 | out.ExitCode = &code |
| 125 | } |
| 126 | return &out |
| 127 | } |
| 128 | |
| 129 | // IntPtr returns a pointer to v for ShellExecution.ExitCode. |
| 130 | func IntPtr(v int) *int { return &v } |
| 131 |