Skip to content

Commit c86195d

Browse files
committed
tool output rename and tooling upgrade
1 parent bbd107a commit c86195d

15 files changed

Lines changed: 168 additions & 160 deletions

File tree

.golangci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# All linters are checked and included either in enable or disable as of v2.8.0 of golangci-lint.
1+
# All linters are checked and included either in enable or disable as of v2.9.0 of golangci-lint.
22
# gofumpt and golines are used as formatters.
33
version: "2"
44
run:

.tool-versions

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
golang 1.25.5
2-
golangci-lint 2.8.0
3-
task 3.45.5
1+
golang 1.25.7
2+
golangci-lint 2.9.0
3+
task 3.48.0

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ The registry provides:
108108

109109
## Tool outputs
110110

111-
- `Registry.Call` returns `[]spec.ToolStoreOutputUnion`.
111+
- `Registry.Call` returns `[]spec.ToolOutputUnion`.
112112

113113
- The call wrapper can modify the union to support two common patterns:
114114
- Structured JSON-as-text (most tools)

exectool/config.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package exectool
2+
3+
import (
4+
"maps"
5+
"time"
6+
7+
"github.com/flexigpt/llmtools-go/internal/fspolicy"
8+
)
9+
10+
// ExecutionPolicy provides policy / hardening knobs (host-configured).
11+
// All limits are clamped to executil hard maximums (downstream enforcement).
12+
type ExecutionPolicy struct {
13+
// If true, skip heuristic checks (fork-bomb/backgrounding).
14+
// NOTE: hard-blocked commands are ALWAYS blocked.
15+
AllowDangerous bool
16+
17+
Timeout time.Duration
18+
MaxOutputBytes int64
19+
MaxCommands int
20+
MaxCommandLength int
21+
}
22+
23+
type execToolConfig struct {
24+
allowedRoots []string
25+
workBaseDir string
26+
blockSymlinks bool
27+
blockedCommands map[string]struct{}
28+
29+
executionPolicy ExecutionPolicy
30+
runScriptPolicy RunScriptPolicy
31+
}
32+
33+
type execToolPolicy struct {
34+
fsPolicy fspolicy.FSPolicy
35+
blockedCommands map[string]struct{}
36+
37+
executionPolicy ExecutionPolicy
38+
runScriptPolicy RunScriptPolicy
39+
}
40+
41+
// Clone returns an independent copy of the policy snapshot.
42+
func (p *execToolPolicy) Clone() *execToolPolicy {
43+
if p == nil {
44+
return nil
45+
}
46+
47+
cp := new(execToolPolicy)
48+
*cp = *p // copy all value fields (and slice/map headers)
49+
50+
if p.blockedCommands != nil {
51+
cp.blockedCommands = make(map[string]struct{}, len(p.blockedCommands))
52+
maps.Copy(cp.blockedCommands, p.blockedCommands)
53+
} else {
54+
cp.blockedCommands = nil
55+
}
56+
57+
if c := p.executionPolicy.Clone(); c != nil {
58+
cp.executionPolicy = *c
59+
}
60+
61+
if c := p.runScriptPolicy.Clone(); c != nil {
62+
cp.runScriptPolicy = *c
63+
}
64+
65+
return cp
66+
}
67+
68+
// Clone returns an independent copy of the ExecutionPolicy.
69+
// (All fields are value types, so a plain copy is sufficient.)
70+
func (p *ExecutionPolicy) Clone() *ExecutionPolicy {
71+
if p == nil {
72+
return nil
73+
}
74+
cp := new(ExecutionPolicy)
75+
*cp = *p
76+
return cp
77+
}

exectool/exectool.go

Lines changed: 10 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -15,85 +15,6 @@ import (
1515
"github.com/flexigpt/llmtools-go/spec"
1616
)
1717

