bridge

package
v0.61.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 20, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ExecMethodInitialize       = "initialize"
	ExecMethodInitialized      = "initialized" // notification
	ExecMethodProcessStart     = "process/start"
	ExecMethodProcessRead      = "process/read"
	ExecMethodProcessWrite     = "process/write"
	ExecMethodProcessTerminate = "process/terminate"
	ExecMethodProcessExited    = "process/exited" // notification (informational; we poll instead)
	ExecMethodProcessClosed    = "process/closed" // notification (informational)
	ExecMethodFsReadFile       = "fs/readFile"
	ExecMethodFsWriteFile      = "fs/writeFile"
	ExecMethodFsRemove         = "fs/remove"
	ExecMethodFsCopy           = "fs/copy"
)

Method names — must match codex-rs/exec-server/src/protocol.rs.

Variables

View Source
var ErrRelayDisabled = fmt.Errorf("relay: HTTP relay path disabled (no exec-gateway-internal-url or secret)")

ErrRelayDisabled is returned by CreateRelay when the gateway URL or internal secret is empty (config-disabled).

Functions

This section is empty.

Types

type BridgeClient

type BridgeClient struct {
	// contains filtered or unexported fields
}

BridgeClient wraps one WebSocket connection to /bridge/{exe_id} on codex-exec-gateway and exposes a JSON-RPC client interface that env-mcp uses to talk codex's exec-server protocol.

Concurrency model: a single background goroutine reads frames and dispatches them to a per-id reply channel; Call() blocks on its channel until the goroutine delivers, the context is cancelled, or the connection closes.

func DialBridge

func DialBridge(ctx context.Context, wsURL, authToken string, logger *slog.Logger) (*BridgeClient, error)

DialBridge dials wsURL and, when authToken is non-empty, sets `Authorization: Bearer <authToken>` on the upgrade request. Returns once the WebSocket handshake completes; subsequent reads are pumped by a background goroutine.

nhooyr.io/websocket does NOT request `permessage-deflate` by default — we rely on that, because codex's exec-server closes connections that do (see spec § PoC #2 gotchas).

func (*BridgeClient) Call

func (bc *BridgeClient) Call(ctx context.Context, method string, params json.RawMessage) (json.RawMessage, error)

Call sends a JSON-RPC request and blocks until the response arrives, the context is cancelled, or the connection closes.

func (*BridgeClient) Close

func (bc *BridgeClient) Close()

Close shuts the connection. Safe to call repeatedly; first call wins.

func (*BridgeClient) Notify

func (bc *BridgeClient) Notify(ctx context.Context, method string, params json.RawMessage) error

Notify sends a JSON-RPC notification (no id, no reply expected).

type ExecInitializeParams

type ExecInitializeParams struct {
	ClientName      string  `json:"clientName"`
	ResumeSessionID *string `json:"resumeSessionId,omitempty"`
}

ExecInitializeParams matches codex-rs's InitializeParams (camelCase).

type ExecInitializeResult

type ExecInitializeResult struct {
	SessionID string `json:"sessionId"`
}

type FsCopyParams

type FsCopyParams struct {
	SourcePath      string `json:"sourcePath"`
	DestinationPath string `json:"destinationPath"`
	Recursive       bool   `json:"recursive,omitempty"`
}

FsCopyParams is the request body for fs/copy.

type FsReadFileParams

type FsReadFileParams struct {
	Path string `json:"path"`
}

FsReadFileParams is the request body for fs/readFile.

type FsReadFileResult

type FsReadFileResult struct {
	DataBase64 string `json:"dataBase64"`
}

FsReadFileResult: dataBase64 is the file's full content (codex returns the entire file; we expose offset/limit slicing in the MCP tool wrapper).

type FsRemoveParams

type FsRemoveParams struct {
	Path      string `json:"path"`
	Recursive bool   `json:"recursive,omitempty"`
}

FsRemoveParams is the request body for fs/remove.

type FsWriteFileParams

type FsWriteFileParams struct {
	Path       string `json:"path"`
	DataBase64 string `json:"dataBase64"`
	// CreateMissing controls whether intermediate directories are
	// created. Codex's default is true.
	CreateMissing bool `json:"createMissing,omitempty"`
}

FsWriteFileParams is the request body for fs/writeFile.

type JSONRPCError

type JSONRPCError struct {
	Code    int             `json:"code"`
	Message string          `json:"message"`
	Data    json.RawMessage `json:"data,omitempty"`
}

type JSONRPCMessage

type JSONRPCMessage struct {
	JSONRPC string          `json:"jsonrpc"`
	ID      *int64          `json:"id,omitempty"`
	Method  string          `json:"method,omitempty"`
	Params  json.RawMessage `json:"params,omitempty"`
	Result  json.RawMessage `json:"result,omitempty"`
	Error   *JSONRPCError   `json:"error,omitempty"`
}

