Files
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

264 lines
7.8 KiB
Go

// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package opensandbox
import (
"encoding/json"
"strconv"
"strings"
)
// OutputMessage represents a single stdout or stderr line from command execution.
type OutputMessage struct {
Text string `json:"text"`
Timestamp int64 `json:"timestamp"`
}
// ExecutionResult represents a result output from code execution.
// Results maps MIME types to their string representations (e.g. "text/plain" → "4").
type ExecutionResult struct {
// Results holds MIME-type keyed outputs (e.g. "text/plain", "text/html").
Results map[string]string `json:"results,omitempty"`
Timestamp int64 `json:"timestamp"`
}
// Text returns the text/plain result, or empty string if not present.
func (r ExecutionResult) Text() string {
return r.Results["text/plain"]
}
// ExecutionError represents an error during code/command execution.
type ExecutionError struct {
Name string `json:"name"`
Value string `json:"value"`
Timestamp int64 `json:"timestamp"`
Traceback []string `json:"traceback"`
}
// ExecutionComplete represents the completion event from the server.
type ExecutionComplete struct {
Timestamp int64 `json:"timestamp"`
ExecutionTime int64 `json:"execution_time"`
}
// ExecutionInit represents the initialization event from the server.
type ExecutionInit struct {
ID string `json:"text"`
Timestamp int64 `json:"timestamp"`
}
// Execution is the structured result of a command or code execution.
type Execution struct {
// ID is the execution/command identifier from the init event.
ID string
// Stdout contains all stdout messages in order.
Stdout []OutputMessage
// Stderr contains all stderr messages in order.
Stderr []OutputMessage
// Results contains execution results (for code interpreter).
Results []ExecutionResult
// Error is set if the execution failed.
Error *ExecutionError
// Complete is set when execution finishes.
Complete *ExecutionComplete
// ExitCode is the process exit code. Nil if not available.
ExitCode *int
}
// Text returns the combined stdout text.
func (e *Execution) Text() string {
var b strings.Builder
for i, m := range e.Stdout {
if i > 0 {
b.WriteByte('\n')
}
b.WriteString(m.Text)
}
return b.String()
}
// ExecutionHandlers provides optional callbacks invoked during streaming execution.
// Return a non-nil error from any handler to abort the stream.
type ExecutionHandlers struct {
OnInit func(ExecutionInit) error
OnStdout func(OutputMessage) error
OnStderr func(OutputMessage) error
OnResult func(ExecutionResult) error
OnComplete func(ExecutionComplete) error
OnError func(ExecutionError) error
// SkipAccumulation, when true, prevents stdout/stderr messages from being
// accumulated in the Execution struct. Messages are still delivered to handlers.
// Use for long-running executions to prevent unbounded memory growth.
SkipAccumulation bool
}
// sseErrorPayload is the nested error object in a ServerStreamEvent.
type sseErrorPayload struct {
EName string `json:"ename,omitempty"`
EValue string `json:"evalue,omitempty"`
Traceback []string `json:"traceback,omitempty"`
}
// sseEvent is the raw JSON structure from execd SSE/NDJSON events.
// Supports both the spec format (nested error/results objects) and the
// legacy flat format (top-level ename/evalue/traceback) for backward compat.
type sseEvent struct {
Type string `json:"type"`
Text string `json:"text"`
Timestamp int64 `json:"timestamp"`
ExitCode *int `json:"exit_code,omitempty"`
ExecutionTime int64 `json:"execution_time,omitempty"`
// Nested error object (spec: {"type":"error","error":{...}})
Error *sseErrorPayload `json:"error,omitempty"`
// Nested results object (spec: {"type":"result","results":{"text/plain":"..."}})
Results map[string]string `json:"results,omitempty"`
// Flat error fields kept for backward compatibility with older servers
EName string `json:"ename,omitempty"`
EValue string `json:"evalue,omitempty"`
Traceback []string `json:"traceback,omitempty"`
}
// processStreamEvent parses a raw StreamEvent into the Execution accumulator
// and invokes the appropriate handler.
func processStreamEvent(exec *Execution, event StreamEvent, handlers *ExecutionHandlers) error {
data := event.Data
if data == "" {
return nil
}
var ev sseEvent
if err := json.Unmarshal([]byte(data), &ev); err != nil {
// Not JSON — treat as raw stdout
msg := OutputMessage{Text: data}
if handlers == nil || !handlers.SkipAccumulation {
exec.Stdout = append(exec.Stdout, msg)
}
if handlers != nil && handlers.OnStdout != nil {
return handlers.OnStdout(msg)
}
return nil
}
switch ev.Type {
case "init":
initEvent := ExecutionInit{ID: ev.Text, Timestamp: ev.Timestamp}
exec.ID = ev.Text
if handlers != nil && handlers.OnInit != nil {
return handlers.OnInit(initEvent)
}
case "stdout":
msg := OutputMessage{Text: ev.Text, Timestamp: ev.Timestamp}
if handlers == nil || !handlers.SkipAccumulation {
exec.Stdout = append(exec.Stdout, msg)
}
if handlers != nil && handlers.OnStdout != nil {
return handlers.OnStdout(msg)
}
case "stderr":
msg := OutputMessage{Text: ev.Text, Timestamp: ev.Timestamp}
if handlers == nil || !handlers.SkipAccumulation {
exec.Stderr = append(exec.Stderr, msg)
}
if handlers != nil && handlers.OnStderr != nil {
return handlers.OnStderr(msg)
}
case "result":
res := ExecutionResult{Timestamp: ev.Timestamp}
if ev.Results != nil {
// Spec format: MIME-keyed map under "results"
res.Results = ev.Results
} else if ev.Text != "" {
// Legacy flat format: bare "text" field
res.Results = map[string]string{"text/plain": ev.Text}
}
exec.Results = append(exec.Results, res)
if handlers != nil && handlers.OnResult != nil {
return handlers.OnResult(res)
}
case "error":
var ename, evalue string
var traceback []string
// Prefer nested error object per spec; fall back to flat fields
if ev.Error != nil {
ename = ev.Error.EName
evalue = ev.Error.EValue
traceback = ev.Error.Traceback
} else {
ename = ev.EName
evalue = ev.EValue
traceback = ev.Traceback
}
execErr := ExecutionError{
Name: ename,
Value: evalue,
Timestamp: ev.Timestamp,
Traceback: traceback,
}
exec.Error = &execErr
// Try to parse exit code from error value
if code, err := strconv.Atoi(evalue); err == nil {
exec.ExitCode = &code
}
if handlers != nil && handlers.OnError != nil {
return handlers.OnError(execErr)
}
case "execution_complete":
complete := ExecutionComplete{
Timestamp: ev.Timestamp,
ExecutionTime: ev.ExecutionTime,
}
exec.Complete = &complete
// Foreground command exit code: 0 if no error
if exec.ExitCode == nil && exec.Error == nil {
zero := 0
exec.ExitCode = &zero
}
if handlers != nil && handlers.OnComplete != nil {
return handlers.OnComplete(complete)
}
case "ping":
// Ignore ping events
default:
// Unknown event type — treat as stdout
if ev.Text != "" {
msg := OutputMessage{Text: ev.Text, Timestamp: ev.Timestamp}
if handlers == nil || !handlers.SkipAccumulation {
exec.Stdout = append(exec.Stdout, msg)
}
if handlers != nil && handlers.OnStdout != nil {
return handlers.OnStdout(msg)
}
}
}
return nil
}