Add persistent Run control stream
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
@@ -148,6 +149,84 @@ func (c PlatformClient) Heartbeat(ctx context.Context, request protocol.RunHeart
|
||||
return postPlatformJSON[protocol.RunHeartbeatRequest, protocol.RunHeartbeatResponse](ctx, c, "/api/v1/run/control/heartbeat", request)
|
||||
}
|
||||
|
||||
func (c PlatformClient) StreamControlEvents(ctx context.Context, request protocol.RunControlStreamRequest, handle func(protocol.RunControlEvent) error) error {
|
||||
if handle == nil {
|
||||
return fmt.Errorf("control event handler is required")
|
||||
}
|
||||
startedAt := time.Now()
|
||||
path := "/api/v1/run/control/events"
|
||||
log.Printf("RUN platform stream status=starting method=POST base=%s path=%s", diagnosticLogValue(c.baseURL), path)
|
||||
var body bytes.Buffer
|
||||
if err := json.NewEncoder(&body).Encode(request); err != nil {
|
||||
return fmt.Errorf("encode control stream request: %w", err)
|
||||
}
|
||||
httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, &body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("build control stream request: %w", err)
|
||||
}
|
||||
httpRequest.Header.Set("Content-Type", "application/json")
|
||||
httpRequest.Header.Set("Accept", "text/event-stream")
|
||||
signatureSummary, err := signRunRequest(httpRequest, body.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("RUN platform stream status=signed method=POST base=%s path=%s endpoint=%s timestamp=%s nonce=%s bodyHash=%s signature=%s", diagnosticLogValue(c.baseURL), path, diagnosticLogValue(signatureSummary.RunEndpointID), signatureSummary.Timestamp, shortDiagnosticValue(signatureSummary.Nonce), shortDiagnosticValue(signatureSummary.BodyHash), shortDiagnosticValue(signatureSummary.Signature))
|
||||
httpResponse, err := c.httpClient.Do(httpRequest)
|
||||
if err != nil {
|
||||
log.Printf("RUN platform stream status=send_error method=POST base=%s path=%s durationMs=%d error=%s", diagnosticLogValue(c.baseURL), path, time.Since(startedAt).Milliseconds(), err)
|
||||
return fmt.Errorf("send control stream request: %w", err)
|
||||
}
|
||||
defer httpResponse.Body.Close()
|
||||
log.Printf("RUN platform stream status=response method=POST base=%s path=%s httpStatus=%d durationMs=%d", diagnosticLogValue(c.baseURL), path, httpResponse.StatusCode, time.Since(startedAt).Milliseconds())
|
||||
if httpResponse.StatusCode < http.StatusOK || httpResponse.StatusCode >= http.StatusMultipleChoices {
|
||||
var failure struct {
|
||||
Code string `json:"code"`
|
||||
Details []string `json:"details"`
|
||||
}
|
||||
_ = json.NewDecoder(io.LimitReader(httpResponse.Body, 64<<10)).Decode(&failure)
|
||||
return PlatformRequestError{Status: httpResponse.StatusCode, Path: path, Code: failure.Code, Details: failure.Details}
|
||||
}
|
||||
reader := bufio.NewReader(httpResponse.Body)
|
||||
var eventName string
|
||||
var dataLines []string
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil && len(line) == 0 {
|
||||
if errors.Is(err, io.EOF) || ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
return fmt.Errorf("read control stream: %w", err)
|
||||
}
|
||||
line = strings.TrimRight(line, "\r\n")
|
||||
if line == "" {
|
||||
if len(dataLines) > 0 {
|
||||
var event protocol.RunControlEvent
|
||||
if err := json.Unmarshal([]byte(strings.Join(dataLines, "\n")), &event); err != nil {
|
||||
return fmt.Errorf("decode control event: %w", err)
|
||||
}
|
||||
if event.Type == "" {
|
||||
event.Type = eventName
|
||||
}
|
||||
if err := handle(event); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
eventName = ""
|
||||
dataLines = nil
|
||||
} else if strings.HasPrefix(line, "event:") {
|
||||
eventName = strings.TrimSpace(strings.TrimPrefix(line, "event:"))
|
||||
} else if strings.HasPrefix(line, "data:") {
|
||||
dataLines = append(dataLines, strings.TrimSpace(strings.TrimPrefix(line, "data:")))
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) || ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
return fmt.Errorf("read control stream: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c PlatformClient) ReportLifecycle(ctx context.Context, request protocol.RunLifecycleReportRequest) (protocol.RunLifecycleReportResponse, error) {
|
||||
return postPlatformJSON[protocol.RunLifecycleReportRequest, protocol.RunLifecycleReportResponse](ctx, c, "/api/v1/run/lifecycle/report", request)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user