1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
package bridge
import "encoding/json"
// This file is the wire contract between the SPA and the backend, over a
// WebSocket carrying one JSON message per text frame. ACP payloads (session
// updates, permission requests) are relayed verbatim so the frontend works
// with the protocol's own shapes, documented at
// https://agentclientprotocol.com.
// Incoming message types (browser → server).
const (
// IncomingPrompt starts a turn: {"type":"prompt","text":"...","attachments":[...]}.
IncomingPrompt = "prompt"
// IncomingCancel interrupts the current turn: {"type":"cancel"}.
IncomingCancel = "cancel"
// IncomingPermissionResponse answers a permission request:
// {"type":"permission_response","requestId":"...","optionId":"..."} or
// {"type":"permission_response","requestId":"...","cancelled":true}.
IncomingPermissionResponse = "permission_response"
)
// Outgoing message types (server → browser).
const (
// OutgoingHello is sent once per connection with the session state.
OutgoingHello = "hello"
// OutgoingUserMessage echoes the prompt that started a turn, so every
// client (and every reconnection replay) renders the user's side of the
// conversation from the same source of truth as the agent's side.
OutgoingUserMessage = "user_message"
// OutgoingSessionUpdate relays one ACP session/update notification;
// its "update" field is the raw ACP SessionUpdate object.
OutgoingSessionUpdate = "session_update"
// OutgoingPermissionRequest relays an ACP permission request; its
// "request" field is the raw ACP RequestPermissionRequest object.
OutgoingPermissionRequest = "permission_request"
// OutgoingPermissionResolved tells every client a permission request
// was answered (so open dialogs close everywhere).
OutgoingPermissionResolved = "permission_resolved"
// OutgoingTurnStarted marks the start of a prompt turn.
OutgoingTurnStarted = "turn_started"
// OutgoingTurnEnded marks the end of a turn, with its stop reason.
OutgoingTurnEnded = "turn_ended"
// OutgoingError carries a human-readable error message.
OutgoingError = "error"
)
// Attachment is a workspace file the user mentioned in a prompt (with the
// "@" selector). The server turns it into an ACP resource_link content block
// so the agent can open the file, while the "@name" mention stays visible in
// the prompt text.
type Attachment struct {
// Path is the file path, absolute or relative to the workspace root.
Path string `json:"path"`
// Name is the label shown for the file (the relative path in the UI);
// it defaults to Path.
Name string `json:"name,omitempty"`
}
// Incoming is a message sent by the browser.
type Incoming struct {
Type string `json:"type"`
// Text is the prompt text (IncomingPrompt).
Text string `json:"text,omitempty"`
// Attachments are the files mentioned in the prompt (IncomingPrompt).
Attachments []Attachment `json:"attachments,omitempty"`
// RequestId identifies the permission request being answered.
RequestId string `json:"requestId,omitempty"`
// OptionId is the chosen ACP permission option.
OptionId string `json:"optionId,omitempty"`
// Cancelled dismisses the permission request instead of choosing.
Cancelled bool `json:"cancelled,omitempty"`
}
// Outgoing is a message sent to the browser.
type Outgoing struct {
Type string `json:"type"`
// SessionId is set on OutgoingHello.
SessionId string `json:"sessionId,omitempty"`
// TurnActive is set on OutgoingHello: true while a prompt turn runs.
TurnActive bool `json:"turnActive,omitempty"`
// Update is the raw ACP SessionUpdate (OutgoingSessionUpdate).
Update json.RawMessage `json:"update,omitempty"`
// RequestId identifies a permission request across messages.
RequestId string `json:"requestId,omitempty"`
// Request is the raw ACP RequestPermissionRequest (OutgoingPermissionRequest).
Request json.RawMessage `json:"request,omitempty"`
// Text is the user's prompt text (OutgoingUserMessage).
Text string `json:"text,omitempty"`
// Attachments are the files the prompt mentioned (OutgoingUserMessage).
Attachments []Attachment `json:"attachments,omitempty"`
// StopReason is the ACP stop reason (OutgoingTurnEnded).
StopReason string `json:"stopReason,omitempty"`
// Message is a human-readable error (OutgoingError).
Message string `json:"message,omitempty"`
}
// encode marshals an Outgoing message; the bridge treats a marshalling
// failure as a programming error and panics, because every field is
// marshal-safe by construction.
func encode(msg Outgoing) []byte {
raw, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return raw
}
|