453 lines
10 KiB
Go
453 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
var toolCallCounter int
|
|
|
|
func nextToolID() string {
|
|
toolCallCounter++
|
|
return fmt.Sprintf("mock_tool_%04d", toolCallCounter)
|
|
}
|
|
|
|
func defaultUsage() *Usage {
|
|
return &Usage{
|
|
InputTokens: 1200,
|
|
OutputTokens: 350,
|
|
}
|
|
}
|
|
|
|
// --- Atomic emitters ---
|
|
|
|
// emitThinking emits an assistant message with a thinking block.
|
|
func emitThinking(enc *json.Encoder, model string) {
|
|
randomDelay(model)
|
|
emitThinkingBlock(enc, "Analyzing the request and considering the best approach...", "")
|
|
}
|
|
|
|
// emitThinkingBlock emits an assistant message with a thinking content block.
|
|
func emitThinkingBlock(enc *json.Encoder, thought, parentToolUseID string) {
|
|
_ = enc.Encode(AssistantMsg{
|
|
Type: TypeAssistant,
|
|
ParentToolUseID: parentToolUseID,
|
|
Message: AssistantBody{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{Type: BlockThinking, Thinking: thought},
|
|
},
|
|
Model: "mock-default",
|
|
Usage: defaultUsage(),
|
|
},
|
|
})
|
|
}
|
|
|
|
// emitTextBlock emits an assistant message with a text content block.
|
|
func emitTextBlock(enc *json.Encoder, text, parentToolUseID string) {
|
|
_ = enc.Encode(AssistantMsg{
|
|
Type: TypeAssistant,
|
|
ParentToolUseID: parentToolUseID,
|
|
Message: AssistantBody{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{Type: BlockText, Text: text},
|
|
},
|
|
Model: "mock-default",
|
|
StopReason: "end_turn",
|
|
Usage: defaultUsage(),
|
|
},
|
|
})
|
|
}
|
|
|
|
// emitReadFile emits a Read tool_use followed by a tool_result using a real workspace file.
|
|
func emitReadFile(enc *json.Encoder, model string) {
|
|
toolID := nextToolID()
|
|
f := randomFile()
|
|
snippet := readFileSnippet(f.absPath, 30)
|
|
randomDelay(model)
|
|
|
|
_ = enc.Encode(AssistantMsg{
|
|
Type: TypeAssistant,
|
|
Message: AssistantBody{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolUse,
|
|
ID: toolID,
|
|
Name: ToolRead,
|
|
Input: map[string]any{
|
|
"file_path": f.absPath,
|
|
},
|
|
},
|
|
},
|
|
Model: "mock-default",
|
|
StopReason: "tool_use",
|
|
Usage: defaultUsage(),
|
|
},
|
|
})
|
|
|
|
randomDelay(model)
|
|
|
|
_ = enc.Encode(UserMsg{
|
|
Type: TypeUser,
|
|
Message: UserMsgBody{
|
|
Role: "user",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolResult,
|
|
ToolUseID: toolID,
|
|
Content: snippet,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// emitEditFile emits an Edit tool_use with a real file and real content fragment.
|
|
func emitEditFile(enc *json.Encoder, scanner *bufio.Scanner, model string) {
|
|
toolID := nextToolID()
|
|
f := randomFile()
|
|
oldStr, newStr := pickEditableFragment(f.absPath)
|
|
randomDelay(model)
|
|
|
|
input := map[string]any{
|
|
"file_path": f.absPath,
|
|
"old_string": oldStr,
|
|
"new_string": newStr,
|
|
}
|
|
|
|
_ = enc.Encode(AssistantMsg{
|
|
Type: TypeAssistant,
|
|
Message: AssistantBody{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolUse,
|
|
ID: toolID,
|
|
Name: ToolEdit,
|
|
Input: input,
|
|
},
|
|
},
|
|
Model: "mock-default",
|
|
StopReason: "tool_use",
|
|
Usage: defaultUsage(),
|
|
},
|
|
})
|
|
|
|
allowed := requestPermission(enc, scanner, ToolEdit, toolID, input)
|
|
|
|
if allowed {
|
|
_ = enc.Encode(UserMsg{
|
|
Type: TypeUser,
|
|
Message: UserMsgBody{
|
|
Role: "user",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolResult,
|
|
ToolUseID: toolID,
|
|
Content: "File edited successfully: " + f.absPath,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
} else {
|
|
emitTextBlock(enc, "Permission denied for Edit, skipping.", "")
|
|
}
|
|
}
|
|
|
|
// emitShellExec emits a Bash tool_use with permission request, then tool_result.
|
|
func emitShellExec(enc *json.Encoder, scanner *bufio.Scanner, model string) {
|
|
toolID := nextToolID()
|
|
randomDelay(model)
|
|
|
|
input := map[string]any{
|
|
"command": "go test ./...",
|
|
"description": "Run all tests",
|
|
}
|
|
|
|
_ = enc.Encode(AssistantMsg{
|
|
Type: TypeAssistant,
|
|
Message: AssistantBody{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolUse,
|
|
ID: toolID,
|
|
Name: ToolBash,
|
|
Input: input,
|
|
},
|
|
},
|
|
Model: "mock-default",
|
|
StopReason: "tool_use",
|
|
Usage: defaultUsage(),
|
|
},
|
|
})
|
|
|
|
allowed := requestPermission(enc, scanner, ToolBash, toolID, input)
|
|
|
|
if allowed {
|
|
_ = enc.Encode(UserMsg{
|
|
Type: TypeUser,
|
|
Message: UserMsgBody{
|
|
Role: "user",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolResult,
|
|
ToolUseID: toolID,
|
|
Content: "ok \tgithub.com/example/project\t0.042s\nPASS",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
} else {
|
|
emitTextBlock(enc, "Permission denied for Bash, skipping.", "")
|
|
}
|
|
}
|
|
|
|
// emitCodeSearch emits a Grep tool_use using real workspace file paths.
|
|
func emitCodeSearch(enc *json.Encoder, model string) {
|
|
toolID := nextToolID()
|
|
randomDelay(model)
|
|
|
|
searchPatterns := []string{"func ", "import ", "TODO", "return ", "error", "type "}
|
|
pattern := searchPatterns[toolCallCounter%len(searchPatterns)]
|
|
|
|
f := randomFile()
|
|
_ = enc.Encode(AssistantMsg{
|
|
Type: TypeAssistant,
|
|
Message: AssistantBody{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolUse,
|
|
ID: toolID,
|
|
Name: ToolGrep,
|
|
Input: map[string]any{
|
|
"pattern": pattern,
|
|
"path": f.absPath,
|
|
},
|
|
},
|
|
},
|
|
Model: "mock-default",
|
|
StopReason: "tool_use",
|
|
Usage: defaultUsage(),
|
|
},
|
|
})
|
|
|
|
randomDelay(model)
|
|
|
|
// Build realistic search results using real file paths
|
|
paths := randomFilePaths(3)
|
|
var results []string
|
|
for i, p := range paths {
|
|
results = append(results, fmt.Sprintf("%s:%d:%s found here", p, (i+1)*10, strings.TrimSpace(pattern)))
|
|
}
|
|
|
|
_ = enc.Encode(UserMsg{
|
|
Type: TypeUser,
|
|
Message: UserMsgBody{
|
|
Role: "user",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolResult,
|
|
ToolUseID: toolID,
|
|
Content: strings.Join(results, "\n"),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// emitSubagent emits a Task tool_use with nested child messages using real files.
|
|
func emitSubagent(enc *json.Encoder, scanner *bufio.Scanner, model string) {
|
|
taskToolID := nextToolID()
|
|
randomDelay(model)
|
|
|
|
_ = enc.Encode(AssistantMsg{
|
|
Type: TypeAssistant,
|
|
Message: AssistantBody{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolUse,
|
|
ID: taskToolID,
|
|
Name: ToolTask,
|
|
Input: map[string]any{
|
|
"description": "Explore codebase",
|
|
"prompt": "Find all files and summarize the project structure",
|
|
},
|
|
},
|
|
},
|
|
Model: "mock-default",
|
|
StopReason: "tool_use",
|
|
Usage: defaultUsage(),
|
|
},
|
|
})
|
|
|
|
randomDelay(model)
|
|
|
|
_ = enc.Encode(SystemMsg{
|
|
Type: TypeSystem,
|
|
SessionID: sessionID,
|
|
SessionStatus: "active",
|
|
})
|
|
|
|
randomDelay(model)
|
|
emitThinkingBlock(enc, "Exploring the project structure...", taskToolID)
|
|
randomDelay(model)
|
|
|
|
paths := randomFilePaths(5)
|
|
emitTextBlock(enc, fmt.Sprintf("Found %d files. The project structure looks well-organized.", len(paths)), taskToolID)
|
|
randomDelay(model)
|
|
|
|
// Child Glob tool
|
|
childToolID := nextToolID()
|
|
_ = enc.Encode(AssistantMsg{
|
|
Type: TypeAssistant,
|
|
ParentToolUseID: taskToolID,
|
|
Message: AssistantBody{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolUse,
|
|
ID: childToolID,
|
|
Name: ToolGlob,
|
|
Input: map[string]any{
|
|
"pattern": "**/*",
|
|
},
|
|
},
|
|
},
|
|
Model: "mock-default",
|
|
StopReason: "tool_use",
|
|
Usage: defaultUsage(),
|
|
},
|
|
})
|
|
|
|
randomDelay(model)
|
|
|
|
_ = enc.Encode(UserMsg{
|
|
Type: TypeUser,
|
|
ParentToolUseID: taskToolID,
|
|
Message: UserMsgBody{
|
|
Role: "user",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolResult,
|
|
ToolUseID: childToolID,
|
|
Content: strings.Join(paths, "\n"),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
randomDelay(model)
|
|
emitTextBlock(enc, "Project structure analysis complete.", taskToolID)
|
|
randomDelay(model)
|
|
|
|
_ = enc.Encode(UserMsg{
|
|
Type: TypeUser,
|
|
Message: UserMsgBody{
|
|
Role: "user",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolResult,
|
|
ToolUseID: taskToolID,
|
|
Content: fmt.Sprintf("Subagent completed: Found %d files across the project.", len(paths)),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// emitTodo emits a TodoWrite tool_use followed by a tool_result.
|
|
func emitTodo(enc *json.Encoder, model string) {
|
|
toolID := nextToolID()
|
|
randomDelay(model)
|
|
|
|
_ = enc.Encode(AssistantMsg{
|
|
Type: TypeAssistant,
|
|
Message: AssistantBody{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolUse,
|
|
ID: toolID,
|
|
Name: ToolTodoWrite,
|
|
Input: map[string]any{
|
|
"todos": []map[string]any{
|
|
{"id": "1", "content": "Review code changes", "status": "in_progress"},
|
|
{"id": "2", "content": "Run tests", "status": "pending"},
|
|
{"id": "3", "content": "Update documentation", "status": "pending"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Model: "mock-default",
|
|
StopReason: "tool_use",
|
|
Usage: defaultUsage(),
|
|
},
|
|
})
|
|
|
|
randomDelay(model)
|
|
|
|
_ = enc.Encode(UserMsg{
|
|
Type: TypeUser,
|
|
Message: UserMsgBody{
|
|
Role: "user",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolResult,
|
|
ToolUseID: toolID,
|
|
Content: "Todo list updated: 3 items (1 in progress, 2 pending)",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// emitWebFetch emits a WebFetch tool_use followed by a tool_result.
|
|
func emitWebFetch(enc *json.Encoder, model string) {
|
|
toolID := nextToolID()
|
|
randomDelay(model)
|
|
|
|
_ = enc.Encode(AssistantMsg{
|
|
Type: TypeAssistant,
|
|
Message: AssistantBody{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolUse,
|
|
ID: toolID,
|
|
Name: ToolWebFetch,
|
|
Input: map[string]any{
|
|
"url": "https://example.com/api/docs",
|
|
"prompt": "Extract the API endpoints and their descriptions",
|
|
},
|
|
},
|
|
},
|
|
Model: "mock-default",
|
|
StopReason: "tool_use",
|
|
Usage: defaultUsage(),
|
|
},
|
|
})
|
|
|
|
randomDelay(model)
|
|
|
|
_ = enc.Encode(UserMsg{
|
|
Type: TypeUser,
|
|
Message: UserMsgBody{
|
|
Role: "user",
|
|
Content: []ContentBlock{
|
|
{
|
|
Type: BlockToolResult,
|
|
ToolUseID: toolID,
|
|
Content: "API Documentation:\n- GET /api/v1/users - List all users\n- POST /api/v1/users - Create a new user\n- GET /api/v1/users/:id - Get user by ID\n- PUT /api/v1/users/:id - Update user\n- DELETE /api/v1/users/:id - Delete user",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|