chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
// Copyright 2025 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/opensandbox/execd/pkg/flag"
|
||||
"github.com/alibaba/opensandbox/execd/pkg/jupyter/execute"
|
||||
"github.com/alibaba/opensandbox/execd/pkg/runtime"
|
||||
"github.com/alibaba/opensandbox/execd/pkg/telemetry"
|
||||
"github.com/alibaba/opensandbox/execd/pkg/web/model"
|
||||
)
|
||||
|
||||
// RunCommand executes a shell command and streams the output via SSE.
|
||||
func (c *CodeInterpretingController) RunCommand() {
|
||||
var request model.RunCommandRequest
|
||||
if err := c.bindJSON(&request); err != nil {
|
||||
c.RespondError(
|
||||
http.StatusBadRequest,
|
||||
model.ErrorCodeInvalidRequest,
|
||||
fmt.Sprintf("error parsing request, MAYBE invalid body format. %v", err),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
err := request.Validate()
|
||||
if err != nil {
|
||||
c.RespondError(
|
||||
http.StatusBadRequest,
|
||||
model.ErrorCodeInvalidRequest,
|
||||
fmt.Sprintf("invalid request, validation error %v", err),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(c.ctx.Request.Context())
|
||||
defer cancel()
|
||||
execStart := time.Now()
|
||||
var recordOnce sync.Once
|
||||
recordExecution := func(result string) {
|
||||
recordOnce.Do(func() {
|
||||
telemetry.RecordExecutionDuration(
|
||||
ctx,
|
||||
"run_command",
|
||||
result,
|
||||
float64(time.Since(execStart))/float64(time.Millisecond),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
runCodeRequest := c.buildExecuteCommandRequest(request)
|
||||
eventsHandler := c.setServerEventsHandler(ctx)
|
||||
origComplete := eventsHandler.OnExecuteComplete
|
||||
eventsHandler.OnExecuteComplete = func(executionTime time.Duration) {
|
||||
origComplete(executionTime)
|
||||
recordExecution("success")
|
||||
}
|
||||
origError := eventsHandler.OnExecuteError
|
||||
eventsHandler.OnExecuteError = func(err *execute.ErrorOutput) {
|
||||
origError(err)
|
||||
recordExecution("failure")
|
||||
}
|
||||
runCodeRequest.Hooks = eventsHandler
|
||||
|
||||
// SSE headers are committed lazily on the first event write
|
||||
// (see writeSingleEvent), so a synchronous error from Execute below can
|
||||
// still be surfaced as a structured JSON error response.
|
||||
err = codeRunner.Execute(runCodeRequest)
|
||||
if err != nil {
|
||||
recordExecution("failure")
|
||||
c.RespondError(
|
||||
http.StatusInternalServerError,
|
||||
model.ErrorCodeRuntimeError,
|
||||
fmt.Sprintf("error running commands %v", err),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(flag.ApiGracefulShutdownTimeout)
|
||||
}
|
||||
|
||||
// InterruptCommand stops a running shell command session.
|
||||
func (c *CodeInterpretingController) InterruptCommand() {
|
||||
c.interrupt()
|
||||
}
|
||||
|
||||
// GetCommandStatus returns command status by id.
|
||||
func (c *CodeInterpretingController) GetCommandStatus() {
|
||||
commandID := c.ctx.Param("id")
|
||||
if commandID == "" {
|
||||
c.RespondError(http.StatusBadRequest, model.ErrorCodeInvalidRequest, "missing command execution id")
|
||||
return
|
||||
}
|
||||
|
||||
status, err := codeRunner.GetCommandStatus(commandID)
|
||||
if err != nil {
|
||||
c.RespondError(http.StatusNotFound, model.ErrorCodeInvalidRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := model.CommandStatusResponse{
|
||||
ID: status.Session,
|
||||
Running: status.Running,
|
||||
ExitCode: status.ExitCode,
|
||||
Error: status.Error,
|
||||
Content: status.Content,
|
||||
}
|
||||
if !status.StartedAt.IsZero() {
|
||||
resp.StartedAt = status.StartedAt
|
||||
}
|
||||
if status.FinishedAt != nil {
|
||||
resp.FinishedAt = status.FinishedAt
|
||||
}
|
||||
|
||||
c.RespondSuccess(resp)
|
||||
}
|
||||
|
||||
// GetBackgroundCommandOutput returns accumulated stdout/stderr for a command session as plain text.
|
||||
func (c *CodeInterpretingController) GetBackgroundCommandOutput() {
|
||||
id := c.ctx.Param("id")
|
||||
if id == "" {
|
||||
c.RespondError(http.StatusBadRequest, model.ErrorCodeMissingQuery, "missing command execution id")
|
||||
return
|
||||
}
|
||||
|
||||
cursor := c.QueryInt64(c.ctx.Query("cursor"), 0)
|
||||
output, lastCursor, err := codeRunner.SeekBackgroundCommandOutput(id, cursor)
|
||||
if err != nil {
|
||||
c.RespondError(http.StatusBadRequest, model.ErrorCodeInvalidRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ctx.Header("EXECD-COMMANDS-TAIL-CURSOR", strconv.FormatInt(lastCursor, 10))
|
||||
c.ctx.Header("Content-Type", "text/plain; charset=utf-8")
|
||||
c.ctx.String(http.StatusOK, "%s", output)
|
||||
}
|
||||
|
||||
func (c *CodeInterpretingController) buildExecuteCommandRequest(request model.RunCommandRequest) *runtime.ExecuteCodeRequest {
|
||||
timeout := time.Duration(request.TimeoutMs) * time.Millisecond
|
||||
if request.Background {
|
||||
return &runtime.ExecuteCodeRequest{
|
||||
Language: runtime.BackgroundCommand,
|
||||
Code: request.Command,
|
||||
Cwd: request.Cwd,
|
||||
Timeout: timeout,
|
||||
Gid: request.Gid,
|
||||
Uid: request.Uid,
|
||||
Envs: request.Envs,
|
||||
}
|
||||
} else {
|
||||
return &runtime.ExecuteCodeRequest{
|
||||
Language: runtime.Command,
|
||||
Code: request.Command,
|
||||
Cwd: request.Cwd,
|
||||
Timeout: timeout,
|
||||
Gid: request.Gid,
|
||||
Uid: request.Uid,
|
||||
Envs: request.Envs,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user