529 lines
25 KiB
Go
529 lines
25 KiB
Go
package openai
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"iop/packages/go/config"
|
|
)
|
|
|
|
func TestWorkspaceToolBindingContract(t *testing.T) {
|
|
structured := workspaceAlternative("structured", "write_file", false, true)
|
|
command := workspaceAlternative("command", "run_workspace", true, false)
|
|
openAITools := []any{openAIChatTool("write_file", structuredSchema()), unrelatedTool()}
|
|
anthropicTools := []any{anthropicWorkspaceTool("write_file", structuredSchema()), unrelatedTool()}
|
|
|
|
t.Run("normalizes actual OpenAI and Anthropic definitions", func(t *testing.T) {
|
|
openAIBinding, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{structured}, openAITools)
|
|
if err != nil {
|
|
t.Fatalf("compile OpenAI tool: %v", err)
|
|
}
|
|
anthropicBinding, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{structured}, anthropicTools)
|
|
if err != nil {
|
|
t.Fatalf("compile Anthropic tool: %v", err)
|
|
}
|
|
if openAIBinding.alternativeName != "structured" || anthropicBinding.alternativeName != "structured" {
|
|
t.Fatalf("unexpected selected alternatives: %q, %q", openAIBinding.alternativeName, anthropicBinding.alternativeName)
|
|
}
|
|
if openAIBinding.fingerprint != anthropicBinding.fingerprint {
|
|
t.Fatalf("normalized endpoint shapes must fingerprint identically: %s != %s", openAIBinding.fingerprint, anthropicBinding.fingerprint)
|
|
}
|
|
})
|
|
|
|
t.Run("normalizes native decoded Anthropic tool", func(t *testing.T) {
|
|
rawSchema, err := json.Marshal(structuredSchema())
|
|
if err != nil {
|
|
t.Fatalf("marshal native schema: %v", err)
|
|
}
|
|
nativeBinding, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{structured}, []anthropicTool{
|
|
anthropicTool{Name: "write_file", Description: "workspace tool", InputSchema: rawSchema},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("compile native Anthropic tool: %v", err)
|
|
}
|
|
openAIBinding := mustBinding(t, structured, []any{openAIChatTool("write_file", structuredSchema())})
|
|
if nativeBinding.fingerprint != openAIBinding.fingerprint {
|
|
t.Fatalf("native Anthropic fingerprint = %s, want %s", nativeBinding.fingerprint, openAIBinding.fingerprint)
|
|
}
|
|
})
|
|
|
|
t.Run("rejects unsupported command placeholders and missing write content", func(t *testing.T) {
|
|
for name, argv := range map[string][]any{
|
|
"missing content": {"write", "{path}"},
|
|
"duplicate content": {"write", "{path}", "{content}", "{content}"},
|
|
"embedded placeholder": {"write", "--path={path}", "{content}"},
|
|
"unknown placeholder": {"write", "{path}", "{unsupported}", "{content}"},
|
|
"missing path placeholder": {"write", "{content}"},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
invalid := workspaceAlternative("invalid-command", "run_workspace", true, true)
|
|
invalid.Operations["write"] = config.ExecutionWorkspaceOperation{
|
|
ToolName: "run_workspace", SchemaMatcher: map[string]any{"type": "object"},
|
|
ArgumentMap: map[string]any{"path": "path", "content": "content", "command": "command", "argv": argv},
|
|
ResultMatcher: successMatcher(), CreatesParents: true,
|
|
}
|
|
if _, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{invalid}, []any{openAIChatTool("run_workspace", commandSchema())}); err == nil {
|
|
t.Fatal("invalid command template unexpectedly compiled")
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("uses configured order and rejects name heuristics", func(t *testing.T) {
|
|
fallback, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{command, structured}, openAITools)
|
|
if err != nil {
|
|
t.Fatalf("compile fallback: %v", err)
|
|
}
|
|
if fallback.alternativeName != "structured" {
|
|
t.Fatalf("expected configured second alternative, got %q", fallback.alternativeName)
|
|
}
|
|
if _, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{structured}, []any{unrelatedTool()}); err == nil {
|
|
t.Fatal("unrelated get_weather tool must not bind by lexical role inference")
|
|
}
|
|
})
|
|
|
|
t.Run("rejects missing tools, schema mismatch, and incomplete parent contract", func(t *testing.T) {
|
|
missing := workspaceAlternative("missing", "absent_tool", true, true)
|
|
if _, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{missing}, openAITools); err == nil {
|
|
t.Fatal("missing configured tool unexpectedly bound")
|
|
}
|
|
mismatched := workspaceAlternative("mismatched", "write_file", true, true)
|
|
mismatched.Operations["write"] = config.ExecutionWorkspaceOperation{
|
|
ToolName: "write_file",
|
|
SchemaMatcher: map[string]any{"type": "object", "properties": map[string]any{"bytes": map[string]any{"type": "number"}}},
|
|
ArgumentMap: map[string]any{"path": "path", "content": "content"},
|
|
ResultMatcher: successMatcher(),
|
|
CreatesParents: true,
|
|
}
|
|
if _, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{mismatched}, openAITools); err == nil {
|
|
t.Fatal("schema-mismatched configured tool unexpectedly bound")
|
|
}
|
|
noPrepare := workspaceAlternative("no-prepare", "write_file", false, false)
|
|
delete(noPrepare.Operations, "prepare")
|
|
if _, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{noPrepare}, openAITools); err == nil {
|
|
t.Fatal("write without parent capability or prepare unexpectedly bound")
|
|
}
|
|
})
|
|
|
|
t.Run("copies full contract into a stable fingerprint", func(t *testing.T) {
|
|
binding, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{structured}, openAITools)
|
|
if err != nil {
|
|
t.Fatalf("compile binding: %v", err)
|
|
}
|
|
before := binding.fingerprint
|
|
structured.Operations["write"] = config.ExecutionWorkspaceOperation{ToolName: "changed"}
|
|
if binding.fingerprint != before || binding.operation(opKindWrite).toolName != "write_file" {
|
|
t.Fatal("binding retained mutable config state")
|
|
}
|
|
openAITools[0].(map[string]any)["function"].(map[string]any)["parameters"].(map[string]any)["properties"].(map[string]any)["path"] = map[string]any{"type": "number"}
|
|
if binding.operation(opKindWrite).normalizedSchema.properties["path"].(map[string]any)["type"] != "string" {
|
|
t.Fatal("binding retained mutable request tool schema")
|
|
}
|
|
withDifferentReceipt := workspaceAlternative("structured", "write_file", false, true)
|
|
withDifferentReceipt.Operations["write"] = config.ExecutionWorkspaceOperation{
|
|
ToolName: "write_file", SchemaMatcher: map[string]any{"type": "object"},
|
|
ArgumentMap: map[string]any{"path": "path", "content": "content"},
|
|
ResultMatcher: map[string]any{"status": "success", "result": map[string]any{"saved": true}}, CreatesParents: true,
|
|
}
|
|
changed, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{withDifferentReceipt}, openAITools)
|
|
if err != nil {
|
|
t.Fatalf("compile changed receipt binding: %v", err)
|
|
}
|
|
if before == changed.fingerprint {
|
|
t.Fatal("fingerprint omitted configured result contract")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceContainmentGuard(t *testing.T) {
|
|
writeCall := func(path string) normalizedToolCall {
|
|
return normalizedToolCall{ID: "guard-call", Name: "write_file", Arguments: map[string]any{"path": path, "content": "x"}}
|
|
}
|
|
|
|
t.Run("parent-capable write admits fresh nested parents", func(t *testing.T) {
|
|
binding := mustBinding(t, workspaceAlternative("parents", "write_file", false, true), []any{openAIChatTool("write_file", structuredSchema())})
|
|
payload, err := encodeWorkspaceCall(binding, opKindWrite, writeCall(".iop/job/request-1/plan.md"))
|
|
if err != nil {
|
|
t.Fatalf("encode workspace call: %v", err)
|
|
}
|
|
if err := evaluateContainmentGuard(t.TempDir(), payload.containmentGuard); err != nil {
|
|
t.Fatalf("parent-capable guard rejected a fresh nested path: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("write without parent capability requires immediate parent", func(t *testing.T) {
|
|
binding := mustBinding(t, workspaceAlternative("prepare-required", "write_file", false, false), []any{openAIChatTool("write_file", structuredSchema())})
|
|
payload, err := encodeWorkspaceCall(binding, opKindWrite, writeCall(".iop/job/request-2/plan.md"))
|
|
if err != nil {
|
|
t.Fatalf("encode workspace call: %v", err)
|
|
}
|
|
if err := evaluateContainmentGuard(t.TempDir(), payload.containmentGuard); err == nil {
|
|
t.Fatal("non-parent-capable guard accepted a missing immediate parent")
|
|
}
|
|
})
|
|
|
|
t.Run("root workspace admits existing relative target", func(t *testing.T) {
|
|
binding := mustBinding(t, workspaceAlternative("parents", "write_file", false, true), []any{openAIChatTool("write_file", structuredSchema())})
|
|
payload, err := encodeWorkspaceCall(binding, opKindWrite, writeCall("tmp"))
|
|
if err != nil {
|
|
t.Fatalf("encode workspace call: %v", err)
|
|
}
|
|
if err := evaluateContainmentGuard("/", payload.containmentGuard); err != nil {
|
|
t.Fatalf("root workspace guard rejected existing relative target: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("root workspace admits non-parent-capable target with existing immediate parent", func(t *testing.T) {
|
|
binding := mustBinding(t, workspaceAlternative("prepare-required", "write_file", false, false), []any{openAIChatTool("write_file", structuredSchema())})
|
|
payload, err := encodeWorkspaceCall(binding, opKindWrite, writeCall("tmp/iop_root_test_file.txt"))
|
|
if err != nil {
|
|
t.Fatalf("encode workspace call: %v", err)
|
|
}
|
|
if err := evaluateContainmentGuard("/", payload.containmentGuard); err != nil {
|
|
t.Fatalf("root workspace guard rejected non-parent-capable target with existing parent: %v", err)
|
|
}
|
|
})
|
|
|
|
for name, setup := range map[string]func(t *testing.T, root, outside string){
|
|
"final symlink": func(t *testing.T, root, outside string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Join(root, ".iop", "job", "request-3"), 0o755); err != nil {
|
|
t.Fatalf("create workspace path: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(outside, "target.md"), []byte("outside"), 0o600); err != nil {
|
|
t.Fatalf("create outside target: %v", err)
|
|
}
|
|
if err := os.Symlink(filepath.Join(outside, "target.md"), filepath.Join(root, ".iop", "job", "request-3", "plan.md")); err != nil {
|
|
t.Fatalf("create final symlink: %v", err)
|
|
}
|
|
},
|
|
"ancestor symlink": func(t *testing.T, root, outside string) {
|
|
t.Helper()
|
|
if err := os.Symlink(outside, filepath.Join(root, ".iop")); err != nil {
|
|
t.Fatalf("create ancestor symlink: %v", err)
|
|
}
|
|
},
|
|
} {
|
|
t.Run(name+" escapes workspace", func(t *testing.T) {
|
|
root := t.TempDir()
|
|
outside := t.TempDir()
|
|
setup(t, root, outside)
|
|
binding := mustBinding(t, workspaceAlternative("parents", "write_file", false, true), []any{openAIChatTool("write_file", structuredSchema())})
|
|
payload, err := encodeWorkspaceCall(binding, opKindWrite, writeCall(".iop/job/request-3/plan.md"))
|
|
if err != nil {
|
|
t.Fatalf("encode workspace call: %v", err)
|
|
}
|
|
if err := evaluateContainmentGuard(root, payload.containmentGuard); err == nil {
|
|
t.Fatal("symlink escape was accepted")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// evaluateContainmentGuard executes only the generated guard against a
|
|
// temporary workspace fixture. It never invokes a caller workspace command.
|
|
func evaluateContainmentGuard(root, guard string) error {
|
|
cmd := exec.Command("sh", "-c", guard)
|
|
cmd.Env = append(os.Environ(), "IOP_WORKSPACE_CWD="+root)
|
|
return cmd.Run()
|
|
}
|
|
|
|
func TestWorkspaceCommandEncodingAndGuards(t *testing.T) {
|
|
structured := workspaceAlternative("structured", "write_file", false, true)
|
|
command := workspaceAlternative("command", "run_workspace", true, false)
|
|
|
|
t.Run("structured payload preserves typed values and identities", func(t *testing.T) {
|
|
binding := mustBinding(t, structured, []any{openAIChatTool("write_file", structuredSchema())})
|
|
content := map[string]any{"lines": []any{"first", 2, true}, "nested": map[string]any{"raw": "' $HOME"}}
|
|
payload, err := encodeWorkspaceCall(binding, opKindWrite, normalizedToolCall{
|
|
ID: "public-1", ProviderCallID: "provider-1", Name: "write_file",
|
|
Arguments: map[string]any{"path": ".iop/job/r1/plan.md", "content": content, "ignored": "must not pass"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encode structured call: %v", err)
|
|
}
|
|
if payload.publicCallID != "public-1" || payload.providerCallID != "provider-1" {
|
|
t.Fatalf("call identities lost: %#v", payload)
|
|
}
|
|
if !reflect.DeepEqual(payload.structuredArgs["content"], content) {
|
|
t.Fatalf("structured content changed: %#v", payload.structuredArgs["content"])
|
|
}
|
|
if _, present := payload.structuredArgs["ignored"]; present {
|
|
t.Fatal("unmapped structured argument escaped the configured contract")
|
|
}
|
|
})
|
|
|
|
t.Run("command mapping has fixed positions and shell-safe output", func(t *testing.T) {
|
|
binding := mustBinding(t, command, []any{openAIChatTool("run_workspace", commandSchema())})
|
|
call := normalizedToolCall{ID: "public-2", Name: "run_workspace", Arguments: map[string]any{"path": ".iop/job/r2/review.md", "content": "hello 'world'"}}
|
|
first, err := encodeWorkspaceCall(binding, opKindWrite, call)
|
|
if err != nil {
|
|
t.Fatalf("encode command call: %v", err)
|
|
}
|
|
second, err := encodeWorkspaceCall(binding, opKindWrite, call)
|
|
if err != nil || first.commandString != second.commandString {
|
|
t.Fatalf("command encoding is not deterministic: %q / %q (%v)", first.commandString, second.commandString, err)
|
|
}
|
|
wantArgv := []string{"write", ".iop/job/r2/review.md", "hello 'world'"}
|
|
if !reflect.DeepEqual(first.commandArgv, wantArgv) {
|
|
t.Fatalf("command argv = %#v, want %#v", first.commandArgv, wantArgv)
|
|
}
|
|
if !strings.Contains(first.commandString, "'\\''") {
|
|
t.Fatalf("command does not safely quote apostrophe: %q", first.commandString)
|
|
}
|
|
})
|
|
|
|
t.Run("no-escape guard is concrete and unsafe paths fail before caller execution", func(t *testing.T) {
|
|
binding := mustBinding(t, structured, []any{openAIChatTool("write_file", structuredSchema())})
|
|
for _, path := range []string{"../escape", "/etc/passwd", ".iop/job/r3/../../escape", "bad;rm"} {
|
|
if _, err := encodeWorkspaceCall(binding, opKindWrite, normalizedToolCall{ID: "public-3", Name: "write_file", Arguments: map[string]any{"path": path, "content": "x"}}); err == nil {
|
|
t.Fatalf("unsafe path %q was accepted", path)
|
|
}
|
|
}
|
|
payload, err := encodeWorkspaceCall(binding, opKindWrite, normalizedToolCall{ID: "public-4", Name: "write_file", Arguments: map[string]any{"path": ".iop/job/r4/plan.md", "content": "x"}})
|
|
if err != nil {
|
|
t.Fatalf("encode safe path: %v", err)
|
|
}
|
|
for _, required := range []string{"IOP_WS_ROOT=", "IOP_WORKSPACE_CWD", "realpath -e", "IOP_WS_CANDIDATE=", "path escapes workspace root"} {
|
|
if !strings.Contains(payload.containmentGuard, required) {
|
|
t.Fatalf("guard missing %q: %s", required, payload.containmentGuard)
|
|
}
|
|
}
|
|
if !strings.Contains(payload.containmentGuard, `IOP_WS_CANDIDATE="$IOP_WS_ROOT/.iop/job/r4/plan.md"`) {
|
|
t.Fatalf("guard does not retain exact candidate path: %s", payload.containmentGuard)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceBindingReceipts(t *testing.T) {
|
|
binding := mustBinding(t, workspaceAlternative("structured", "write_file", false, true), []any{openAIChatTool("write_file", structuredSchema())})
|
|
payload, err := encodeWorkspaceCall(binding, opKindWrite, normalizedToolCall{
|
|
ID: "public-receipt", ProviderCallID: "provider-receipt", Name: "write_file",
|
|
Arguments: map[string]any{"path": ".iop/job/r5/plan.md", "content": "plan"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encode payload: %v", err)
|
|
}
|
|
|
|
t.Run("configured exact receipt accepts either issued identity", func(t *testing.T) {
|
|
for _, id := range []string{"public-receipt", "provider-receipt"} {
|
|
receipt := matchResultReceipt(binding, payload, workspaceResult{callID: id, status: "success", body: []byte(`{"written":true}`)})
|
|
if !receipt.matched || receipt.fingerprint != binding.fingerprint || receipt.path != payload.safePath {
|
|
t.Fatalf("valid receipt did not correlate: %#v", receipt)
|
|
}
|
|
}
|
|
})
|
|
|
|
for name, result := range map[string]workspaceResult{
|
|
"opaque": {callID: "public-receipt", status: "success"},
|
|
"error": {callID: "public-receipt", status: "error", body: []byte(`{"written":true}`)},
|
|
"embedded error": {callID: "public-receipt", status: "success", body: []byte(`{"written":true,"error":{"message":"nope"}}`)},
|
|
"trailing JSON": {callID: "public-receipt", status: "success", body: []byte(`{"written":true} {"error":"nope"}`)},
|
|
"wrong id": {callID: "other", status: "success", body: []byte(`{"written":true}`)},
|
|
"wrong body": {callID: "public-receipt", status: "success", body: []byte(`{"written":false}`)},
|
|
"arbitrary JSON": {callID: "public-receipt", status: "success", body: []byte(`{"anything":"else"}`)},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
if receipt := matchResultReceipt(binding, payload, result); receipt.matched {
|
|
t.Fatalf("mismatched receipt was accepted: %#v", receipt)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("rejects every issued payload mutation", func(t *testing.T) {
|
|
mutations := map[string]func(*workspaceEncodedPayload){
|
|
"operation": func(p *workspaceEncodedPayload) { p.operation = opKindPrepare },
|
|
"path": func(p *workspaceEncodedPayload) { p.safePath = ".iop/job/r5/review.md" },
|
|
"arguments": func(p *workspaceEncodedPayload) { p.structuredArgs["content"] = "mutated" },
|
|
"guard": func(p *workspaceEncodedPayload) { p.containmentGuard = "mutated" },
|
|
}
|
|
for name, mutate := range mutations {
|
|
t.Run(name, func(t *testing.T) {
|
|
copy := cloneWorkspacePayload(payload)
|
|
mutate(copy)
|
|
if receipt := matchResultReceipt(binding, copy, workspaceResult{callID: "public-receipt", status: "success", body: []byte(`{"written":true}`)}); receipt.matched {
|
|
t.Fatalf("mutated payload unexpectedly matched: %#v", receipt)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceResultExactness(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
result workspaceResult
|
|
wantExact bool
|
|
}{
|
|
{
|
|
name: "empty success body is opaque",
|
|
result: workspaceResult{status: "success", body: nil},
|
|
wantExact: false,
|
|
},
|
|
{
|
|
name: "whitespace success body is opaque",
|
|
result: workspaceResult{status: "success", body: []byte(" \n\t ")},
|
|
wantExact: false,
|
|
},
|
|
{
|
|
name: "empty explicit error is exact",
|
|
result: workspaceResult{status: "error", body: nil},
|
|
wantExact: true,
|
|
},
|
|
{
|
|
name: "non-empty matcher failure is exact",
|
|
result: workspaceResult{status: "success", body: []byte(`{"written":false}`)},
|
|
wantExact: true,
|
|
},
|
|
{
|
|
name: "malformed json body is opaque",
|
|
result: workspaceResult{status: "success", body: []byte(`not-json`)},
|
|
wantExact: false,
|
|
},
|
|
{
|
|
name: "trailing json body is opaque",
|
|
result: workspaceResult{status: "success", body: []byte(`{"written":true} {"error":"nope"}`)},
|
|
wantExact: false,
|
|
},
|
|
{
|
|
name: "valid success receipt body is exact",
|
|
result: workspaceResult{status: "success", body: []byte(`{"written":true}`)},
|
|
wantExact: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := workspaceResultIsExact(tt.result); got != tt.wantExact {
|
|
t.Fatalf("workspaceResultIsExact() = %v, want %v", got, tt.wantExact)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkspaceOperationMatrix(t *testing.T) {
|
|
nativeSchema, err := json.Marshal(structuredSchema())
|
|
if err != nil {
|
|
t.Fatalf("marshal native schema: %v", err)
|
|
}
|
|
for _, tc := range []struct {
|
|
name string
|
|
command bool
|
|
tools any
|
|
}{
|
|
{name: "structured", tools: []any{openAIChatTool("workspace", structuredSchema())}},
|
|
{name: "command", command: true, tools: []any{openAIChatTool("workspace", commandSchema())}},
|
|
{name: "native Anthropic", tools: []anthropicTool{{Name: "workspace", Description: "workspace tool", InputSchema: nativeSchema}}},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
binding := mustBinding(t, fullWorkspaceAlternative(tc.name, "workspace", tc.command), tc.tools)
|
|
for _, operation := range canonicalOperationOrder {
|
|
args := map[string]any{"path": ".iop/job/r6/" + string(operation) + ".md"}
|
|
if operation == opKindWrite {
|
|
args["content"] = "content"
|
|
}
|
|
payload, err := encodeWorkspaceCall(binding, operation, normalizedToolCall{ID: "call-" + string(operation), Name: "workspace", Arguments: args})
|
|
if err != nil {
|
|
t.Fatalf("encode %s: %v", operation, err)
|
|
}
|
|
if payload.operation != operation || payload.correlationDigest == "" {
|
|
t.Fatalf("payload for %s is not sealed: %#v", operation, payload)
|
|
}
|
|
if receipt := matchResultReceipt(binding, payload, workspaceResult{callID: payload.publicCallID, status: "success", body: []byte(`{"written":true}`)}); !receipt.matched {
|
|
t.Fatalf("valid %s receipt did not match: %#v", operation, receipt)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("ordered complete alternatives and missing tools", func(t *testing.T) {
|
|
first := fullWorkspaceAlternative("first", "first_workspace", false)
|
|
second := fullWorkspaceAlternative("second", "second_workspace", false)
|
|
tools := []any{openAIChatTool("second_workspace", structuredSchema()), openAIChatTool("first_workspace", structuredSchema())}
|
|
binding, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{second, first}, tools)
|
|
if err != nil || binding.alternativeName != "second" {
|
|
t.Fatalf("configured first complete alternative was not selected: binding=%#v err=%v", binding, err)
|
|
}
|
|
if _, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{first}, []any{openAIChatTool("first_workspace", structuredSchema()), unrelatedTool()}); err != nil {
|
|
t.Fatalf("extra unrelated tool must not invalidate a complete alternative: %v", err)
|
|
}
|
|
if _, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{first}, []any{unrelatedTool()}); err == nil {
|
|
t.Fatal("missing complete operation tool unexpectedly bound")
|
|
}
|
|
})
|
|
}
|
|
|
|
func mustBinding(t *testing.T, alternative config.ExecutionWorkspaceToolAlternative, tools any) *workspaceBinding {
|
|
t.Helper()
|
|
binding, err := compileWorkspaceBinding([]config.ExecutionWorkspaceToolAlternative{alternative}, tools)
|
|
if err != nil {
|
|
t.Fatalf("compile binding: %v", err)
|
|
}
|
|
return binding
|
|
}
|
|
|
|
func workspaceAlternative(name, toolName string, command, createsParents bool) config.ExecutionWorkspaceToolAlternative {
|
|
argumentMap := map[string]any{"path": "path", "content": "content"}
|
|
prepareArgumentMap := map[string]any{"path": "path"}
|
|
if command {
|
|
argumentMap = map[string]any{"path": "path", "content": "content", "command": "command", "argv": []any{"write", "{path}", "{content}"}}
|
|
prepareArgumentMap = map[string]any{"path": "path", "command": "command", "argv": []any{"mkdir", "{path}"}}
|
|
}
|
|
return config.ExecutionWorkspaceToolAlternative{
|
|
Name: name,
|
|
Operations: map[string]config.ExecutionWorkspaceOperation{
|
|
"prepare": {ToolName: toolName, SchemaMatcher: map[string]any{"type": "object"}, ArgumentMap: prepareArgumentMap, ResultMatcher: successMatcher(), CreatesParents: true},
|
|
"write": {ToolName: toolName, SchemaMatcher: map[string]any{"type": "object"}, ArgumentMap: argumentMap, ResultMatcher: successMatcher(), CreatesParents: createsParents},
|
|
},
|
|
}
|
|
}
|
|
|
|
func fullWorkspaceAlternative(name, toolName string, command bool) config.ExecutionWorkspaceToolAlternative {
|
|
alternative := workspaceAlternative(name, toolName, command, true)
|
|
for _, operation := range []workspaceOperationKind{opKindRead, opKindDelete} {
|
|
argumentMap := map[string]any{"path": "path"}
|
|
if command {
|
|
argumentMap = map[string]any{"path": "path", "command": "command", "argv": []any{string(operation), "{path}"}}
|
|
}
|
|
alternative.Operations[string(operation)] = config.ExecutionWorkspaceOperation{
|
|
ToolName: toolName, SchemaMatcher: map[string]any{"type": "object"}, ArgumentMap: argumentMap, ResultMatcher: successMatcher(), CreatesParents: true,
|
|
}
|
|
}
|
|
return alternative
|
|
}
|
|
|
|
func cloneWorkspacePayload(payload *workspaceEncodedPayload) *workspaceEncodedPayload {
|
|
copy := *payload
|
|
copy.structuredArgs = cloneAnyMap(payload.structuredArgs)
|
|
copy.commandArgv = append([]string(nil), payload.commandArgv...)
|
|
return ©
|
|
}
|
|
|
|
func successMatcher() map[string]any {
|
|
return map[string]any{"status": "success", "result": map[string]any{"written": true}}
|
|
}
|
|
|
|
func structuredSchema() map[string]any {
|
|
return map[string]any{"type": "object", "properties": map[string]any{"path": map[string]any{"type": "string"}, "content": map[string]any{}}, "required": []any{"path", "content"}}
|
|
}
|
|
|
|
func commandSchema() map[string]any {
|
|
return map[string]any{"type": "object", "properties": map[string]any{"command": map[string]any{"type": "string"}}}
|
|
}
|
|
|
|
func openAIChatTool(name string, schema map[string]any) map[string]any {
|
|
return map[string]any{"type": "function", "function": map[string]any{"name": name, "description": "workspace tool", "parameters": schema}}
|
|
}
|
|
|
|
func anthropicWorkspaceTool(name string, schema map[string]any) map[string]any {
|
|
return map[string]any{"name": name, "description": "workspace tool", "input_schema": schema}
|
|
}
|
|
|
|
func unrelatedTool() map[string]any {
|
|
return openAIChatTool("get_weather", map[string]any{"type": "object", "properties": map[string]any{"city": map[string]any{"type": "string"}}})
|
|
}
|