P3: meta + primitive tool group (think/now/cite + classify/extract/summarize)
Grow executus/tools into a real generic tool library: - Register(reg): the always-available, zero-config tools — think, now (UTC unless a CurrentTimeProvider is wired), cite (inert unless a CitationStorage is wired). All nil-safe; a light host calls Register and is useful. - RegisterMeta(reg, MetaDeps): the LLM-backed meta tools — classify, extract_entities, summarize — over the llmmeta helper. Budget defaults to the shipped in-memory per-run cap; Files optional; caps default. - Seams moved (interface/type-only, no host coupling): research_providers.go (CurrentTimeProvider/CitationStorage/SearchBudget/PageExtractor/PDFFetcher/…) and file_storage.go (FileStorage + FileDomainMeta). Plus the in-memory budget default (research_defaults.go) and scope_validate.go. calculate deferred (drags github.com/Krognol/go-wolfram + a module-path replace — not worth it in the lean core for one tool). Core go.sum still free of gorm/redis/discordgo/sqlite/wolfram. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+101
@@ -0,0 +1,101 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/executus/tool"
|
||||
)
|
||||
|
||||
// nowParams is the LLM-facing param struct for current_time / now.
|
||||
//
|
||||
// Why optional `timezone`: most agent prompts know the user's local
|
||||
// timezone (it's in the chatbot's system prompt) but the agent has
|
||||
// no way to override on a per-call basis. An explicit arg lets a
|
||||
// research skill ask "what time is it in NYC for the user reading
|
||||
// this report?" without needing access to a member-config lookup
|
||||
// tool.
|
||||
type nowParams struct {
|
||||
Timezone string `json:"timezone,omitempty" description:"Optional IANA timezone name (e.g. 'America/Chicago', 'Europe/London'). Defaults to the calling user's configured timezone, falling back to UTC."`
|
||||
}
|
||||
|
||||
// nowResponse is the JSON envelope returned to the agent.
|
||||
//
|
||||
// Why a structured shape: the v1 tool returned a markdown blob.
|
||||
// Agents that needed just the year had to substring-parse, which
|
||||
// fails on locale variations. JSON lets the agent pick the field
|
||||
// it cares about.
|
||||
type nowResponse struct {
|
||||
NowISO string `json:"now_iso"`
|
||||
NowHuman string `json:"now_human"`
|
||||
Timezone string `json:"timezone"`
|
||||
Weekday string `json:"weekday"`
|
||||
Year int `json:"year"`
|
||||
Month int `json:"month"`
|
||||
Day int `json:"day"`
|
||||
Hour int `json:"hour"`
|
||||
Minute int `json:"minute"`
|
||||
Second int `json:"second"`
|
||||
Warning string `json:"warning,omitempty"`
|
||||
}
|
||||
|
||||
// NewNow constructs the v11 current_time / now tool. The provider
|
||||
// supplies the calling member's configured timezone (per-user
|
||||
// localisation). nil falls back to UTC.
|
||||
//
|
||||
// V11 keeps the registered tool name "now" for back-compat with the
|
||||
// existing tool catalog tests AND adds the same tool surface under
|
||||
// the agent-facing description "current time". The design spec
|
||||
// called the tool "current_time" but the v1 registry already used
|
||||
// "now" — switching the registry name would break stored skills'
|
||||
// `tools` lists. Same name, expanded behaviour.
|
||||
func NewNow(provider CurrentTimeProvider) tool.Tool {
|
||||
return tool.NewGatedTool[nowParams](
|
||||
"now",
|
||||
"Return the current time. Optional 'timezone' (IANA name e.g. 'America/Chicago'); defaults to the calling user's configured timezone or UTC. Returns ISO + human-readable formats plus structured year/month/day/weekday for time-relative reasoning. Use this BEFORE assuming a year — the agent's knowledge cut-off may differ from real time.",
|
||||
tool.Permission{
|
||||
AuthoringRequirement: tool.RequirementAnyone,
|
||||
OperatesOn: tool.ScopeGlobal,
|
||||
SafeForShare: true,
|
||||
Categories: []string{"utility"},
|
||||
},
|
||||
func(ctx context.Context, inv tool.Invocation, p nowParams) (string, error) {
|
||||
tzName := strings.TrimSpace(p.Timezone)
|
||||
warning := ""
|
||||
if tzName == "" && provider != nil {
|
||||
tzName = provider.UserTimezone(ctx, inv.CallerID)
|
||||
}
|
||||
if tzName == "" {
|
||||
tzName = "UTC"
|
||||
}
|
||||
loc, err := time.LoadLocation(tzName)
|
||||
if err != nil {
|
||||
warning = fmt.Sprintf("unknown timezone %q; falling back to UTC", tzName)
|
||||
tzName = "UTC"
|
||||
loc = time.UTC
|
||||
}
|
||||
t := time.Now().In(loc)
|
||||
out := nowResponse{
|
||||
NowISO: t.Format(time.RFC3339),
|
||||
NowHuman: t.Format("Monday, January 2, 2006 at 3:04 PM MST"),
|
||||
Timezone: tzName,
|
||||
Weekday: t.Weekday().String(),
|
||||
Year: t.Year(),
|
||||
Month: int(t.Month()),
|
||||
Day: t.Day(),
|
||||
Hour: t.Hour(),
|
||||
Minute: t.Minute(),
|
||||
Second: t.Second(),
|
||||
Warning: warning,
|
||||
}
|
||||
b, mErr := json.Marshal(out)
|
||||
if mErr != nil {
|
||||
return "", fmt.Errorf("now: marshal: %w", mErr)
|
||||
}
|
||||
return string(b), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user