StepEnded.ReadOutput takes the inline `output`, else asks the seat's verb output on the node that ran the ask for the object `output-object` names (the runtime hands the chunks as the member `file`, A-249); a step-ended with both is refused. Builds: design 47 G A-254, A-240, A-247 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
61 lines
2.3 KiB
Go
61 lines
2.3 KiB
Go
package buildstep
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// OutputVerb is the verb of a build seat that answers a run's answer kept as an object (design 47 G A-254).
|
|
const OutputVerb = "output"
|
|
|
|
// AskVerb asks a seat verb as the runtime calls it (stdio.Ask): the key `seat:<seat>.<verb>[@<node>]` and the body in, the
|
|
// verb's answer out. A verb marked chunks is asked by the plain call, and the runtime hands the whole file in `file` (A-249).
|
|
type AskVerb func(key string, body any) (json.RawMessage, error)
|
|
|
|
// ReadOutput is the answer a build seat's step-ended carries (design 47 G A-254): its inline `output`, else the object
|
|
// `output-object` names, read through the verb `output` of the seat of language on the node that ran the ask. Every asker of a
|
|
// run reads the answer this way, whichever build seat ran it; none reads the seat's step state.
|
|
func (e StepEnded) ReadOutput(language string, ask AskVerb) ([]byte, error) {
|
|
if e.OutputObject == "" {
|
|
return []byte(e.Output), nil
|
|
}
|
|
seat, err := BuildSeat(language)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ask == nil {
|
|
return nil, errors.New("buildstep: the answer is in the output object " + e.OutputObject + ", which this reader has no verb to read")
|
|
}
|
|
var call [16]byte
|
|
if _, err := rand.Read(call[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
raw, err := ask("seat:"+seat+"."+OutputVerb+"@"+e.Node, map[string]any{"ask": e.Ask, "call": hex.EncodeToString(call[:])})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("buildstep: asking %s for the answer %s of the ask %s: %w", seat, e.OutputObject, e.Ask, err)
|
|
}
|
|
var got struct {
|
|
Ask string `json:"ask"`
|
|
File string `json:"file"`
|
|
Chunks int `json:"chunks"`
|
|
}
|
|
if err := json.Unmarshal(raw, &got); err != nil {
|
|
return nil, fmt.Errorf("buildstep: the verb output of %s answered no answer for %s: %w", seat, e.Ask, err)
|
|
}
|
|
if got.Ask != e.Ask {
|
|
return nil, fmt.Errorf("buildstep: the verb output of %s answered the ask %q, asked %s", seat, got.Ask, e.Ask)
|
|
}
|
|
if got.File == "" {
|
|
return nil, fmt.Errorf("buildstep: %s on %s keeps no answer of the ask %s", seat, e.Node, e.Ask)
|
|
}
|
|
body, err := base64.StdEncoding.DecodeString(got.File)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("buildstep: the answer of %s in no base64: %w", e.Ask, err)
|
|
}
|
|
return body, nil
|
|
}
|