mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
162 lines
5.5 KiB
Go
162 lines
5.5 KiB
Go
package toolshared
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/session"
|
|
)
|
|
|
|
// Tool is the interface that all tools must implement.
|
|
type Tool interface {
|
|
Name() string
|
|
Description() string
|
|
Parameters() map[string]any
|
|
Execute(ctx context.Context, args map[string]any) *ToolResult
|
|
}
|
|
|
|
// --- Request-scoped tool context (channel / chatID) ---
|
|
//
|
|
// Carried via context.Value so that concurrent tool calls each receive
|
|
// their own immutable copy — no mutable state on singleton tool instances.
|
|
//
|
|
// Keys are unexported pointer-typed vars — guaranteed collision-free,
|
|
// and only accessible through the helper functions below.
|
|
|
|
type toolCtxKey struct{ name string }
|
|
|
|
var (
|
|
ctxKeyChannel = &toolCtxKey{"channel"}
|
|
ctxKeyChatID = &toolCtxKey{"chatID"}
|
|
ctxKeyMessageID = &toolCtxKey{"messageID"}
|
|
ctxKeyReplyToMessageID = &toolCtxKey{"replyToMessageID"}
|
|
ctxKeyAgentID = &toolCtxKey{"agentID"}
|
|
ctxKeySessionKey = &toolCtxKey{"sessionKey"}
|
|
ctxKeySessionScope = &toolCtxKey{"sessionScope"}
|
|
)
|
|
|
|
// WithToolContext returns a child context carrying channel and chatID.
|
|
func WithToolContext(ctx context.Context, channel, chatID string) context.Context {
|
|
ctx = context.WithValue(ctx, ctxKeyChannel, channel)
|
|
ctx = context.WithValue(ctx, ctxKeyChatID, chatID)
|
|
return ctx
|
|
}
|
|
|
|
// WithToolMessageContext returns a child context carrying inbound message IDs.
|
|
func WithToolMessageContext(ctx context.Context, messageID, replyToMessageID string) context.Context {
|
|
ctx = context.WithValue(ctx, ctxKeyMessageID, messageID)
|
|
ctx = context.WithValue(ctx, ctxKeyReplyToMessageID, replyToMessageID)
|
|
return ctx
|
|
}
|
|
|
|
// WithToolInboundContext returns a child context carrying channel/chat and inbound IDs.
|
|
func WithToolInboundContext(
|
|
ctx context.Context,
|
|
channel, chatID, messageID, replyToMessageID string,
|
|
) context.Context {
|
|
ctx = WithToolContext(ctx, channel, chatID)
|
|
ctx = WithToolMessageContext(ctx, messageID, replyToMessageID)
|
|
return ctx
|
|
}
|
|
|
|
// WithToolSessionContext returns a child context carrying turn-scoped session metadata.
|
|
func WithToolSessionContext(
|
|
ctx context.Context,
|
|
agentID, sessionKey string,
|
|
scope *session.SessionScope,
|
|
) context.Context {
|
|
ctx = context.WithValue(ctx, ctxKeyAgentID, agentID)
|
|
ctx = context.WithValue(ctx, ctxKeySessionKey, sessionKey)
|
|
ctx = context.WithValue(ctx, ctxKeySessionScope, session.CloneScope(scope))
|
|
return ctx
|
|
}
|
|
|
|
// ToolChannel extracts the channel from ctx, or "" if unset.
|
|
func ToolChannel(ctx context.Context) string {
|
|
v, _ := ctx.Value(ctxKeyChannel).(string)
|
|
return v
|
|
}
|
|
|
|
// ToolChatID extracts the chatID from ctx, or "" if unset.
|
|
func ToolChatID(ctx context.Context) string {
|
|
v, _ := ctx.Value(ctxKeyChatID).(string)
|
|
return v
|
|
}
|
|
|
|
// ToolMessageID extracts the current inbound message ID from ctx, or "" if unset.
|
|
func ToolMessageID(ctx context.Context) string {
|
|
v, _ := ctx.Value(ctxKeyMessageID).(string)
|
|
return v
|
|
}
|
|
|
|
// ToolReplyToMessageID extracts the current inbound reply target from ctx, or "" if unset.
|
|
func ToolReplyToMessageID(ctx context.Context) string {
|
|
v, _ := ctx.Value(ctxKeyReplyToMessageID).(string)
|
|
return v
|
|
}
|
|
|
|
// ToolAgentID extracts the active turn's agent ID from ctx, or "" if unset.
|
|
func ToolAgentID(ctx context.Context) string {
|
|
v, _ := ctx.Value(ctxKeyAgentID).(string)
|
|
return v
|
|
}
|
|
|
|
// ToolSessionKey extracts the active turn's session key from ctx, or "" if unset.
|
|
func ToolSessionKey(ctx context.Context) string {
|
|
v, _ := ctx.Value(ctxKeySessionKey).(string)
|
|
return v
|
|
}
|
|
|
|
// ToolSessionScope extracts the active turn's structured session scope from ctx.
|
|
func ToolSessionScope(ctx context.Context) *session.SessionScope {
|
|
scope, _ := ctx.Value(ctxKeySessionScope).(*session.SessionScope)
|
|
return session.CloneScope(scope)
|
|
}
|
|
|
|
// AsyncCallback is a function type that async tools use to notify completion.
|
|
// When an async tool finishes its work, it calls this callback with the result.
|
|
//
|
|
// The ctx parameter allows the callback to be canceled if the agent is shutting down.
|
|
// The result parameter contains the tool's execution result.
|
|
type AsyncCallback func(ctx context.Context, result *ToolResult)
|
|
|
|
// AsyncExecutor is an optional interface that tools can implement to support
|
|
// asynchronous execution with completion callbacks.
|
|
//
|
|
// Unlike the old AsyncTool pattern (SetCallback + Execute), AsyncExecutor
|
|
// receives the callback as a parameter of ExecuteAsync. This eliminates the
|
|
// data race where concurrent calls could overwrite each other's callbacks
|
|
// on a shared tool instance.
|
|
//
|
|
// This is useful for:
|
|
// - Long-running operations that shouldn't block the agent loop
|
|
// - Subagent spawns that complete independently
|
|
// - Background tasks that need to report results later
|
|
//
|
|
// Example:
|
|
//
|
|
// func (t *SpawnTool) ExecuteAsync(ctx context.Context, args map[string]any, cb AsyncCallback) *ToolResult {
|
|
// go func() {
|
|
// result := t.runSubagent(ctx, args)
|
|
// if cb != nil { cb(ctx, result) }
|
|
// }()
|
|
// return AsyncResult("Subagent spawned, will report back")
|
|
// }
|
|
type AsyncExecutor interface {
|
|
Tool
|
|
// ExecuteAsync runs the tool asynchronously. The callback cb will be
|
|
// invoked (possibly from another goroutine) when the async operation
|
|
// completes. cb is guaranteed to be non-nil by the caller (registry).
|
|
ExecuteAsync(ctx context.Context, args map[string]any, cb AsyncCallback) *ToolResult
|
|
}
|
|
|
|
func ToolToSchema(tool Tool) map[string]any {
|
|
return map[string]any{
|
|
"type": "function",
|
|
"function": map[string]any{
|
|
"name": tool.Name(),
|
|
"description": tool.Description(),
|
|
"parameters": tool.Parameters(),
|
|
},
|
|
}
|
|
}
|