package openai import ( "context" "fmt" "strings" "time" edgeservice "iop/apps/edge/internal/service" ) func collectRunResult(ctx context.Context, stream edgeservice.RunStream, timeout time.Duration) (string, string, *openAIUsage, error) { if stream.Events == nil { return "", "", nil, fmt.Errorf("run stream unavailable") } var contentBuilder strings.Builder var reasoningBuilder strings.Builder var usage *openAIUsage timer := time.NewTimer(timeout) defer timer.Stop() for { select { case <-ctx.Done(): return "", "", nil, ctx.Err() case <-timer.C: return "", "", nil, fmt.Errorf("run timed out") case nodeEvent, ok := <-stream.NodeEvents: if !ok { stream.NodeEvents = nil continue } if edgeservice.IsNodeDisconnected(nodeEvent) { return "", "", nil, fmt.Errorf("node disconnected") } case event, ok := <-stream.Events: if !ok { return "", "", nil, fmt.Errorf("run stream closed") } if event == nil { continue } switch event.GetType() { case "delta": contentBuilder.WriteString(event.GetDelta()) case "reasoning_delta": reasoningBuilder.WriteString(event.GetDelta()) case "complete": if u := event.GetUsage(); u != nil { usage = &openAIUsage{ PromptTokens: int(u.GetInputTokens()), CompletionTokens: int(u.GetOutputTokens()), TotalTokens: int(u.GetInputTokens() + u.GetOutputTokens()), } } return contentBuilder.String(), reasoningBuilder.String(), usage, nil case "error", "cancelled": msg := event.GetError() if msg == "" { msg = event.GetMessage() } if msg == "" { msg = "run failed" } return "", "", nil, fmt.Errorf("%s", msg) } } } }