Documentation
¶
Overview ¶
Package attachclient is the shared client for attach-mode endpoints, used by both `core-agent attach`/`ls` (in cmd/core-agent) and `core-agent-tui` (in cmd/core-agent-tui). The package is internal/ so the surface isn't part of the public API stability promise — it's a coordination point between two of our own binaries, not a SDK consumers should reach for.
Index ¶
- type BearerCreds
- type Client
- func (c *Client) Agents(ctx context.Context, sessionPath string) ([]attach.AgentInfo, error)
- func (c *Client) AllowPatterns(ctx context.Context, sessionPath string, patterns []string) error
- func (c *Client) Context(ctx context.Context, sessionPath string) (attach.ContextInfo, error)
- func (c *Client) DenyPatterns(ctx context.Context, sessionPath string, patterns []string) error
- func (c *Client) Inject(ctx context.Context, sessionPath, message string) error
- func (c *Client) Interrupt(ctx context.Context, sessionPath string) (InterruptResponse, error)
- func (c *Client) ListPeers(ctx context.Context) ([]PeerDescriptor, error)
- func (c *Client) ListSessions(ctx context.Context) ([]SessionDescriptor, error)
- func (c *Client) MCP(ctx context.Context, sessionPath string) (attach.MCPInfo, error)
- func (c *Client) Memory(ctx context.Context, sessionPath string) ([]attach.MemorySource, error)
- func (c *Client) NewSession(ctx context.Context) (NewSessionResponse, error)
- func (c *Client) Perms(ctx context.Context, sessionPath string) (attach.PermsInfo, error)
- func (c *Client) Pricing(ctx context.Context, sessionPath string) (attach.PricingInfo, error)
- func (c *Client) PromptStream(ctx context.Context, sessionPath string) (<-chan attach.PromptFrame, error)
- func (c *Client) RefreshPricing(ctx context.Context, sessionPath string) (attach.PricingRefreshResponse, error)
- func (c *Client) Reload(ctx context.Context, sessionPath string) (attach.ReloadResponse, error)
- func (c *Client) Replan(ctx context.Context, sessionPath, reason string) (attach.ReplanResponse, error)
- func (c *Client) RespondToPrompt(ctx context.Context, sessionPath, id, decision string) error
- func (c *Client) SetManualPricing(ctx context.Context, sessionPath string, req attach.PricingSetRequest) error
- func (c *Client) Skills(ctx context.Context, sessionPath string) ([]attach.SkillInfo, error)
- func (c *Client) SlashBtw(ctx context.Context, sessionPath, question string) (string, error)
- func (c *Client) SlashCompact(ctx context.Context, sessionPath, focus string) (attach.CompactResponse, error)
- func (c *Client) SlashDone(ctx context.Context, sessionPath, note string) (attach.CheckpointResponse, error)
- func (c *Client) SlashSubagent(ctx context.Context, sessionPath string, spec attach.SubagentSpec) (attach.SubagentSpawnResponse, error)
- func (c *Client) Status(ctx context.Context, sessionPath string) (attach.StatusInfo, error)
- func (c *Client) Stream(ctx context.Context, sessionPath string, since int64) (<-chan attach.Frame, error)
- func (c *Client) Tools(ctx context.Context, sessionPath string) ([]attach.ToolInfo, error)
- func (c *Client) Usage(ctx context.Context, sessionPath string) (attach.UsageInfo, error)
- func (c *Client) Wake(ctx context.Context, sessionPath string) error
- type Credentials
- type GoogleIDTokenCreds
- type GoogleOAuthCreds
- type InterruptResponse
- type NewSessionResponse
- type ParsedURL
- type PeerDescriptor
- type SessionDescriptor
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BearerCreds ¶
type BearerCreds struct {
Token string
}
BearerCreds sends the attach token as Authorization: Bearer <token>. Zero-value (Token == "") is auth-disabled — Apply is a no-op, matching the historical attach-over-Unix-socket convention.
type Client ¶
type Client struct {
URL *ParsedURL
// Token is kept for source-compat with the original constructor
// (New stores its token here AND wraps it in a BearerCreds in
// Credentials below). New code should prefer Credentials directly.
// When both are set, Credentials wins — that's how callers opt
// in to the gateway-fronted path while keeping the legacy field
// for backward compatibility.
Token string
Credentials Credentials
// contains filtered or unexported fields
}
Client is a thin HTTP client for one attach-mode endpoint. Holds the parsed URL, bearer token (empty for no auth), and a configured http.Client (Unix-socket-aware when the URL scheme is unix://). Safe for concurrent use.
Two HTTP clients live inside: `http` for short-lived RPC calls with a request timeout, and `streamHTTP` for SSE — no timeout, because the stream body stays open for as long as the agent runs and minutes can pass between frames. A single client with a Timeout would cut the SSE body mid-response on long model turns; the symptom is "stream ended: <nil>" reconnect-loops in the UI.
func New ¶
New builds a Client wrapped in BearerCreds. ParseURL the rawURL first; Token may be empty (auth disabled — fine for Unix socket). timeout governs short-lived RPC calls. SSE streams ignore it (caller's ctx is the cancel signal). Zero timeout falls back to 30 s for RPCs.
Use NewWithCredentials to construct a Client with a non-Bearer auth strategy (Cloud Run IAM, IAP, …).
func NewWithCredentials ¶
func NewWithCredentials(parsed *ParsedURL, creds Credentials, timeout time.Duration) *Client
NewWithCredentials builds a Client with an explicit Credentials implementation. Used by callers that need a non-Bearer auth path (e.g. cmd/core-agent-tui's --auth=google-id-token mode, which supplies a GoogleIDTokenCreds backed by ADC).
func (*Client) AllowPatterns ¶
AllowPatterns calls POST <base>/sessions/<sid>/perms/allow with the given patterns. Backs the remote TUI's /allow slash. Returns nil on success (204), an error otherwise — including 501 when the agent doesn't implement PermsController and 400 when the gate rejects a pattern.
func (*Client) Context ¶
Context calls GET <base>/sessions/<sid>/context. Backs the remote TUI's /context slash. Returns zero ContextInfo on 501.
func (*Client) DenyPatterns ¶
DenyPatterns calls POST <base>/sessions/<sid>/perms/deny. Backs the remote TUI's /deny slash.
func (*Client) Inject ¶
Inject calls POST <base>/sessions/<sid>/inject with the given message. sessionPath is the /sessions/<sid> prefix (relative to BaseURL).
func (*Client) Interrupt ¶
Interrupt calls POST <base>/sessions/<sid>/interrupt to cancel the in-flight turn on that session. The returned InterruptResponse reports whether something was actually cancelled.
func (*Client) ListPeers ¶
func (c *Client) ListPeers(ctx context.Context) ([]PeerDescriptor, error)
ListPeers calls GET <base>/peers. Returns nil (not an error) when the listener doesn't have peer-registration enabled (HTTP 404).
func (*Client) ListSessions ¶
func (c *Client) ListSessions(ctx context.Context) ([]SessionDescriptor, error)
ListSessions calls GET <base>/sessions.
func (*Client) MCP ¶
MCP calls GET <base>/sessions/<sid>/mcp. Backs the remote TUI's /mcp slash. Returns zero MCPInfo on 501.
func (*Client) Memory ¶
Memory calls GET <base>/sessions/<sid>/memory. Backs the remote TUI's /memory slash. Returns empty slice (not nil) on 501.
func (*Client) NewSession ¶
func (c *Client) NewSession(ctx context.Context) (NewSessionResponse, error)
NewSession calls POST <base>/sessions to create a fresh session owned by the authenticated caller. Returns the new session's descriptor on success.
Server-side behavior:
- 201: new session created, response carries the triple + URL
- 401: caller couldn't be resolved (anonymous request)
- 501: daemon doesn't have a SessionFactory configured
- 500: factory error
- 409: triple collision (factory's SessionID generator clashed)
All non-2xx responses surface as errors.
func (*Client) Perms ¶
Perms calls GET <base>/sessions/<sid>/perms. Backs the remote TUI's /permissions slash. Returns zero PermsInfo on 501.
func (*Client) Pricing ¶
Pricing calls GET <base>/sessions/<sid>/pricing. Backs the remote TUI's /pricing slash. Returns zero PricingInfo on 501.
func (*Client) PromptStream ¶
func (c *Client) PromptStream(ctx context.Context, sessionPath string) (<-chan attach.PromptFrame, error)
PromptStream subscribes to <base><sessionPath>/perms/stream and returns a channel of PromptFrames. Closes the channel on ctx cancel, stream error, or upstream EOF. 501 (capability not registered — agent wasn't constructed with WithAttachPromptBroker) is returned synchronously so callers can fall back gracefully.
func (*Client) RefreshPricing ¶
func (c *Client) RefreshPricing(ctx context.Context, sessionPath string) (attach.PricingRefreshResponse, error)
RefreshPricing calls POST <base>/sessions/<sid>/pricing/refresh. Backs the remote TUI's /pricing refresh subcommand. Returns the outcome (whether the LiteLLM fetch actually pulled new data and the post-refresh model count) so the client can update its display.
func (*Client) Reload ¶
Reload calls POST <base>/sessions/<sid>/reload. Backs the remote TUI's /reload slash. Returns the per-surface success flags + any errors so the operator sees which parts (memory / skills / mcp) succeeded and which failed.
func (*Client) Replan ¶
func (c *Client) Replan(ctx context.Context, sessionPath, reason string) (attach.ReplanResponse, error)
Replan calls POST <base>/sessions/<sid>/slash/replan. Backs the remote TUI's /replan slash. Reason is the optional free-text the operator typed after /replan; today it's surfaced in the archive's audit trail but doesn't drive any model-side behavior.
func (*Client) RespondToPrompt ¶
RespondToPrompt POSTs the operator's decision to <base><sessionPath>/perms/respond. decision must be one of the wire-format strings (e.g. "allow-once"); see attach.DecisionFromWire for the mapping.
func (*Client) SetManualPricing ¶
func (c *Client) SetManualPricing(ctx context.Context, sessionPath string, req attach.PricingSetRequest) error
SetManualPricing calls POST <base>/sessions/<sid>/pricing/set. Backs the remote TUI's /pricing set subcommand.
func (*Client) Skills ¶
Skills calls GET <base>/sessions/<sid>/skills. Backs the remote TUI's /skills slash.
func (*Client) SlashBtw ¶
SlashBtw calls POST <base>/sessions/<sid>/slash/btw. Synchronous. Backs the remote TUI's /btw slash. The answer renders as a dismissible overlay (no event-log persistence).
func (*Client) SlashCompact ¶
func (c *Client) SlashCompact(ctx context.Context, sessionPath, focus string) (attach.CompactResponse, error)
SlashCompact calls POST <base>/sessions/<sid>/slash/compact. Synchronous: blocks until the compaction summarizer completes (5–30s typical for real model calls). The remote TUI should render the in-chat preamble row at dispatch — this call does NOT emit a preamble itself.
func (*Client) SlashDone ¶
func (c *Client) SlashDone(ctx context.Context, sessionPath, note string) (attach.CheckpointResponse, error)
SlashDone calls POST <base>/sessions/<sid>/slash/done. Synchronous. Backs the remote TUI's /done slash.
func (*Client) SlashSubagent ¶
func (c *Client) SlashSubagent(ctx context.Context, sessionPath string, spec attach.SubagentSpec) (attach.SubagentSpawnResponse, error)
SlashSubagent calls POST <base>/sessions/<sid>/slash/subagent. Backs the remote TUI's /subagent slash. Returns the spawn confirmation (name + started_at); the subagent's events flow through the existing SSE stream under a branch label so the operator sees its turns alongside the parent's.
func (*Client) Stream ¶
func (c *Client) Stream(ctx context.Context, sessionPath string, since int64) (<-chan attach.Frame, error)
Stream connects to <base><sessionPath>/events?since=<since> and returns a channel of decoded frames. Closes the channel on ctx cancel, stream error, or upstream EOF. Errors that prevented the initial GET (network failure, non-200 status) are returned synchronously; downstream errors land in the returned channel's error field via the second return value being closed.
The lossless-replay property of the protocol means that passing a non-zero since value asks the server to replay any frames since that sequence before resuming live tail.
func (*Client) Tools ¶
Tools calls GET <base>/sessions/<sid>/tools. Returns the parsed list; empty (not nil) if the session doesn't implement the provider.
type Credentials ¶
type Credentials interface {
// Apply stamps headers on req. Returns an error when the
// underlying credential source fails to produce a token —
// callers propagate (the request is not sent in that case).
Apply(req *http.Request) error
}
Credentials stamps authentication headers on outbound requests. Implementations must be safe for concurrent use from multiple goroutines (the Client uses one Credentials for every request, including parallel RPC + SSE).
type GoogleIDTokenCreds ¶
type GoogleIDTokenCreds struct {
Source oauth2.TokenSource
AttachToken string
}
GoogleIDTokenCreds is the audience-bound variant. The Source produces a Google ID token bound to a specific audience (the gateway's expected audience — service URL for Cloud Run, OAuth client ID for IAP). Use when audience-binding is required (IAP) or when a service explicitly requires ID tokens.
Important constraint: idtoken.NewTokenSource does NOT accept end-user (authorized_user) credentials — operators using gcloud auth application-default login will hit "unsupported credentials type: authorized_user" at construction time. Workarounds:
- Re-login with impersonation: gcloud auth application-default login --impersonate-service-account=SA_EMAIL
- Set GOOGLE_APPLICATION_CREDENTIALS to a service-account JSON key
- Use --auth=google-oauth instead (Cloud Run IAM accepts access tokens; the audience-binding loss is mostly theoretical)
AttachToken may be empty when the daemon runs without --attach-token ("Posture B"). The header is omitted entirely in that case.
type GoogleOAuthCreds ¶
type GoogleOAuthCreds struct {
Source oauth2.TokenSource
AttachToken string
}
GoogleOAuthCreds wraps a Google OAuth2 access-token source (typically google.FindDefaultCredentials's TokenSource) and stamps the access token on Authorization. Works with every ADC shape end users actually have on their workstations — end-user (authorized_user) creds, service-account JSON keys, metadata server, impersonation.
This is the right default for Cloud Run IAM: the gateway accepts either OAuth access tokens OR audience-bound ID tokens, and access tokens come for free from end-user ADC. Mirrors MCP's googleAuthTransport pattern (pkg/mcp/lifecycle.go:296).
AttachToken may be empty when the daemon runs without --attach-token ("Posture B"). The header is omitted entirely in that case.
type InterruptResponse ¶
type InterruptResponse struct {
Interrupted bool `json:"interrupted"`
Session string `json:"session"`
}
InterruptResponse is the parsed body of POST /sessions/<sid>/interrupt. Interrupted reports whether there was an in-flight turn to cancel (server-side); false means the agent was idle and the call was a no-op. The TUI distinguishes these for its "nothing to interrupt" toast vs. "turn cancelled" rendering.
type NewSessionResponse ¶
type NewSessionResponse struct {
AppName string `json:"app"`
UserID string `json:"user"`
SessionID string `json:"sessionID"`
URL string `json:"url"`
}
NewSessionResponse mirrors the attach server's POST /sessions 201 body — the new session's triple plus the absolute URL the client should attach to (events / inject / status / etc. live underneath).
type ParsedURL ¶
type ParsedURL struct {
Scheme string // http | https | unix
Host string // host:port (empty for unix)
SocketPath string // for unix scheme
BaseURL string // ready-to-use for HTTP client: http(s)://host OR http://unix placeholder
Session string // /sessions/<...> path; empty for list endpoints
}
ParsedURL holds the components of an attach-mode URL. Three schemes are accepted: http://, https://, unix:// (the last for Unix-socket listeners — convention is unix:///path/to/socket/sessions/<sid>).
Session is non-empty when the URL targets a specific session (e.g. /sessions/<sid> or /sessions/<app>/<sid>). For listing endpoints (GET /sessions) Session is empty.