Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require (
github.com/go-logr/logr v1.4.4
github.com/go-logr/zapr v1.3.0
github.com/go-playground/assert/v2 v2.2.0
github.com/go-zeromq/zmq4 v0.17.0
github.com/gobwas/glob v1.0.0
github.com/golang-jwt/jwt/v4 v4.5.2
github.com/golang-jwt/jwt/v5 v5.3.1
Expand Down Expand Up @@ -70,6 +71,7 @@ require (
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0
go.opentelemetry.io/otel v1.46.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0
go.opentelemetry.io/otel/metric v1.46.0
go.opentelemetry.io/otel/sdk v1.46.0
go.opentelemetry.io/otel/trace v1.46.0
go.uber.org/dig v1.19.0
Expand Down Expand Up @@ -158,6 +160,7 @@ require (
github.com/go-playground/universal-translator v0.18.2 // indirect
github.com/go-playground/validator/v10 v10.30.5 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/go-zeromq/goczmq/v4 v4.2.2 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
Expand Down Expand Up @@ -228,7 +231,6 @@ require (
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect
go.opentelemetry.io/otel/metric v1.46.0 // indirect
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-zeromq/goczmq/v4 v4.2.2 h1:HAJN+i+3NW55ijMJJhk7oWxHKXgAuSBkoFfvr8bYj4U=
github.com/go-zeromq/goczmq/v4 v4.2.2/go.mod h1:Sm/lxrfxP/Oxqs0tnHD6WAhwkWrx+S+1MRrKzcxoaYE=
github.com/go-zeromq/zmq4 v0.17.0 h1:r12/XdqPeRbuaF4C3QZJeWCt7a5vpJbslDH1rTXF+Kc=
github.com/go-zeromq/zmq4 v0.17.0/go.mod h1:EQxjJD92qKnrsVMzAnx62giD6uJIPi1dMGZ781iCDtY=
github.com/gobwas/glob v1.0.0 h1:p+FKbLEIsK1yZ39/OINwFvqNb5oyPY4H8xcy6uYu8dg=
github.com/gobwas/glob v1.0.0/go.mod h1:oWCdo522i2P1n/hMXGNWs7yoV4wy/ciZuUIbvKj5rkc=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
Expand Down
13 changes: 9 additions & 4 deletions pkg/agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

The `pkg/agent` package contains the Go server used by the Runme web
application. It provides runner and parser services, optional static web
application hosting, authentication, and Jupyter proxying. WebMCP is the
supported integration path for browser automation.
application hosting, authentication, and direct Jupyter kernel management.
WebMCP is the supported integration path for browser automation.

Despite its historical package name, this is not an AI-only server. Runme Web
depends on it for normal notebook and cell execution.
Expand All @@ -18,8 +18,11 @@ The server provides the runtime services required by Runme Web:
- The Runner service manages execution sessions and related runner operations.
- The Parser service parses and serializes notebook content when
`assistantServer.parserService` is enabled.
- The Jupyter proxy manages Jupyter servers and forwards kernel channel
WebSockets.
- The Jupyter kernel API starts client-specified kernels, manages their
lifecycle, and bridges browser channel WebSockets directly to kernel ZeroMQ
sockets. It is available only when both
`assistantServer.jupyterService` and `assistantServer.runnerService` are
enabled. Callers need `RunnerUserRole`; no separate Jupyter Server is used.
- Optional OIDC authentication, authorization, and telemetry are provided by
the same HTTP server.
- A web application can optionally be served from the directory configured by
Expand All @@ -44,6 +47,8 @@ logging:
assistantServer:
port: 8080
runnerService: true
# Opt in to client-selected Jupyter kernel commands. Requires runnerService.
jupyterService: true
parserService: true
# Optional: serve a web application from this directory.
# staticAssets: /absolute/path/to/webapp/dist
Expand Down
6 changes: 6 additions & 0 deletions pkg/agent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ func (ac *AppConfig) GetConfigDir() string {
// IsValid validates the configuration and returns any errors.
func (c *Config) IsValid() []string {
problems := make([]string, 0, 1)
if c.AssistantServer != nil && c.AssistantServer.JupyterService && !c.AssistantServer.RunnerService {
problems = append(problems, "assistantServer.jupyterService requires assistantServer.runnerService")
}

return problems
}
Expand Down Expand Up @@ -472,6 +475,9 @@ type AssistantServerConfig struct {
// RunnerService starts the Runme runner service if true otherwise it doesn't start the runner service.
RunnerService bool `json:"runnerService" yaml:"runnerService"`

// JupyterService starts the direct Jupyter kernel service if true. It is disabled by default and requires RunnerService.
JupyterService bool `json:"jupyterService" yaml:"jupyterService"`

// RunnerReconnect is a flag to enable automatic reconnecting to the runner from the frontend.
RunnerReconnect bool `json:"runnerReconnect,omitempty" yaml:"runnerReconnect,omitempty"`

Expand Down
19 changes: 19 additions & 0 deletions pkg/agent/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,25 @@ func Test_ConfigAutomaticEnvVars(t *testing.T) {
}
}

func Test_ConfigJupyterServiceRequiresRunnerService(t *testing.T) {
cfg := &Config{
AssistantServer: &AssistantServerConfig{JupyterService: true},
}

problems := cfg.IsValid()
if len(problems) != 1 {
t.Fatalf("Expected one validation problem, got %v", problems)
}
if problems[0] != "assistantServer.jupyterService requires assistantServer.runnerService" {
t.Fatalf("Unexpected validation problem: %q", problems[0])
}

cfg.AssistantServer.RunnerService = true
if problems := cfg.IsValid(); len(problems) != 0 {
t.Fatalf("Expected runner-backed Jupyter service configuration to be valid, got %v", problems)
}
}

func Test_DeprecatedAgentConfigurationIsAccepted(t *testing.T) {
v := viper.New()
v.SetConfigType("yaml")
Expand Down
7 changes: 3 additions & 4 deletions pkg/agent/iam/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,9 @@ func NewAuthMiddlewareForOIDC(oidc *OIDC) (func(http.Handler) http.Handler, erro
// Prefer bearer token over session cookie.
// Browser WebSocket clients cannot set custom Authorization headers, so
// the Jupyter channels client sends `?authorization=Bearer ...` during
// the handshake. We use query auth for the Jupyter proxy because we want
// to preserve the kernel protocol payload as-is, instead of introducing
// a Runme-style in-band auth envelope in websocket messages.
// The Jupyter proxy strips this param before forwarding upstream.
// the handshake. We use query auth for the direct Jupyter bridge to
// preserve the kernel protocol payload as-is instead of introducing a
// Runme-style in-band auth envelope in WebSocket messages.
bearerToken := r.Header.Get("Authorization")
if !strings.HasPrefix(strings.ToLower(strings.TrimSpace(bearerToken)), "bearer ") {
queryAuthorization := r.URL.Query().Get("authorization")
Expand Down
Loading
Loading