The puller's end of a run carries the run's program's last line of standard output as `output` up to StepEndedInlineMost, else keeps it with KeepOutput and names the object in `output-object`; a program that exits other than 0 ends failed. OutputTool serves the seat's verb `output` from the kept object, as the whole file the tool runner sends in chunks; OutputsStore names the store. Builds: design 47 G A-254, A-257, §602 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
83 lines
4.1 KiB
Go
83 lines
4.1 KiB
Go
package steprun
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
|
|
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// SeatTools are a build seat's verbs on one node, as its holder serves them (novox/hq ADR 0525 §10; mesh-sdk catalogue
|
|
// buildSeatVerbs): `<seat>.current` (what the node runs, its capacity, whether it is paused), `<seat>.pause` and
|
|
// `<seat>.resume`, the pause kept on the node's pause key by the puller. what names the asks in the descriptions ("Go").
|
|
func SeatTools(seat, what string, p *Puller) []stdio.Tool {
|
|
verb := func(name, description string, run func() (any, error)) stdio.Tool {
|
|
return stdio.Tool{Name: seat + "." + name, Description: description,
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{}},
|
|
Run: func(map[string]any) (any, error) { return run() }}
|
|
}
|
|
current := func() (any, error) {
|
|
c := p.CapacityDetail()
|
|
return map[string]any{"node": p.Node, "capacity": c.Effective, "capacity_why": c.Why, "capacity_detail": c,
|
|
"running": p.Running(), "paused": p.Paused()}, nil
|
|
}
|
|
drop := stdio.Tool{Name: seat + ".drop", Description: "End an " + what + " ask its caller no longer waits for: it is kept as dropped on this node " +
|
|
"(so a bus that gives it again has it refused), and a step running here for it is ended, written refused naming who dropped it and why.",
|
|
Input: map[string]any{"type": "object", "required": []string{"id", "why"}, "properties": map[string]any{
|
|
"id": map[string]any{"type": "string", "description": "the ask's id"},
|
|
"by": map[string]any{"type": "string", "description": "who drops it"},
|
|
"why": map[string]any{"type": "string", "description": "why the caller no longer waits"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
str := func(k string) string { s, _ := args[k].(string); return s }
|
|
if err := p.Drop(str("id"), str("by"), str("why")); err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string]any{}, nil
|
|
}}
|
|
return []stdio.Tool{drop,
|
|
verb("current", "The "+what+" asks this node runs now (each one's id, kind and when it started), its build capacity (the configured one, "+
|
|
"bounded by the cores its node's build slice gives over the cores a step needs) and why, "+
|
|
"and whether it is paused.", current),
|
|
verb("pause", "Take no new "+what+" ask on this node until resumed; an ask running here ends in its place. Kept "+
|
|
"across a restart (the node's pause key).", func() (any, error) {
|
|
if err := p.Pause("the seat's pause verb"); err != nil {
|
|
return nil, err
|
|
}
|
|
return current()
|
|
}),
|
|
verb("resume", "Take "+what+" asks again on this node.", func() (any, error) {
|
|
if err := p.Resume("the seat's resume verb"); err != nil {
|
|
return nil, err
|
|
}
|
|
return current()
|
|
}),
|
|
}
|
|
}
|
|
|
|
// OutputTool serves `<seat>.output` (design 47 G A-254, A-257): a run's answer by its ask, as the object the holder keeps it in
|
|
// (sdkstep.OutputObjectName), read with read, which answers nil for none. The verb's answer is marked chunks: the bundle answers
|
|
// the whole answer as the member `file` (base64) and the tool runner sends it in chunks and answers their count; an ask
|
|
// whose answer this node does not keep answers no chunks.
|
|
func OutputTool(seat string, read func(object string) ([]byte, error)) stdio.Tool {
|
|
return stdio.Tool{Name: seat + ".output",
|
|
Description: "A run's answer by its ask, when its step-ended named it as an output-object: the answer's bytes in chunks, of any size.",
|
|
Input: map[string]any{"type": "object", "required": []string{"ask", "call"}, "properties": map[string]any{
|
|
"ask": map[string]any{"type": "string", "description": "the ask's id"},
|
|
"call": map[string]any{"type": "string", "description": "the subject token of this send's chunks"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
ask, _ := args["ask"].(string)
|
|
if ask == "" {
|
|
return nil, errors.New("the verb output is asked for no ask")
|
|
}
|
|
body, err := read(sdkstep.OutputObjectName(ask))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if body == nil {
|
|
return map[string]any{"ask": ask, "chunks": 0}, nil
|
|
}
|
|
return map[string]any{"ask": ask, "file": base64.StdEncoding.EncodeToString(body)}, nil
|
|
}}
|
|
}
|