18-
// ExecutionPolicy provides policy / hardening knobs (host-configured).
19-
// All limits are clamped to executil hard maximums (downstream enforcement).
20-
type ExecutionPolicy struct {
21-
// If true, skip heuristic checks (fork-bomb/backgrounding).
22-
// NOTE: hard-blocked commands are ALWAYS blocked.
23-
AllowDangerous bool
24-
25-
Timeout time.Duration
26-
MaxOutputBytes int64
27-
MaxCommands int
28-
MaxCommandLength int
29-
}
30-
31-
// Clone returns an independent copy of the ExecutionPolicy.
32-
// (All fields are value types, so a plain copy is sufficient.)
33-
func (p *ExecutionPolicy) Clone() *ExecutionPolicy {
34-
if p == nil {
35-
return nil
36-
}
37-
cp := new(ExecutionPolicy)
38-
*cp = *p
39-
return cp
40-
}
41-
42-
func DefaultExecutionPolicy() ExecutionPolicy {
43-
return ExecutionPolicy{
44-
AllowDangerous: false,
45-
Timeout: executil.DefaultTimeout,
46-
MaxOutputBytes: executil.DefaultMaxOutputBytes,
47-
MaxCommands: executil.DefaultMaxCommands,
48-
MaxCommandLength: executil.DefaultMaxCommandLength,
49-
}
50-
}
51-
52-
type execToolConfig struct {
53-
allowedRoots []string
54-
workBaseDir string
55-
blockSymlinks bool
56-
blockedCommands map[string]struct{}
57-
58-
executionPolicy ExecutionPolicy
59-
runScriptPolicy RunScriptPolicy
60-
}
61-
62-
type execToolPolicy struct {
63-
fsPolicy fspolicy.FSPolicy
64-
blockedCommands map[string]struct{}
65-
66-
executionPolicy ExecutionPolicy
67-
runScriptPolicy RunScriptPolicy
68-
}
69-
70-
// Clone returns an independent copy of the policy snapshot.
71-
func (p *execToolPolicy) Clone() *execToolPolicy {
72-
if p == nil {
73-
return nil
74-
}
75-
76-
cp := new(execToolPolicy)
77-
*cp = *p // copy all value fields (and slice/map headers)
78-
79-
if p.blockedCommands != nil {
80-
cp.blockedCommands = make(map[string]struct{}, len(p.blockedCommands))
81-
maps.Copy(cp.blockedCommands, p.blockedCommands)
82-
} else {
83-
cp.blockedCommands = nil
84-
}
85-
86-
if c := p.executionPolicy.Clone(); c != nil {
87-
cp.executionPolicy = *c
88-
}
89-
90-
if c := p.runScriptPolicy.Clone(); c != nil {
91-
cp.runScriptPolicy = *c
92-
}
93-
94-
return cp
95-
}
96-
9718
// ExecTool is an instance-owned execution tool runner.
9819
// It centralizes:
9920
// - path sandboxing (workBaseDir, allowedRoots, blockSymlinks)
@@ -273,3 +194,13 @@ func (et *ExecTool) snapshotPolicy() *execToolPolicy {
273194
}
274195
return p.Clone()
275196
}
197+
198+
func DefaultExecutionPolicy() ExecutionPolicy {
199+
return ExecutionPolicy{
200+
AllowDangerous: false,
201+
Timeout: executil.DefaultTimeout,
202+
MaxOutputBytes: executil.DefaultMaxOutputBytes,
203+
MaxCommands: executil.DefaultMaxCommands,
204+
MaxCommandLength: executil.DefaultMaxCommandLength,
205+
}
206+
}

