Add daemon client protocol frames
This commit is contained in:
164
internal/protocol/frame.go
Normal file
164
internal/protocol/frame.go
Normal file
@@ -0,0 +1,164 @@
|
||||
// Package protocol defines the daemon/client control frames shared by
|
||||
// transports. It intentionally contains data shapes only; app behavior stays
|
||||
// in internal/app until the headless daemon split is complete.
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FrameType identifies one protocol message kind.
|
||||
type FrameType string
|
||||
|
||||
const (
|
||||
FrameHello FrameType = "hello"
|
||||
FrameAuthChallenge FrameType = "auth_challenge"
|
||||
FrameAuthOK FrameType = "auth_ok"
|
||||
FrameAttach FrameType = "attach"
|
||||
FrameDetach FrameType = "detach"
|
||||
FrameProjectList FrameType = "project_list"
|
||||
FrameChrome FrameType = "chrome"
|
||||
FramePaneSnapshot FrameType = "pane_snapshot"
|
||||
FramePaneChunk FrameType = "pane_chunk"
|
||||
FrameLifecycle FrameType = "lifecycle"
|
||||
FrameAttention FrameType = "attention"
|
||||
FrameTrustPrompt FrameType = "trust_prompt"
|
||||
FrameInput FrameType = "input"
|
||||
FrameFocus FrameType = "focus"
|
||||
FrameSwitchProject FrameType = "switch_project"
|
||||
FrameOpenProject FrameType = "open_project"
|
||||
FramePaletteCommand FrameType = "palette_command"
|
||||
FrameTrustResponse FrameType = "trust_response"
|
||||
FrameResize FrameType = "resize"
|
||||
)
|
||||
|
||||
// Frame is the transport envelope. Payload is deliberately raw JSON so
|
||||
// network transports can frame without knowing every message type; loopback
|
||||
// transports may pass the same bytes without JSON re-encoding.
|
||||
type Frame struct {
|
||||
Type FrameType `json:"type"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
Payload json.RawMessage `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
// NewFrame marshals payload into a protocol frame.
|
||||
func NewFrame[T any](typ FrameType, payload T) (Frame, error) {
|
||||
b, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return Frame{}, fmt.Errorf("protocol: marshal %s: %w", typ, err)
|
||||
}
|
||||
return Frame{Type: typ, Payload: b}, nil
|
||||
}
|
||||
|
||||
// Decode unmarshals f.Payload into v.
|
||||
func Decode[T any](f Frame) (T, error) {
|
||||
var v T
|
||||
if len(f.Payload) == 0 {
|
||||
return v, nil
|
||||
}
|
||||
if err := json.Unmarshal(f.Payload, &v); err != nil {
|
||||
return v, fmt.Errorf("protocol: decode %s: %w", f.Type, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type Hello struct {
|
||||
Version int `json:"version"`
|
||||
DaemonID string `json:"daemon_id,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
ProjectKey string `json:"project_key,omitempty"`
|
||||
}
|
||||
|
||||
type Attach struct {
|
||||
Token string `json:"token,omitempty"`
|
||||
ProjectKey string `json:"project_key,omitempty"`
|
||||
TermSize Size `json:"term_size"`
|
||||
}
|
||||
|
||||
type Detach struct {
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
}
|
||||
|
||||
type Size struct {
|
||||
Cols uint16 `json:"cols"`
|
||||
Rows uint16 `json:"rows"`
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
Key string `json:"key"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
LastActive time.Time `json:"last_active,omitempty"`
|
||||
TabCount int `json:"tab_count"`
|
||||
}
|
||||
|
||||
type ProjectList struct {
|
||||
Projects []Project `json:"projects"`
|
||||
}
|
||||
|
||||
type Chrome struct {
|
||||
ProjectKey string `json:"project_key"`
|
||||
Model json.RawMessage `json:"model"`
|
||||
}
|
||||
|
||||
type PaneSnapshot struct {
|
||||
PaneID string `json:"pane_id"`
|
||||
Bytes []byte `json:"bytes"`
|
||||
}
|
||||
|
||||
type PaneChunk struct {
|
||||
PaneID string `json:"pane_id"`
|
||||
Bytes []byte `json:"bytes"`
|
||||
}
|
||||
|
||||
type LifecycleKind string
|
||||
|
||||
const (
|
||||
LifecycleSpawned LifecycleKind = "spawned"
|
||||
LifecycleExited LifecycleKind = "exited"
|
||||
LifecycleClosed LifecycleKind = "closed"
|
||||
LifecycleStateChanged LifecycleKind = "state_changed"
|
||||
)
|
||||
|
||||
type Lifecycle struct {
|
||||
Kind LifecycleKind `json:"kind"`
|
||||
ProjectKey string `json:"project_key,omitempty"`
|
||||
ChildID string `json:"child_id,omitempty"`
|
||||
Child json.RawMessage `json:"child,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
type Input struct {
|
||||
PaneID string `json:"pane_id"`
|
||||
Bytes []byte `json:"bytes"`
|
||||
}
|
||||
|
||||
type Focus struct {
|
||||
PaneID string `json:"pane_id,omitempty"`
|
||||
Pad string `json:"pad,omitempty"`
|
||||
}
|
||||
|
||||
type SwitchProject struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type OpenProject struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type PaletteCommand struct {
|
||||
Kind string `json:"kind"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type TrustResponse struct {
|
||||
ProcessID string `json:"process_id"`
|
||||
Preset string `json:"preset"`
|
||||
Allow bool `json:"allow"`
|
||||
}
|
||||
|
||||
type Resize struct {
|
||||
Size Size `json:"size"`
|
||||
}
|
||||
Reference in New Issue
Block a user