refactor(tools): reorganize tool packages and facades

This commit is contained in:
lc6464
2026-04-17 13:44:31 +08:00
parent ee634dc8db
commit 4c133dc2d9
44 changed files with 778 additions and 98 deletions
+15
View File
@@ -0,0 +1,15 @@
package tools
import "testing"
func TestFacadeConstructorsRemainAvailable(t *testing.T) {
if NewI2CTool() == nil {
t.Fatal("NewI2CTool should return a tool")
}
if NewSPITool() == nil {
t.Fatal("NewSPITool should return a tool")
}
if NewMessageTool() == nil {
t.Fatal("NewMessageTool should return a tool")
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
package tools
package fstools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package fstools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package fstools
import (
"bufio"
@@ -24,6 +24,18 @@ import (
const MaxReadFileSize = 64 * 1024 // 64KB limit to avoid context overflow
func ValidatePathWithAllowPaths(
path, workspace string,
restrict bool,
patterns []*regexp.Regexp,
) (string, error) {
return validatePathWithAllowPaths(path, workspace, restrict, patterns)
}
func IsAllowedPath(path string, patterns []*regexp.Regexp) bool {
return isAllowedPath(path, patterns)
}
func validatePathWithAllowPaths(
path, workspace string,
restrict bool,
@@ -1,4 +1,4 @@
package tools
package fstools
import (
"context"
@@ -1050,43 +1050,6 @@ func TestReadFileLinesTool_OffsetBeyondEOF(t *testing.T) {
}
}
func TestReadFileLinesTool_RegistryValidationSupportsMaxLinesAndRejectsLimit(t *testing.T) {
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "registry_lines.txt")
err := os.WriteFile(testFile, []byte("line 1\nline 2\nline 3\n"), 0o644)
if err != nil {
t.Fatalf("Failed to write test file: %v", err)
}
reg := NewToolRegistry()
reg.Register(NewReadFileLinesTool(tmpDir, false, MaxReadFileSize))
result := reg.Execute(context.Background(), "read_file", map[string]any{
"path": testFile,
"start_line": 1,
"max_lines": 1,
})
if result.IsError {
t.Fatalf("expected max_lines to pass registry validation, got: %s", result.ForLLM)
}
if !strings.Contains(result.ForLLM, "1|line 1\n") {
t.Fatalf("expected first line via max_lines, got: %s", result.ForLLM)
}
result = reg.Execute(context.Background(), "read_file", map[string]any{
"path": testFile,
"start_line": 2,
"limit": 1,
})
if !result.IsError {
t.Fatalf("expected limit to be rejected, got success: %s", result.ForLLM)
}
if !strings.Contains(result.ForLLM, "unexpected property \"limit\"") {
t.Fatalf("expected registry validation error for limit, got: %s", result.ForLLM)
}
}
func TestReadFileLinesTool_RejectsOffset(t *testing.T) {
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "legacy_offset.txt")
@@ -1,4 +1,4 @@
package tools
package fstools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package fstools
import (
"context"
@@ -9,7 +9,6 @@ import (
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/media"
"github.com/sipeed/picoclaw/pkg/providers"
)
func TestLoadImage_PathRequired(t *testing.T) {
@@ -78,28 +77,6 @@ func TestLoadImage_FileTooLarge(t *testing.T) {
}
}
func TestSubagentManager_SetMediaResolver_StoresResolver(t *testing.T) {
manager := NewSubagentManager(nil, "gpt-test", "/tmp")
called := false
manager.SetMediaResolver(func(msgs []providers.Message) []providers.Message {
called = true
return msgs
})
manager.mu.RLock()
got := manager.mediaResolver
manager.mu.RUnlock()
if got == nil {
t.Fatal("expected mediaResolver to be set")
}
if called {
t.Fatal("resolver should not be called during SetMediaResolver")
}
}
func TestLoadImage_SuccessPath(t *testing.T) {
dir := t.TempDir()
@@ -1,4 +1,4 @@
package tools
package fstools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package fstools
import (
"context"
+37
View File
@@ -0,0 +1,37 @@
package fstools
import (
"context"
toolshared "github.com/sipeed/picoclaw/pkg/tools/shared"
)
type ToolResult = toolshared.ToolResult
func WithToolContext(ctx context.Context, channel, chatID string) context.Context {
return toolshared.WithToolContext(ctx, channel, chatID)
}
func ToolChannel(ctx context.Context) string {
return toolshared.ToolChannel(ctx)
}
func ToolChatID(ctx context.Context) string {
return toolshared.ToolChatID(ctx)
}
func ErrorResult(message string) *ToolResult {
return toolshared.ErrorResult(message)
}
func NewToolResult(forLLM string) *ToolResult {
return toolshared.NewToolResult(forLLM)
}
func SilentResult(forLLM string) *ToolResult {
return toolshared.SilentResult(forLLM)
}
func MediaResult(forLLM string, mediaRefs []string) *ToolResult {
return toolshared.MediaResult(forLLM, mediaRefs)
}
+79
View File
@@ -0,0 +1,79 @@
package tools
import (
"regexp"
"github.com/sipeed/picoclaw/pkg/media"
fstools "github.com/sipeed/picoclaw/pkg/tools/fs"
)
type (
ReadFileTool = fstools.ReadFileTool
ReadFileLinesTool = fstools.ReadFileLinesTool
WriteFileTool = fstools.WriteFileTool
ListDirTool = fstools.ListDirTool
EditFileTool = fstools.EditFileTool
AppendFileTool = fstools.AppendFileTool
LoadImageTool = fstools.LoadImageTool
SendFileTool = fstools.SendFileTool
)
const MaxReadFileSize = fstools.MaxReadFileSize
func NewReadFileTool(workspace string, restrict bool, maxReadFileSize int, allowPaths ...[]*regexp.Regexp) *ReadFileTool {
return fstools.NewReadFileTool(workspace, restrict, maxReadFileSize, allowPaths...)
}
func NewReadFileBytesTool(
workspace string,
restrict bool,
maxReadFileSize int,
allowPaths ...[]*regexp.Regexp,
) *ReadFileTool {
return fstools.NewReadFileBytesTool(workspace, restrict, maxReadFileSize, allowPaths...)
}
func NewReadFileLinesTool(
workspace string,
restrict bool,
maxReadFileSize int,
allowPaths ...[]*regexp.Regexp,
) *ReadFileLinesTool {
return fstools.NewReadFileLinesTool(workspace, restrict, maxReadFileSize, allowPaths...)
}
func NewWriteFileTool(workspace string, restrict bool, allowPaths ...[]*regexp.Regexp) *WriteFileTool {
return fstools.NewWriteFileTool(workspace, restrict, allowPaths...)
}
func NewListDirTool(workspace string, restrict bool, allowPaths ...[]*regexp.Regexp) *ListDirTool {
return fstools.NewListDirTool(workspace, restrict, allowPaths...)
}
func NewEditFileTool(workspace string, restrict bool, allowPaths ...[]*regexp.Regexp) *EditFileTool {
return fstools.NewEditFileTool(workspace, restrict, allowPaths...)
}
func NewAppendFileTool(workspace string, restrict bool, allowPaths ...[]*regexp.Regexp) *AppendFileTool {
return fstools.NewAppendFileTool(workspace, restrict, allowPaths...)
}
func NewLoadImageTool(
workspace string,
restrict bool,
maxFileSize int,
store media.MediaStore,
allowPaths ...[]*regexp.Regexp,
) *LoadImageTool {
return fstools.NewLoadImageTool(workspace, restrict, maxFileSize, store, allowPaths...)
}
func NewSendFileTool(
workspace string,
restrict bool,
maxFileSize int,
store media.MediaStore,
allowPaths ...[]*regexp.Regexp,
) *SendFileTool {
return fstools.NewSendFileTool(workspace, restrict, maxFileSize, store, allowPaths...)
}
+46
View File
@@ -0,0 +1,46 @@
package tools
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
)
func TestReadFileLinesTool_RegistryValidationSupportsMaxLinesAndRejectsLimit(t *testing.T) {
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "registry_lines.txt")
err := os.WriteFile(testFile, []byte("line 1\nline 2\nline 3\n"), 0o644)
if err != nil {
t.Fatalf("Failed to write test file: %v", err)
}
reg := NewToolRegistry()
reg.Register(NewReadFileLinesTool(tmpDir, false, MaxReadFileSize))
result := reg.Execute(context.Background(), "read_file", map[string]any{
"path": testFile,
"start_line": 1,
"max_lines": 1,
})
if result.IsError {
t.Fatalf("expected max_lines to pass registry validation, got: %s", result.ForLLM)
}
if !strings.Contains(result.ForLLM, "1|line 1\n") {
t.Fatalf("expected first line via max_lines, got: %s", result.ForLLM)
}
result = reg.Execute(context.Background(), "read_file", map[string]any{
"path": testFile,
"start_line": 2,
"limit": 1,
})
if !result.IsError {
t.Fatalf("expected limit to be rejected, got success: %s", result.ForLLM)
}
if !strings.Contains(result.ForLLM, "unexpected property \"limit\"") {
t.Fatalf("expected registry validation error for limit, got: %s", result.ForLLM)
}
}
@@ -1,4 +1,4 @@
package tools
package hardwaretools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package hardwaretools
import (
"encoding/json"
@@ -1,6 +1,6 @@
//go:build !linux
package tools
package hardwaretools
// scan is a stub for non-Linux platforms.
func (t *I2CTool) scan(args map[string]any) *ToolResult {
+13
View File
@@ -0,0 +1,13 @@
package hardwaretools
import toolshared "github.com/sipeed/picoclaw/pkg/tools/shared"
type ToolResult = toolshared.ToolResult
func ErrorResult(message string) *ToolResult {
return toolshared.ErrorResult(message)
}
func SilentResult(forLLM string) *ToolResult {
return toolshared.SilentResult(forLLM)
}
@@ -1,4 +1,4 @@
package tools
package hardwaretools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package hardwaretools
import (
"encoding/json"
@@ -1,6 +1,6 @@
//go:build !linux
package tools
package hardwaretools
// transfer is a stub for non-Linux platforms.
func (t *SPITool) transfer(args map[string]any) *ToolResult {
+16
View File
@@ -0,0 +1,16 @@
package tools
import hardwaretools "github.com/sipeed/picoclaw/pkg/tools/hardware"
type (
I2CTool = hardwaretools.I2CTool
SPITool = hardwaretools.SPITool
)
func NewI2CTool() *I2CTool {
return hardwaretools.NewI2CTool()
}
func NewSPITool() *SPITool {
return hardwaretools.NewSPITool()
}
+48
View File
@@ -0,0 +1,48 @@
package tools
import "strings"
func sanitizeIdentifierComponent(s string) string {
const maxLen = 64
s = strings.ToLower(s)
var b strings.Builder
b.Grow(len(s))
prevUnderscore := false
for _, r := range s {
isAllowed := (r >= 'a' && r <= 'z') ||
(r >= '0' && r <= '9') ||
r == '_' || r == '-'
if !isAllowed {
if !prevUnderscore {
b.WriteRune('_')
prevUnderscore = true
}
continue
}
if r == '_' {
if prevUnderscore {
continue
}
prevUnderscore = true
} else {
prevUnderscore = false
}
b.WriteRune(r)
}
result := strings.Trim(b.String(), "_")
if result == "" {
result = "unnamed"
}
if len(result) > maxLen {
result = result[:maxLen]
}
return result
}
+134
View File
@@ -0,0 +1,134 @@
package integrationtools
import (
"fmt"
"math"
"mime"
"path/filepath"
"regexp"
"strconv"
"strings"
"unicode"
)
var (
inlineMarkdownDataURLRe = regexp.MustCompile(`!\[[^\]]*\]\((data:[^)]+)\)`)
inlineRawDataURLRe = regexp.MustCompile(`data:[^;\s]+;base64,[A-Za-z0-9+/=\r\n]+`)
)
const (
largeBase64OmittedMessage = "[Tool returned a large base64-like payload; omitted from model context.]"
inlineMediaOmittedMessage = "[Tool returned inline media content; omitted from model context.]"
)
func sanitizeToolLLMContent(text string) string {
trimmed := strings.TrimSpace(text)
if trimmed == "" {
return text
}
if inlineMarkdownDataURLRe.MatchString(trimmed) || inlineRawDataURLRe.MatchString(trimmed) {
cleaned := inlineMarkdownDataURLRe.ReplaceAllString(trimmed, "")
cleaned = inlineRawDataURLRe.ReplaceAllString(cleaned, "")
cleaned = strings.TrimSpace(cleaned)
if cleaned == "" {
return inlineMediaOmittedMessage
}
return cleaned + "\n" + inlineMediaOmittedMessage
}
if looksLikeLargeBase64Payload(trimmed) {
return largeBase64OmittedMessage
}
return text
}
func looksLikeLargeBase64Payload(text string) bool {
trimmed := strings.TrimSpace(text)
if len(trimmed) < 1024 {
return false
}
nonSpace := 0
base64Like := 0
spaceCount := 0
for _, r := range trimmed {
if unicode.IsSpace(r) {
spaceCount++
continue
}
nonSpace++
if (r >= 'A' && r <= 'Z') ||
(r >= 'a' && r <= 'z') ||
(r >= '0' && r <= '9') ||
r == '+' || r == '/' || r == '=' {
base64Like++
}
}
if nonSpace == 0 {
return false
}
ratio := float64(base64Like) / float64(nonSpace)
return ratio >= 0.97 && spaceCount <= len(trimmed)/128
}
func extensionForMIMEType(mimeType string) string {
if mimeType == "" {
return ".bin"
}
if exts, err := mime.ExtensionsByType(mimeType); err == nil && len(exts) > 0 {
return exts[0]
}
switch strings.ToLower(mimeType) {
case "image/jpeg":
return ".jpg"
case "image/png":
return ".png"
case "image/gif":
return ".gif"
case "image/webp":
return ".webp"
case "audio/wav", "audio/x-wav":
return ".wav"
case "audio/mpeg":
return ".mp3"
case "audio/ogg":
return ".ogg"
case "video/mp4":
return ".mp4"
default:
return filepath.Ext(mimeType)
}
}
func getInt64Arg(args map[string]any, key string, defaultVal int64) (int64, error) {
raw, exists := args[key]
if !exists {
return defaultVal, nil
}
switch v := raw.(type) {
case float64:
if v != math.Trunc(v) {
return 0, fmt.Errorf("%s must be an integer, got float %v", key, v)
}
if v > math.MaxInt64 || v < math.MinInt64 {
return 0, fmt.Errorf("%s value %v overflows int64", key, v)
}
return int64(v), nil
case int:
return int64(v), nil
case int64:
return v, nil
case string:
parsed, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, fmt.Errorf("invalid integer format for %s parameter: %w", key, err)
}
return parsed, nil
default:
return 0, fmt.Errorf("unsupported type %T for %s parameter", raw, key)
}
}
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
+77
View File
@@ -0,0 +1,77 @@
package integrationtools
import (
"context"
"github.com/sipeed/picoclaw/pkg/session"
toolshared "github.com/sipeed/picoclaw/pkg/tools/shared"
)
type (
Tool = toolshared.Tool
ToolResult = toolshared.ToolResult
AsyncCallback = toolshared.AsyncCallback
)
func WithToolContext(ctx context.Context, channel, chatID string) context.Context {
return toolshared.WithToolContext(ctx, channel, chatID)
}
func WithToolInboundContext(
ctx context.Context,
channel, chatID, messageID, replyToMessageID string,
) context.Context {
return toolshared.WithToolInboundContext(ctx, channel, chatID, messageID, replyToMessageID)
}
func WithToolSessionContext(
ctx context.Context,
agentID, sessionKey string,
scope *session.SessionScope,
) context.Context {
return toolshared.WithToolSessionContext(ctx, agentID, sessionKey, scope)
}
func ToolChannel(ctx context.Context) string {
return toolshared.ToolChannel(ctx)
}
func ToolChatID(ctx context.Context) string {
return toolshared.ToolChatID(ctx)
}
func ToolMessageID(ctx context.Context) string {
return toolshared.ToolMessageID(ctx)
}
func ToolAgentID(ctx context.Context) string {
return toolshared.ToolAgentID(ctx)
}
func ToolSessionKey(ctx context.Context) string {
return toolshared.ToolSessionKey(ctx)
}
func ToolSessionScope(ctx context.Context) *session.SessionScope {
return toolshared.ToolSessionScope(ctx)
}
func ErrorResult(message string) *ToolResult {
return toolshared.ErrorResult(message)
}
func SilentResult(forLLM string) *ToolResult {
return toolshared.SilentResult(forLLM)
}
func NewToolResult(forLLM string) *ToolResult {
return toolshared.NewToolResult(forLLM)
}
func UserResult(content string) *ToolResult {
return toolshared.UserResult(content)
}
func MediaResult(forLLM string, mediaRefs []string) *ToolResult {
return toolshared.MediaResult(forLLM, mediaRefs)
}
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"context"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"bytes"
@@ -1,4 +1,4 @@
package tools
package integrationtools
import (
"bytes"
+105
View File
@@ -0,0 +1,105 @@
package tools
import (
"context"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/sipeed/picoclaw/pkg/audio/tts"
"github.com/sipeed/picoclaw/pkg/media"
"github.com/sipeed/picoclaw/pkg/skills"
integrationtools "github.com/sipeed/picoclaw/pkg/tools/integration"
)
type (
SendCallbackWithContext = integrationtools.SendCallbackWithContext
ReactionCallback = integrationtools.ReactionCallback
MCPManager = integrationtools.MCPManager
MCPTool = integrationtools.MCPTool
FindSkillsTool = integrationtools.FindSkillsTool
InstallSkillTool = integrationtools.InstallSkillTool
MessageTool = integrationtools.MessageTool
ReactionTool = integrationtools.ReactionTool
SendTTSTool = integrationtools.SendTTSTool
APIKeyPool = integrationtools.APIKeyPool
APIKeyIterator = integrationtools.APIKeyIterator
SearchProvider = integrationtools.SearchProvider
SearchResultItem = integrationtools.SearchResultItem
BraveSearchProvider = integrationtools.BraveSearchProvider
TavilySearchProvider = integrationtools.TavilySearchProvider
SogouSearchProvider = integrationtools.SogouSearchProvider
DuckDuckGoSearchProvider = integrationtools.DuckDuckGoSearchProvider
PerplexitySearchProvider = integrationtools.PerplexitySearchProvider
SearXNGSearchProvider = integrationtools.SearXNGSearchProvider
GLMSearchProvider = integrationtools.GLMSearchProvider
BaiduSearchProvider = integrationtools.BaiduSearchProvider
WebSearchTool = integrationtools.WebSearchTool
WebSearchToolOptions = integrationtools.WebSearchToolOptions
WebFetchTool = integrationtools.WebFetchTool
)
func NewMCPTool(manager MCPManager, serverName string, tool *mcp.Tool) *MCPTool {
return integrationtools.NewMCPTool(manager, serverName, tool)
}
func NewFindSkillsTool(registryMgr *skills.RegistryManager, cache *skills.SearchCache) *FindSkillsTool {
return integrationtools.NewFindSkillsTool(registryMgr, cache)
}
func NewInstallSkillTool(registryMgr *skills.RegistryManager, workspace string) *InstallSkillTool {
return integrationtools.NewInstallSkillTool(registryMgr, workspace)
}
func NewMessageTool() *MessageTool {
return integrationtools.NewMessageTool()
}
func NewReactionTool() *ReactionTool {
return integrationtools.NewReactionTool()
}
func NewSendTTSTool(provider tts.TTSProvider, store media.MediaStore) *SendTTSTool {
return integrationtools.NewSendTTSTool(provider, store)
}
func NewAPIKeyPool(keys []string) *APIKeyPool {
return integrationtools.NewAPIKeyPool(keys)
}
func SetPreferredWebSearchLanguage(lang string) {
integrationtools.SetPreferredWebSearchLanguage(lang)
}
func GetPreferredWebSearchLanguage() string {
return integrationtools.GetPreferredWebSearchLanguage()
}
func NewWebSearchTool(opts WebSearchToolOptions) (*WebSearchTool, error) {
return integrationtools.NewWebSearchTool(opts)
}
func NewWebFetchTool(maxChars int, format string, fetchLimitBytes int64) (*WebFetchTool, error) {
return integrationtools.NewWebFetchTool(maxChars, format, fetchLimitBytes)
}
func NewWebFetchToolWithProxy(
maxChars int,
proxy string,
format string,
fetchLimitBytes int64,
privateHostWhitelist []string,
) (*WebFetchTool, error) {
return integrationtools.NewWebFetchToolWithProxy(maxChars, proxy, format, fetchLimitBytes, privateHostWhitelist)
}
func NewWebFetchToolWithConfig(
maxChars int,
proxy string,
format string,
fetchLimitBytes int64,
privateHostWhitelist []string,
) (*WebFetchTool, error) {
return integrationtools.NewWebFetchToolWithConfig(maxChars, proxy, format, fetchLimitBytes, privateHostWhitelist)
}
func _keepContext(context.Context) {}
+29
View File
@@ -0,0 +1,29 @@
package tools
import (
"testing"
"github.com/sipeed/picoclaw/pkg/providers"
)
func TestSubagentManager_SetMediaResolver_StoresResolver(t *testing.T) {
manager := NewSubagentManager(nil, "gpt-test", "/tmp")
called := false
manager.SetMediaResolver(func(msgs []providers.Message) []providers.Message {
called = true
return msgs
})
manager.mu.RLock()
got := manager.mediaResolver
manager.mu.RUnlock()
if got == nil {
t.Fatal("expected mediaResolver to be set")
}
if called {
t.Fatal("resolver should not be called during SetMediaResolver")
}
}
+19
View File
@@ -0,0 +1,19 @@
package tools
import (
"regexp"
fstools "github.com/sipeed/picoclaw/pkg/tools/fs"
)
func validatePathWithAllowPaths(
path, workspace string,
restrict bool,
patterns []*regexp.Regexp,
) (string, error) {
return fstools.ValidatePathWithAllowPaths(path, workspace, restrict, patterns)
}
func isAllowedPath(path string, patterns []*regexp.Regexp) bool {
return fstools.IsAllowedPath(path, patterns)
}
-8
View File
@@ -242,11 +242,3 @@ func (sm *SessionManager) List() []SessionInfo {
func generateSessionID() string {
return uuid.New().String()[:8]
}
type SessionInfo struct {
ID string `json:"id"`
Command string `json:"command"`
Status string `json:"status"`
PID int `json:"pid"`
StartedAt int64 `json:"startedAt"`
}
@@ -1,4 +1,4 @@
package tools
package toolshared
import (
"context"
@@ -1,4 +1,4 @@
package tools
package toolshared
import (
"encoding/json"
@@ -1,4 +1,4 @@
package tools
package toolshared
import "context"
@@ -77,3 +77,11 @@ type ExecResponse struct {
Error string `json:"error,omitempty"`
Sessions []SessionInfo `json:"sessions,omitempty"`
}
type SessionInfo struct {
ID string `json:"id"`
Command string `json:"command"`
Status string `json:"status"`
PID int `json:"pid"`
StartedAt int64 `json:"startedAt"`
}
+110
View File
@@ -0,0 +1,110 @@
package tools
import (
"context"
"github.com/sipeed/picoclaw/pkg/session"
toolshared "github.com/sipeed/picoclaw/pkg/tools/shared"
)
type (
Message = toolshared.Message
ToolCall = toolshared.ToolCall
FunctionCall = toolshared.FunctionCall
LLMResponse = toolshared.LLMResponse
UsageInfo = toolshared.UsageInfo
LLMProvider = toolshared.LLMProvider
ToolDefinition = toolshared.ToolDefinition
ToolFunctionDefinition = toolshared.ToolFunctionDefinition
ExecRequest = toolshared.ExecRequest
ExecResponse = toolshared.ExecResponse
SessionInfo = toolshared.SessionInfo
Tool = toolshared.Tool
AsyncCallback = toolshared.AsyncCallback
AsyncExecutor = toolshared.AsyncExecutor
ToolResult = toolshared.ToolResult
)
const (
handledToolLLMNote = "The requested output has already been delivered to the user in the current chat. Do not call send_file or any other delivery tool again. If you reply, provide only a brief confirmation."
artifactPathsLLMNote = "Use `send_file` with one of these paths to send it to the user, or use file/exec tools to save it inside the workspace if requested."
)
func WithToolContext(ctx context.Context, channel, chatID string) context.Context {
return toolshared.WithToolContext(ctx, channel, chatID)
}
func WithToolMessageContext(ctx context.Context, messageID, replyToMessageID string) context.Context {
return toolshared.WithToolMessageContext(ctx, messageID, replyToMessageID)
}
func WithToolInboundContext(
ctx context.Context,
channel, chatID, messageID, replyToMessageID string,
) context.Context {
return toolshared.WithToolInboundContext(ctx, channel, chatID, messageID, replyToMessageID)
}
func WithToolSessionContext(
ctx context.Context,
agentID, sessionKey string,
scope *session.SessionScope,
) context.Context {
return toolshared.WithToolSessionContext(ctx, agentID, sessionKey, scope)
}
func ToolChannel(ctx context.Context) string {
return toolshared.ToolChannel(ctx)
}
func ToolChatID(ctx context.Context) string {
return toolshared.ToolChatID(ctx)
}
func ToolMessageID(ctx context.Context) string {
return toolshared.ToolMessageID(ctx)
}
func ToolReplyToMessageID(ctx context.Context) string {
return toolshared.ToolReplyToMessageID(ctx)
}
func ToolAgentID(ctx context.Context) string {
return toolshared.ToolAgentID(ctx)
}
func ToolSessionKey(ctx context.Context) string {
return toolshared.ToolSessionKey(ctx)
}
func ToolSessionScope(ctx context.Context) *session.SessionScope {
return toolshared.ToolSessionScope(ctx)
}
func ToolToSchema(tool Tool) map[string]any {
return toolshared.ToolToSchema(tool)
}
func NewToolResult(forLLM string) *ToolResult {
return toolshared.NewToolResult(forLLM)
}
func SilentResult(forLLM string) *ToolResult {
return toolshared.SilentResult(forLLM)
}
func AsyncResult(forLLM string) *ToolResult {
return toolshared.AsyncResult(forLLM)
}
func ErrorResult(message string) *ToolResult {
return toolshared.ErrorResult(message)
}
func UserResult(content string) *ToolResult {
return toolshared.UserResult(content)
}
func MediaResult(forLLM string, mediaRefs []string) *ToolResult {
return toolshared.MediaResult(forLLM, mediaRefs)
}