fstool/fstool.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ func (ft *FSTool) MIMEForPath(ctx context.Context, args MIMEForPathArgs) (*MIMEF
121121
func (ft *FSTool) ReadFile(
122122
ctx context.Context,
123123
args ReadFileArgs,
124-
) ([]spec.ToolStoreOutputUnion, error) {
125-
return toolutil.WithRecoveryResp(func() ([]spec.ToolStoreOutputUnion, error) {
124+
) ([]spec.ToolOutputUnion, error) {
125+
return toolutil.WithRecoveryResp(func() ([]spec.ToolOutputUnion, error) {
126126
p := ft.snapshotPolicy()
127127
return readFile(ctx, args, p)
128128
})

fstool/helpers_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,6 @@ func evalTestSymlinksBestEffort(p string) string {
128128
return p
129129
}
130130

131-
func stringSliceAsSet(in []string) map[string]int {
132-
m := make(map[string]int, len(in))
133-
for _, s := range in {
134-
m[s]++
135-
}
136-
return m
137-
}
138-
139131
func equalStringMultisets(a, b []string) bool {
140132
ma := stringSliceAsSet(a)
141133
mb := stringSliceAsSet(b)
@@ -150,6 +142,14 @@ func equalStringMultisets(a, b []string) bool {
150142
return true
151143
}
152144

145+
func stringSliceAsSet(in []string) map[string]int {
146+
m := make(map[string]int, len(in))
147+
for _, s := range in {
148+
m[s]++
149+
}
150+
return m
151+
}
152+
153153
func wantErrContains(substr string) func(error) bool {
154154
return func(err error) bool {
155155
return err != nil && strings.Contains(err.Error(), substr)

fstool/readfile.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func readFile(
6363
ctx context.Context,
6464
args ReadFileArgs,
6565
p fspolicy.FSPolicy,
66-
) ([]spec.ToolStoreOutputUnion, error) {
66+
) ([]spec.ToolOutputUnion, error) {
6767
if err := ctx.Err(); err != nil {
6868
return nil, err
6969
}
@@ -114,10 +114,10 @@ func readFile(
114114
return nil, err
115115
}
116116

117-
return []spec.ToolStoreOutputUnion{
117+
return []spec.ToolOutputUnion{
118118
{
119-
Kind: spec.ToolStoreOutputKindText,
120-
TextItem: &spec.ToolStoreOutputText{
119+
Kind: spec.ToolOutputKindText,
120+
TextItem: &spec.ToolOutputText{
121121
Text: text,
122122
},
123123
},
@@ -144,10 +144,10 @@ func readFile(
144144
)
145145
}
146146

147-
return []spec.ToolStoreOutputUnion{
147+
return []spec.ToolOutputUnion{
148148
{
149-
Kind: spec.ToolStoreOutputKindText,
150-
TextItem: &spec.ToolStoreOutputText{
149+
Kind: spec.ToolOutputKindText,
150+
TextItem: &spec.ToolOutputText{
151151
Text: data,
152152
},
153153
},
@@ -181,10 +181,10 @@ func readFile(
181181
}
182182

183183
if strings.HasPrefix(mt, "image/") {
184-
return []spec.ToolStoreOutputUnion{
184+
return []spec.ToolOutputUnion{
185185
{
186-
Kind: spec.ToolStoreOutputKindImage,
187-
ImageItem: &spec.ToolStoreOutputImage{
186+
Kind: spec.ToolOutputKindImage,
187+
ImageItem: &spec.ToolOutputImage{
188188
Detail: spec.ImageDetailAuto,
189189
ImageName: baseName,
190190
ImageMIME: mt,
@@ -194,10 +194,10 @@ func readFile(
194194
}, nil
195195
}
196196

197-
return []spec.ToolStoreOutputUnion{
197+
return []spec.ToolOutputUnion{
198198
{
199-
Kind: spec.ToolStoreOutputKindFile,
200-
FileItem: &spec.ToolStoreOutputFile{
199+
Kind: spec.ToolOutputKindFile,
200+
FileItem: &spec.ToolOutputFile{
201201
FileName: baseName,
202202
FileMIME: mt,
203203
FileData: data, // base64-encoded

internal/integration/fs_image_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestE2E_FS_MIME_Delete_And_ImageFlows(t *testing.T) {
4141
}
4242

4343
readBin := callRaw(t, h.r, "readfile", fstool.ReadFileArgs{Path: binRel, Encoding: "binary"})
44-
fileItem := requireKind(t, readBin, spec.ToolStoreOutputKindFile)
44+
fileItem := requireKind(t, readBin, spec.ToolOutputKindFile)
4545
if fileItem.FileItem == nil || fileItem.FileItem.FileData == "" {
4646
t.Fatalf("expected file output with base64 data, got: %+v", fileItem)
4747
}
@@ -118,7 +118,7 @@ func TestE2E_FS_MIME_Delete_And_ImageFlows(t *testing.T) {
118118

119119
// "readfile(binary)" should emit an image output for image/*.
120120
readImg := callRaw(t, h.r, "readfile", fstool.ReadFileArgs{Path: "pixel.png", Encoding: "binary"})
121-
imageItem := requireKind(t, readImg, spec.ToolStoreOutputKindImage)
121+
imageItem := requireKind(t, readImg, spec.ToolOutputKindImage)
122122
if imageItem.ImageItem == nil || imageItem.ImageItem.ImageData == "" {
123123
t.Fatalf("expected image output with base64 data, got: %+v", imageItem)
124124
}

internal/integration/helpers_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func callJSON[T any](t *testing.T, r *llmtools.Registry, slug string, args any)
123123
t.Helper()
124124

125125
rawOut := callRaw(t, r, slug, args)
126-
if len(rawOut) != 1 || rawOut[0].Kind != spec.ToolStoreOutputKindText || rawOut[0].TextItem == nil {
126+
if len(rawOut) != 1 || rawOut[0].Kind != spec.ToolOutputKindText || rawOut[0].TextItem == nil {
127127
t.Fatalf("expected single text output for %s, got: %+v", slug, rawOut)
128128
}
129129
var decoded T
@@ -133,7 +133,7 @@ func callJSON[T any](t *testing.T, r *llmtools.Registry, slug string, args any)
133133
return decoded
134134
}
135135

136-
func callRaw(t *testing.T, r *llmtools.Registry, slug string, args any) []spec.ToolStoreOutputUnion {
136+
func callRaw(t *testing.T, r *llmtools.Registry, slug string, args any) []spec.ToolOutputUnion {
137137
t.Helper()
138138

139139
in, err := json.Marshal(args)
@@ -159,27 +159,27 @@ func funcIDBySlug(t *testing.T, r *llmtools.Registry, slug string) spec.FuncID {
159159
return ""
160160
}
161161

162-
func requireSingleTextOutput(t *testing.T, out []spec.ToolStoreOutputUnion) string {
162+
func requireSingleTextOutput(t *testing.T, out []spec.ToolOutputUnion) string {
163163
t.Helper()
164-
if len(out) != 1 || out[0].Kind != spec.ToolStoreOutputKindText || out[0].TextItem == nil {
164+
if len(out) != 1 || out[0].Kind != spec.ToolOutputKindText || out[0].TextItem == nil {
165165
t.Fatalf("expected single text output, got: %+v", out)
166166
}
167167
return out[0].TextItem.Text
168168
}
169169

170170
func requireKind(
171171
t *testing.T,
172-
out []spec.ToolStoreOutputUnion,
173-
kind spec.ToolStoreOutputKind,
174-
) spec.ToolStoreOutputUnion {
172+
out []spec.ToolOutputUnion,
173+
kind spec.ToolOutputKind,
174+
) spec.ToolOutputUnion {
175175
t.Helper()
176176
for _, item := range out {
177177
if item.Kind == kind {
178178
return item
179179
}
180180
}
181181
t.Fatalf("expected output kind %q, got: %+v", kind, out)
182-
return spec.ToolStoreOutputUnion{}
182+
return spec.ToolOutputUnion{}
183183
}
184184

185185
func debugJSON(t *testing.T, v any) string {

0 commit comments

Comments
 (0)