JSONRPCMessage is the JSON-RPC 2.0 envelope shared by both MCP (over stdio) and exec-server (over ws). The ID field is a pointer so notifications (which have no ID) marshal cleanly without the field.

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool maintains one BridgeClient per exe_id, dialed lazily on first Get and reused across calls. Closed connections (detected via the BridgeClient's `closed` channel) are dropped and redialed transparently. Used by env-mcp tools to multiplex multiple executor targets behind one stdio MCP server.

func NewPool

func NewPool(gatewayBaseURL, token string, logger *slog.Logger) *Pool

NewPool returns a pool. gatewayBaseURL should be the prefix to which `/<exe_id>` is appended (i.e. include `/bridge` but no trailing slash); token is the workspace-scoped capability token issued for this turn.

func (*Pool) Close

func (p *Pool) Close()

Close shuts down every pooled connection. Idempotent.

func (*Pool) Get

func (p *Pool) Get(ctx context.Context, exeID string) (*BridgeClient, error)

Get returns a live BridgeClient for exeID. Dials on first use (with codex exec-server `initialize` handshake performed in-band so the caller can start issuing process/* and fs/* calls immediately). On subsequent calls, returns the cached client if it's still open.

Race-safety: dial happens outside the pool lock so a slow dial for exe_a doesn't block Get(exe_b). If two goroutines race to dial the same exe_id, both will dial but only one connection ends up in the map; the loser closes its connection and returns the winner's.

type ProcessOutputChunk

type ProcessOutputChunk struct {
	Seq    uint64 `json:"seq"`
	Stream string `json:"stream"` // "stdout" | "stderr"
	Chunk  string `json:"chunk"`
}

ProcessOutputChunk: chunk is base64-encoded raw bytes (per codex's ByteChunk wrapper that uses serde_with for base64 encoding).

type ProcessReadParams

type ProcessReadParams struct {
	ProcessID string `json:"processId"`
	AfterSeq  uint64 `json:"afterSeq"`
	MaxBytes  int    `json:"maxBytes"`
	WaitMs    int    `json:"waitMs"`
}

type ProcessReadResult

type ProcessReadResult struct {
	Chunks   []ProcessOutputChunk `json:"chunks"`
	NextSeq  uint64               `json:"nextSeq"`
	Exited   bool                 `json:"exited"`
	ExitCode *int                 `json:"exitCode"`
	Closed   bool                 `json:"closed"`
	Failure  *string              `json:"failure"`
}

type ProcessStartParams

type ProcessStartParams struct {
	ProcessID string            `json:"processId"`
	Argv      []string          `json:"argv"`
	Cwd       string            `json:"cwd"`
	Env       map[string]string `json:"env"`
	TTY       bool              `json:"tty"`
	PipeStdin bool              `json:"pipeStdin"`
	Arg0      *string           `json:"arg0"`
}

type ProcessStartResult

type ProcessStartResult struct {
	ProcessID string `json:"processId"`
}

type ProcessTerminateParams

type ProcessTerminateParams struct {
	ProcessID string `json:"processId"`
}

ProcessTerminateParams is the request body for process/terminate.

type ProcessWriteParams

type ProcessWriteParams struct {
	ProcessID string `json:"processId"`
	Chunk     string `json:"chunk"` // base64 raw bytes
}

ProcessWriteParams is the request body for process/write. The `chunk` field name matches upstream codex's WriteParams (see codex-rs/exec-server/src/protocol.rs); writing with `data` here would 400 with "missing field `chunk`".

type RelayClient

type RelayClient struct {
	// contains filtered or unexported fields
}

RelayClient mints HTTPS relay tickets on the codex-exec-gateway's /api/exec-gateway/relay/create endpoint. Used by CopyPathTool to route file bytes around the bridge ws path.

nil-safe: when ExecGatewayInternalURL or InternalSecret is empty, CreateRelay returns a sentinel error and the caller falls back to the ws cat-pump path.

func NewRelayClient

func NewRelayClient(baseURL, secret, workspaceID string, logger *slog.Logger) *RelayClient

func (*RelayClient) CreateRelay

func (c *RelayClient) CreateRelay(ctx context.Context, srcExeID, dstExeID string, ttl time.Duration, maxBytes int64) (*RelayTicket, error)

CreateRelay mints a ticket good for one PUT + one GET on the gateway's /relay/<ticket> endpoint.

func (*RelayClient) Enabled

func (c *RelayClient) Enabled() bool

Enabled reports whether the relay path can be used (both URL + secret present).

type RelayTicket

type RelayTicket struct {
	Ticket      string    `json:"ticket"`
	UploadURL   string    `json:"upload_url"`
	DownloadURL string    `json:"download_url"`
	ExpiresAt   time.Time `json:"expires_at"`
}

RelayTicket is the gateway's response to relay/create.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL