package toolcall import ( "context" "crypto/sha256" "encoding/hex" "errors" "fmt" "strings" "time" ) type DuplicateCompletedPolicy string const ( DuplicateCompletedReturnCached DuplicateCompletedPolicy = "return_cached" DuplicateCompletedFail DuplicateCompletedPolicy = "fail" ) type Runtime struct { Registry *Registry Store Store DefaultTimeout time.Duration Hook Hook Now func() time.Time DuplicateCompletedPolicy DuplicateCompletedPolicy } func (rt *Runtime) Execute(ctx context.Context, req CallRequest) Result { if ctx == nil { ctx = context.Background() } nowFn := rt.Now if nowFn == nil { nowFn = time.Now } start := nowFn() req.CallID = strings.TrimSpace(req.CallID) req.ToolName = strings.TrimSpace(req.ToolName) invalid := validateCallRequest(req) if invalid != nil { res := failureResult(req, start, nowFn(), StatusFailed, invalid, EnvelopeMeta{}) rt.emit(ctx, Event{Type: EventFinish, Request: req, ToolName: req.ToolName, Status: res.Record.Status, ErrorCode: invalid.Code, LatencyMS: res.Envelope.Meta.LatencyMS}) return res } def, ok := rt.lookup(req.ToolName) if !ok { err := &ToolError{Code: ErrorCodeUnknownTool, Message: fmt.Sprintf("unknown tool %q", req.ToolName), Retryable: false} res := failureResult(req, start, nowFn(), StatusFailed, err, EnvelopeMeta{ToolName: req.ToolName}) rt.completeStore(res.Record) rt.emit(ctx, Event{Type: EventFinish, Request: req, ToolName: req.ToolName, Status: res.Record.Status, ErrorCode: err.Code, LatencyMS: res.Envelope.Meta.LatencyMS}) return res } argsHash := hashArgs(req.RawArguments) seed := Record{ CallID: req.CallID, ToolName: def.Name, ArgsHash: argsHash, Status: StatusStarted, StartedAt: start, } if dup, okDup := rt.handleDuplicate(ctx, req, seed, nowFn); okDup { return dup } rt.emit(ctx, Event{ Type: EventStart, Request: req, ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, Status: StatusStarted, }) rt.emit(ctx, Event{Type: EventValidateStart, Request: req, ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, Status: StatusStarted}) validated, err := validateAgainstDefinition(def, req.RawArguments) if err != nil { te := validationToolError(err) end := nowFn() res := failureResult(req, start, end, StatusFailed, te, EnvelopeMeta{ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, ValidationFailed: true}) res.Record.ToolName = def.Name res.Record.ArgsHash = seed.ArgsHash rt.completeStore(res.Record) rt.emit(ctx, Event{Type: EventValidateDone, Request: req, ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, Status: StatusFailed, ErrorCode: te.Code, Message: te.Message}) rt.emit(ctx, Event{Type: EventFinish, Request: req, ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, Status: res.Record.Status, ErrorCode: te.Code, LatencyMS: res.Envelope.Meta.LatencyMS}) return res } rt.emit(ctx, Event{Type: EventValidateDone, Request: req, ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, Status: StatusSucceeded}) timeout := def.Timeout if timeout <= 0 { timeout = rt.DefaultTimeout } execCtx := ctx cancel := func() {} if timeout > 0 { execCtx, cancel = context.WithTimeout(ctx, timeout) } defer cancel() rt.emit(ctx, Event{Type: EventExecuteStart, Request: req, ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, Status: StatusStarted}) data, execErr := rt.callHandler(execCtx, def.Handler, validated) end := nowFn() if execErr != nil { te, status, meta := classifyHandlerError(def, req.CallID, argsHash, start, end, execErr) res := failureResult(req, start, end, status, te, meta) res.Record.ToolName = def.Name res.Record.ArgsHash = seed.ArgsHash rt.completeStore(res.Record) rt.emit(ctx, Event{Type: EventExecuteDone, Request: req, ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, Status: status, ErrorCode: te.Code, LatencyMS: res.Envelope.Meta.LatencyMS, Message: te.Message}) rt.emit(ctx, Event{Type: EventFinish, Request: req, ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, Status: res.Record.Status, ErrorCode: te.Code, LatencyMS: res.Envelope.Meta.LatencyMS}) return res } latency := end.Sub(start).Milliseconds() env := Envelope{ OK: true, Data: data, Meta: EnvelopeMeta{ToolName: def.Name, ToolVersion: def.Version, CallID: req.CallID, ArgsHash: argsHash, LatencyMS: latency}, } rec := Record{ CallID: req.CallID, ToolName: def.Name, ArgsHash: seed.ArgsHash, Status: StatusSucceeded, StartedAt: start, EndedAt: end, Envelope: env, } res := Result{Envelope: env, Record: rec, Validated: validated} rt.completeStore(rec) rt.emit(ctx, Event{Type: EventExecuteDone, Request: req, ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, Status: StatusSucceeded, LatencyMS: latency}) rt.emit(ctx, Event{Type: EventFinish, Request: req, ToolName: def.Name, ToolVersion: def.Version, ArgsHash: argsHash, Status: rec.Status, LatencyMS: latency}) return res } func (rt *Runtime) handleDuplicate(ctx context.Context, req CallRequest, seed Record, nowFn func() time.Time) (Result, bool) { if rt == nil || rt.Store == nil || req.CallID == "" { return Result{}, false } existing, duplicate := rt.Store.Begin(seed) if !duplicate { return Result{}, false } end := nowFn() if existing.ArgsHash != "" && seed.ArgsHash != "" && existing.ArgsHash != seed.ArgsHash { te := &ToolError{ Code: ErrorCodeDuplicateCallConflict, Message: "same call_id received with different arguments", Retryable: false, } res := failureResult(req, seed.StartedAt, end, StatusDuplicate, te, EnvelopeMeta{ ToolName: seed.ToolName, ArgsHash: seed.ArgsHash, Duplicate: true, Conflict: true, }) res.Record.ToolName = seed.ToolName res.Record.ArgsHash = seed.ArgsHash res.Duplicate = true rt.emit(ctx, Event{Type: EventDuplicate, Request: req, ToolName: seed.ToolName, ArgsHash: seed.ArgsHash, Status: StatusDuplicate, ErrorCode: te.Code, Duplicate: true, Message: te.Message}) return res, true } if existing.Status != "" && existing.Status != StatusStarted { if rt.duplicateCompletedPolicy() == DuplicateCompletedFail { te := &ToolError{ Code: ErrorCodeDuplicateCallConflict, Message: "duplicate completed tool call replay is disallowed", Retryable: false, } res := failureResult(req, seed.StartedAt, end, StatusDuplicate, te, EnvelopeMeta{ToolName: seed.ToolName, ArgsHash: seed.ArgsHash, Duplicate: true, Conflict: true}) res.Record.ToolName = seed.ToolName res.Record.ArgsHash = seed.ArgsHash res.Duplicate = true rt.emit(ctx, Event{Type: EventDuplicate, Request: req, ToolName: seed.ToolName, ArgsHash: seed.ArgsHash, Status: StatusDuplicate, ErrorCode: te.Code, Duplicate: true, Message: te.Message}) return res, true } env := existing.Envelope env.Meta.Duplicate = true env.Meta.ReplayCached = true env.Meta.CallID = req.CallID env.Meta.ArgsHash = seed.ArgsHash if env.Meta.ToolName == "" { env.Meta.ToolName = existing.ToolName } latency := end.Sub(seed.StartedAt).Milliseconds() if env.Meta.LatencyMS == 0 { env.Meta.LatencyMS = latency } res := Result{Envelope: env, Record: existing, Duplicate: true} rt.emit(ctx, Event{Type: EventDuplicate, Request: req, ToolName: existing.ToolName, ArgsHash: seed.ArgsHash, Status: StatusDuplicate, Duplicate: true}) return res, true } te := &ToolError{Code: ErrorCodeDuplicateInFlight, Message: "duplicate tool call is already in progress", Retryable: true} res := failureResult(req, seed.StartedAt, end, StatusDuplicate, te, EnvelopeMeta{ToolName: seed.ToolName, ArgsHash: seed.ArgsHash, Duplicate: true}) res.Record.ToolName = seed.ToolName res.Record.ArgsHash = seed.ArgsHash res.Duplicate = true rt.emit(ctx, Event{Type: EventDuplicate, Request: req, ToolName: seed.ToolName, ArgsHash: seed.ArgsHash, Status: StatusDuplicate, ErrorCode: te.Code, Duplicate: true, Message: te.Message}) return res, true } func (rt *Runtime) lookup(name string) (Definition, bool) { if rt == nil || rt.Registry == nil { return Definition{}, false } return rt.Registry.Get(name) } func (rt *Runtime) completeStore(rec Record) { if rt != nil && rt.Store != nil && rec.CallID != "" { rt.Store.Complete(rec) } } func (rt *Runtime) emit(ctx context.Context, ev Event) { if rt != nil && rt.Hook != nil { rt.Hook.OnToolEvent(ctx, ev) } } func validateCallRequest(req CallRequest) *ToolError { if strings.TrimSpace(req.CallID) == "" { return &ToolError{Code: ErrorCodeInvalidRequest, Message: "call_id is required", Retryable: false} } if strings.TrimSpace(req.ToolName) == "" { return &ToolError{Code: ErrorCodeInvalidRequest, Message: "tool_name is required", Retryable: false} } return nil } func validateAgainstDefinition(def Definition, rawArgs string) (map[string]any, error) { if def.InputSchema != nil { return def.InputSchema.ValidateRaw(rawArgs) } return ParseArgsObject(rawArgs) } func validationToolError(err error) *ToolError { if err == nil { return nil } return &ToolError{Code: ErrorCodeValidationError, Message: err.Error(), Retryable: false} } func classifyHandlerError(def Definition, callID string, argsHash string, start, end time.Time, err error) (*ToolError, Status, EnvelopeMeta) { latency := end.Sub(start).Milliseconds() meta := EnvelopeMeta{ToolName: def.Name, ToolVersion: def.Version, CallID: callID, ArgsHash: argsHash, LatencyMS: latency} if errors.Is(err, context.DeadlineExceeded) { meta.TimedOut = true return &ToolError{Code: ErrorCodeTimeout, Message: "tool execution timed out", Retryable: true}, StatusTimedOut, meta } if errors.Is(err, context.Canceled) { meta.Canceled = true return &ToolError{Code: ErrorCodeCanceled, Message: "tool execution canceled", Retryable: true}, StatusCanceled, meta } var te *ToolError if errors.As(err, &te) && te != nil { return te, StatusFailed, meta } return &ToolError{Code: ErrorCodeHandlerError, Message: err.Error(), Retryable: false}, StatusFailed, meta } func failureResult(req CallRequest, start, end time.Time, status Status, err *ToolError, meta EnvelopeMeta) Result { latency := end.Sub(start).Milliseconds() if meta.CallID == "" { meta.CallID = req.CallID } if meta.ToolName == "" { meta.ToolName = req.ToolName } if meta.ArgsHash == "" { meta.ArgsHash = hashArgs(req.RawArguments) } if meta.LatencyMS == 0 { meta.LatencyMS = latency } env := Envelope{OK: false, Data: nil, Error: err, Meta: meta} rec := Record{ CallID: req.CallID, ToolName: req.ToolName, ArgsHash: hashArgs(req.RawArguments), Status: status, StartedAt: start, EndedAt: end, Envelope: env, } if err != nil { rec.ErrorCode = err.Code } return Result{Envelope: env, Record: rec} } func (rt *Runtime) duplicateCompletedPolicy() DuplicateCompletedPolicy { if rt == nil || rt.DuplicateCompletedPolicy == "" { return DuplicateCompletedReturnCached } return rt.DuplicateCompletedPolicy } func (rt *Runtime) callHandler(ctx context.Context, h Handler, input map[string]any) (_ any, err error) { if h == nil { return nil, &ToolError{Code: ErrorCodeInternalError, Message: "tool handler is nil", Retryable: false} } defer func() { if r := recover(); r != nil { err = &ToolError{ Code: ErrorCodePanic, Message: fmt.Sprintf("tool handler panic: %v", r), Retryable: false, } } }() return h(ctx, input) } func hashArgs(raw string) string { sum := sha256.Sum256([]byte(raw)) return hex.EncodeToString(sum[:]) }