@pi-in-go/pigpen-acp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CREDITS.md +23 -0
- package/LICENSE +22 -0
- package/README.md +110 -0
- package/extensions/acp/cmd/pig-acp/e2e_llm_test.go +168 -0
- package/extensions/acp/cmd/pig-acp/e2e_test.go +541 -0
- package/extensions/acp/cmd/pig-acp/go.mod +6 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent.go +784 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_builtin_test.go +536 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_commands_test.go +199 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_config_test.go +530 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_helpers_test.go +215 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_load_test.go +333 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_prompt.go +289 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/agent_session_test.go +429 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/auth.go +61 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/bench_test.go +47 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/helpers_test.go +421 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/main_test.go +28 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/mutation_extra_test.go +142 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/paths.go +77 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/pisessions.go +317 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/protocol.go +188 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/server.go +218 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/server_test.go +386 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/session.go +1068 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/session_diff_test.go +183 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/session_events_test.go +443 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/session_extra_test.go +547 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/session_usage_test.go +198 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/settings.go +79 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/slash.go +278 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/slash_test.go +128 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/startup.go +156 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/store.go +118 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/store_paths_test.go +466 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/translate.go +352 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/translate_test.go +105 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/twin_test.go +28 -0
- package/extensions/acp/cmd/pig-acp/internal/acp/types.go +202 -0
- package/extensions/acp/cmd/pig-acp/internal/jsonrpc/bench_test.go +24 -0
- package/extensions/acp/cmd/pig-acp/internal/jsonrpc/jsonrpc.go +302 -0
- package/extensions/acp/cmd/pig-acp/internal/jsonrpc/jsonrpc_test.go +320 -0
- package/extensions/acp/cmd/pig-acp/internal/jsonrpc/twin_test.go +28 -0
- package/extensions/acp/cmd/pig-acp/internal/pirpc/kill_unix.go +19 -0
- package/extensions/acp/cmd/pig-acp/internal/pirpc/kill_windows.go +11 -0
- package/extensions/acp/cmd/pig-acp/internal/pirpc/process.go +530 -0
- package/extensions/acp/cmd/pig-acp/internal/pirpc/process_test.go +355 -0
- package/extensions/acp/cmd/pig-acp/internal/pirpc/twin_test.go +28 -0
- package/extensions/acp/cmd/pig-acp/main.go +177 -0
- package/extensions/acp/cmd/pig-acp/main_test.go +213 -0
- package/extensions/acp/cmd/pig-acp/sigpipe_unix.go +10 -0
- package/extensions/acp/cmd/pig-acp/sigpipe_windows.go +5 -0
- package/extensions/acp/cmd/pig-acp/testdata/ask/extension.go +28 -0
- package/extensions/acp/cmd/pig-acp/testdata/ask/go.mod +7 -0
- package/extensions/acp/cmd/pig-acp/testdata/ask/go.sum +2 -0
- package/extensions/acp/cmd/pig-acp/testdata/gate/extension.go +37 -0
- package/extensions/acp/cmd/pig-acp/testdata/gate/go.mod +7 -0
- package/extensions/acp/cmd/pig-acp/testdata/gate/go.sum +2 -0
- package/extensions/acp/cmd/pig-acp/twin_test.go +28 -0
- package/extensions/acp/extension.go +63 -0
- package/extensions/acp/extension_test.go +111 -0
- package/extensions/acp/fakehost_test.go +548 -0
- package/extensions/acp/go.mod +6 -0
- package/extensions/acp/go.sum +2 -0
- package/package.json +41 -0
- package/port/PORT.md +147 -0
- package/port/interop/package-lock.json +89 -0
- package/port/interop/package.json +13 -0
- package/port/interop/run.mjs +229 -0
- package/port/mutate.mjs +110 -0
- package/port/mutation-results.txt +107 -0
- package/port/mutations.json +748 -0
- package/port/oracle/pi-acp/LICENSE +21 -0
- package/port/oracle/pi-acp/README.md +209 -0
- package/port/oracle/pi-acp/package.json +65 -0
- package/port/oracle/pi-acp/src/acp/agent.ts +1661 -0
- package/port/oracle/pi-acp/src/acp/auth-required.ts +37 -0
- package/port/oracle/pi-acp/src/acp/auth.ts +59 -0
- package/port/oracle/pi-acp/src/acp/paths.ts +15 -0
- package/port/oracle/pi-acp/src/acp/pi-commands.ts +59 -0
- package/port/oracle/pi-acp/src/acp/pi-sessions.ts +333 -0
- package/port/oracle/pi-acp/src/acp/pi-settings.ts +75 -0
- package/port/oracle/pi-acp/src/acp/session-store.ts +68 -0
- package/port/oracle/pi-acp/src/acp/session.ts +1078 -0
- package/port/oracle/pi-acp/src/acp/slash-commands.ts +197 -0
- package/port/oracle/pi-acp/src/acp/translate/bash.ts +100 -0
- package/port/oracle/pi-acp/src/acp/translate/pi-messages.ts +17 -0
- package/port/oracle/pi-acp/src/acp/translate/pi-tools.ts +51 -0
- package/port/oracle/pi-acp/src/acp/translate/prompt.ts +71 -0
- package/port/oracle/pi-acp/src/index.ts +82 -0
- package/port/oracle/pi-acp/src/pi-rpc/command.ts +16 -0
- package/port/oracle/pi-acp/src/pi-rpc/process.ts +432 -0
- package/port/oracle/pi-acp/test/component/agent-steering-followup-modes.test.ts +143 -0
- package/port/oracle/pi-acp/test/component/session-diff.test.ts +214 -0
- package/port/oracle/pi-acp/test/component/session-events.test.ts +1102 -0
- package/port/oracle/pi-acp/test/component/session-list-and-load.test.ts +129 -0
- package/port/oracle/pi-acp/test/component/session-list-custom-session-dir.test.ts +52 -0
- package/port/oracle/pi-acp/test/component/session-list-scoped.test.ts +76 -0
- package/port/oracle/pi-acp/test/component/session-load-toolresult.test.ts +68 -0
- package/port/oracle/pi-acp/test/component/session-queue-cancel.test.ts +43 -0
- package/port/oracle/pi-acp/test/component/session-slash-commands.test.ts +37 -0
- package/port/oracle/pi-acp/test/component/session-thinking-modes.test.ts +22 -0
- package/port/oracle/pi-acp/test/component/session-title-long-session.test.ts +56 -0
- package/port/oracle/pi-acp/test/component/session-updatedAt-message-only.test.ts +57 -0
- package/port/oracle/pi-acp/test/helpers/fakes.ts +87 -0
- package/port/oracle/pi-acp/test/unit/auth-methods-terminal-auth-meta.test.ts +22 -0
- package/port/oracle/pi-acp/test/unit/builtin-commands.test.ts +60 -0
- package/port/oracle/pi-acp/test/unit/context-usage.test.ts +204 -0
- package/port/oracle/pi-acp/test/unit/merge-commands.test.ts +19 -0
- package/port/oracle/pi-acp/test/unit/model-thinking-levels.test.ts +268 -0
- package/port/oracle/pi-acp/test/unit/new-session-auth-required-when-no-models.test.ts +51 -0
- package/port/oracle/pi-acp/test/unit/new-session-pi-not-found.test.ts +26 -0
- package/port/oracle/pi-acp/test/unit/new-session-runtime-startup-errors.test.ts +148 -0
- package/port/oracle/pi-acp/test/unit/pi-command.test.ts +44 -0
- package/port/oracle/pi-acp/test/unit/pi-commands.test.ts +32 -0
- package/port/oracle/pi-acp/test/unit/pi-enable-embed-context-flag.test.ts +35 -0
- package/port/oracle/pi-acp/test/unit/pi-messages.test.ts +29 -0
- package/port/oracle/pi-acp/test/unit/pi-rpc-request-timeout.test.ts +137 -0
- package/port/oracle/pi-acp/test/unit/pi-rpc-session-path.test.ts +93 -0
- package/port/oracle/pi-acp/test/unit/pi-tools.test.ts +40 -0
- package/port/oracle/pi-acp/test/unit/prompt-to-pi-message.test.ts +74 -0
- package/port/oracle/pi-acp/test/unit/session-config-options.test.ts +217 -0
- package/port/oracle/pi-acp/test/unit/session-delete.test.ts +170 -0
- package/port/oracle/pi-acp/test/unit/session-restore.test.ts +239 -0
- package/port/oracle/pi-acp/test/unit/slash-commands.test.ts +36 -0
- package/port/oracle/pi-acp/test/unit/startup-info-env.test.ts +84 -0
- package/port/oracle/pi-acp/test/unit/startup-info-load-session.test.ts +53 -0
- package/port/oracle/pi-acp/test/unit/startup-info-project-packages.test.ts +74 -0
- package/port/oracle/pi-acp/test/unit/stdout-destroyed-does-not-crash.test.ts +32 -0
- package/port/oracle/pi-acp/test/unit/thinking-level-rpc.test.ts +43 -0
- package/port/schema/LICENSE +191 -0
- package/port/schema/schema.json +8015 -0
- package/port/upstream-tests.json +201 -0
- package/provenance.json +28 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
package acp
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bufio"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"io"
|
|
7
|
+
"io/fs"
|
|
8
|
+
"os"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
"sort"
|
|
11
|
+
"strings"
|
|
12
|
+
"time"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// PiSessionListItem is one session found on disk.
|
|
16
|
+
type PiSessionListItem struct {
|
|
17
|
+
SessionID string
|
|
18
|
+
Cwd string
|
|
19
|
+
Title *string
|
|
20
|
+
UpdatedAt *string
|
|
21
|
+
SessionFile string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const (
|
|
25
|
+
defaultTailBytes = 256 * 1024
|
|
26
|
+
defaultHeadBytes = 64 * 1024
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
func sessionDirFromSettings(agentDir string) string {
|
|
30
|
+
data := readJSONObject(filepath.Join(agentDir, "settings.json"))
|
|
31
|
+
s, ok := data["sessionDir"].(string)
|
|
32
|
+
if !ok || jsTrim(s) == "" {
|
|
33
|
+
return ""
|
|
34
|
+
}
|
|
35
|
+
if filepath.IsAbs(s) {
|
|
36
|
+
return s
|
|
37
|
+
}
|
|
38
|
+
return filepath.Join(agentDir, s)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// PiSessionsDir is where PiG keeps session files: PIG_CODING_AGENT_SESSION_DIR (in shared mode
|
|
42
|
+
// PI_CODING_AGENT_SESSION_DIR, as pig reads it), else the sessionDir setting, else <agent dir>/sessions.
|
|
43
|
+
func PiSessionsDir() string {
|
|
44
|
+
env := "PIG_CODING_AGENT_SESSION_DIR"
|
|
45
|
+
if sharedPiDirs() {
|
|
46
|
+
env = "PI_CODING_AGENT_SESSION_DIR"
|
|
47
|
+
}
|
|
48
|
+
if d := os.Getenv(env); d != "" {
|
|
49
|
+
return expandTilde(d)
|
|
50
|
+
}
|
|
51
|
+
agent := AgentDir()
|
|
52
|
+
if d := sessionDirFromSettings(agent); d != "" {
|
|
53
|
+
return d
|
|
54
|
+
}
|
|
55
|
+
return filepath.Join(agent, "sessions")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func walkJSONL(dir string, out *[]string) {
|
|
59
|
+
entries, err := os.ReadDir(dir)
|
|
60
|
+
if err != nil {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
for _, e := range entries {
|
|
64
|
+
p := filepath.Join(dir, e.Name())
|
|
65
|
+
switch {
|
|
66
|
+
case e.IsDir():
|
|
67
|
+
walkJSONL(p, out)
|
|
68
|
+
case e.Type().IsRegular() && strings.HasSuffix(e.Name(), ".jsonl"):
|
|
69
|
+
*out = append(*out, p)
|
|
70
|
+
case e.Type()&fs.ModeSymlink != 0 && strings.HasSuffix(e.Name(), ".jsonl"):
|
|
71
|
+
if st, err := os.Stat(p); err == nil && st.Mode().IsRegular() {
|
|
72
|
+
*out = append(*out, p)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func readFirstLine(path string) (string, bool) {
|
|
79
|
+
f, err := os.Open(path)
|
|
80
|
+
if err != nil {
|
|
81
|
+
return "", false
|
|
82
|
+
}
|
|
83
|
+
defer f.Close()
|
|
84
|
+
buf := make([]byte, defaultHeadBytes)
|
|
85
|
+
n, _ := io.ReadFull(f, buf)
|
|
86
|
+
if n <= 0 {
|
|
87
|
+
return "", false
|
|
88
|
+
}
|
|
89
|
+
s := string(buf[:n])
|
|
90
|
+
if i := strings.IndexByte(s, '\n'); i >= 0 {
|
|
91
|
+
return jsTrim(s[:i]), true
|
|
92
|
+
}
|
|
93
|
+
return jsTrim(s), true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func readTail(path string) (string, error) {
|
|
97
|
+
f, err := os.Open(path)
|
|
98
|
+
if err != nil {
|
|
99
|
+
return "", err
|
|
100
|
+
}
|
|
101
|
+
defer f.Close()
|
|
102
|
+
st, err := f.Stat()
|
|
103
|
+
if err != nil {
|
|
104
|
+
return "", err
|
|
105
|
+
}
|
|
106
|
+
start := st.Size() - defaultTailBytes
|
|
107
|
+
if start < 0 {
|
|
108
|
+
start = 0
|
|
109
|
+
}
|
|
110
|
+
buf := make([]byte, st.Size()-start)
|
|
111
|
+
n, err := f.ReadAt(buf, start)
|
|
112
|
+
if err != nil && err != io.EOF {
|
|
113
|
+
return "", err
|
|
114
|
+
}
|
|
115
|
+
return string(buf[:n]), nil
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func splitLines(s string) []string { return strings.Split(strings.ReplaceAll(s, "\r\n", "\n"), "\n") }
|
|
119
|
+
|
|
120
|
+
func parseSessionHeader(first string) (id, cwd string, ok bool) {
|
|
121
|
+
var obj map[string]any
|
|
122
|
+
if json.Unmarshal([]byte(first), &obj) != nil || obj["type"] != "session" {
|
|
123
|
+
return "", "", false
|
|
124
|
+
}
|
|
125
|
+
id, _ = obj["id"].(string)
|
|
126
|
+
cwd, _ = obj["cwd"].(string)
|
|
127
|
+
if id == "" || cwd == "" {
|
|
128
|
+
return "", "", false
|
|
129
|
+
}
|
|
130
|
+
return id, cwd, true
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func sessionInfoName(line string) (string, bool) {
|
|
134
|
+
line = jsTrim(line)
|
|
135
|
+
if line == "" {
|
|
136
|
+
return "", false
|
|
137
|
+
}
|
|
138
|
+
var obj map[string]any
|
|
139
|
+
if json.Unmarshal([]byte(line), &obj) != nil || obj["type"] != "session_info" {
|
|
140
|
+
return "", false
|
|
141
|
+
}
|
|
142
|
+
if name, ok := obj["name"].(string); ok && jsTrim(name) != "" {
|
|
143
|
+
return jsTrim(name), true
|
|
144
|
+
}
|
|
145
|
+
return "", false
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
func pickTitleFromTail(tail string) string {
|
|
149
|
+
lines := splitLines(tail)
|
|
150
|
+
for i := len(lines) - 1; i >= 0; i-- {
|
|
151
|
+
if name, ok := sessionInfoName(lines[i]); ok {
|
|
152
|
+
return name
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return ""
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
func scanSessionInfoName(path string) string {
|
|
159
|
+
f, err := os.Open(path)
|
|
160
|
+
if err != nil {
|
|
161
|
+
return ""
|
|
162
|
+
}
|
|
163
|
+
defer f.Close()
|
|
164
|
+
r := bufio.NewReaderSize(f, 256*1024)
|
|
165
|
+
last := ""
|
|
166
|
+
for {
|
|
167
|
+
line, err := r.ReadString('\n')
|
|
168
|
+
if name, ok := sessionInfoName(line); ok {
|
|
169
|
+
last = name
|
|
170
|
+
}
|
|
171
|
+
if err != nil {
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return last
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
func parseTimestamp(ts string) (string, bool) {
|
|
179
|
+
for _, layout := range []string{time.RFC3339Nano, "2006-01-02T15:04:05.999999999", "2006-01-02 15:04:05Z07:00", "2006-01-02"} {
|
|
180
|
+
if t, err := time.Parse(layout, ts); err == nil {
|
|
181
|
+
return t.UTC().Format("2006-01-02T15:04:05.000Z"), true
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return "", false
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
func pickUpdatedAtFromTail(tail string) string {
|
|
188
|
+
lines := splitLines(tail)
|
|
189
|
+
scan := func(onlyMessages bool) string {
|
|
190
|
+
for i := len(lines) - 1; i >= 0; i-- {
|
|
191
|
+
line := jsTrim(lines[i])
|
|
192
|
+
if line == "" {
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
var obj map[string]any
|
|
196
|
+
if json.Unmarshal([]byte(line), &obj) != nil {
|
|
197
|
+
continue
|
|
198
|
+
}
|
|
199
|
+
if onlyMessages && obj["type"] != "message" {
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
if ts, ok := obj["timestamp"].(string); ok {
|
|
203
|
+
if iso, ok := parseTimestamp(ts); ok {
|
|
204
|
+
return iso
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return ""
|
|
209
|
+
}
|
|
210
|
+
if s := scan(true); s != "" {
|
|
211
|
+
return s
|
|
212
|
+
}
|
|
213
|
+
return scan(false)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func pickFallbackTitleFromHead(path string) string {
|
|
217
|
+
raw, err := os.ReadFile(path)
|
|
218
|
+
if err != nil {
|
|
219
|
+
return ""
|
|
220
|
+
}
|
|
221
|
+
lines := splitLines(string(raw))
|
|
222
|
+
for _, l := range lines {
|
|
223
|
+
line := jsTrim(l)
|
|
224
|
+
if line == "" {
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
227
|
+
var obj map[string]any
|
|
228
|
+
if json.Unmarshal([]byte(line), &obj) == nil && obj["type"] == "message" {
|
|
229
|
+
msg := asObject(obj["message"])
|
|
230
|
+
if msg["role"] == "user" {
|
|
231
|
+
switch c := msg["content"].(type) {
|
|
232
|
+
case string:
|
|
233
|
+
return truncateRunes(c, 80)
|
|
234
|
+
case []any:
|
|
235
|
+
for _, b := range c {
|
|
236
|
+
m := asObject(b)
|
|
237
|
+
if s, ok := m["text"].(string); ok && m["type"] == "text" && s != "" {
|
|
238
|
+
return truncateRunes(s, 80)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if len(lines) > 2000 {
|
|
245
|
+
break
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return ""
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
func truncateRunes(s string, n int) string {
|
|
252
|
+
r := []rune(s)
|
|
253
|
+
if len(r) <= n {
|
|
254
|
+
return s
|
|
255
|
+
}
|
|
256
|
+
return string(r[:n])
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ListPiSessions scans the sessions directory, most recent first.
|
|
260
|
+
func ListPiSessions() []PiSessionListItem {
|
|
261
|
+
var files []string
|
|
262
|
+
walkJSONL(PiSessionsDir(), &files)
|
|
263
|
+
items := []PiSessionListItem{}
|
|
264
|
+
for _, file := range files {
|
|
265
|
+
first, ok := readFirstLine(file)
|
|
266
|
+
if !ok || first == "" {
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
id, cwd, ok := parseSessionHeader(first)
|
|
270
|
+
if !ok {
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
var title, updated string
|
|
274
|
+
if tail, err := readTail(file); err == nil {
|
|
275
|
+
title = pickTitleFromTail(tail)
|
|
276
|
+
updated = pickUpdatedAtFromTail(tail)
|
|
277
|
+
}
|
|
278
|
+
if title == "" {
|
|
279
|
+
title = scanSessionInfoName(file)
|
|
280
|
+
}
|
|
281
|
+
if updated == "" {
|
|
282
|
+
if st, err := os.Stat(file); err == nil {
|
|
283
|
+
updated = st.ModTime().UTC().Format("2006-01-02T15:04:05.000Z")
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if title == "" {
|
|
287
|
+
title = pickFallbackTitleFromHead(file)
|
|
288
|
+
}
|
|
289
|
+
item := PiSessionListItem{SessionID: id, Cwd: cwd, SessionFile: file}
|
|
290
|
+
if title != "" {
|
|
291
|
+
item.Title = &title
|
|
292
|
+
}
|
|
293
|
+
if updated != "" {
|
|
294
|
+
item.UpdatedAt = &updated
|
|
295
|
+
}
|
|
296
|
+
items = append(items, item)
|
|
297
|
+
}
|
|
298
|
+
key := func(p *string) string {
|
|
299
|
+
if p == nil {
|
|
300
|
+
return ""
|
|
301
|
+
}
|
|
302
|
+
return *p
|
|
303
|
+
}
|
|
304
|
+
sort.SliceStable(items, func(i, j int) bool { return key(items[i].UpdatedAt) > key(items[j].UpdatedAt) })
|
|
305
|
+
return items
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// FindPiSession finds one session by id.
|
|
309
|
+
func FindPiSession(sessionID string) *PiSessionListItem {
|
|
310
|
+
for _, s := range ListPiSessions() {
|
|
311
|
+
if s.SessionID == sessionID {
|
|
312
|
+
s := s
|
|
313
|
+
return &s
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return nil
|
|
317
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
package acp
|
|
2
|
+
|
|
3
|
+
// Request and response shapes of the ACP methods the adapter implements. They follow
|
|
4
|
+
// schema.json of @agentclientprotocol/sdk 0.26.0 (protocol version 1).
|
|
5
|
+
|
|
6
|
+
// InitializeRequest is `initialize` params.
|
|
7
|
+
type InitializeRequest struct {
|
|
8
|
+
ProtocolVersion int `json:"protocolVersion"`
|
|
9
|
+
ClientCapabilities map[string]any `json:"clientCapabilities,omitempty"`
|
|
10
|
+
ClientInfo map[string]any `json:"clientInfo,omitempty"`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// InitializeResponse is `initialize` result.
|
|
14
|
+
type InitializeResponse struct {
|
|
15
|
+
ProtocolVersion int `json:"protocolVersion"`
|
|
16
|
+
AgentInfo map[string]any `json:"agentInfo"`
|
|
17
|
+
AuthMethods []map[string]any `json:"authMethods"`
|
|
18
|
+
AgentCapabilities AgentCapabilities `json:"agentCapabilities"`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// AgentCapabilities are the capabilities the adapter advertises: only what it implements.
|
|
22
|
+
type AgentCapabilities struct {
|
|
23
|
+
LoadSession bool `json:"loadSession"`
|
|
24
|
+
McpCapabilities McpCapabilities `json:"mcpCapabilities"`
|
|
25
|
+
PromptCapabilities PromptCapabilities `json:"promptCapabilities"`
|
|
26
|
+
SessionCapabilities SessionCapabilities `json:"sessionCapabilities"`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// McpCapabilities: MCP servers are accepted and stored but never started by the adapter.
|
|
30
|
+
type McpCapabilities struct {
|
|
31
|
+
HTTP bool `json:"http"`
|
|
32
|
+
SSE bool `json:"sse"`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// PromptCapabilities lists the prompt content the adapter accepts.
|
|
36
|
+
type PromptCapabilities struct {
|
|
37
|
+
Image bool `json:"image"`
|
|
38
|
+
Audio bool `json:"audio"`
|
|
39
|
+
EmbeddedContext bool `json:"embeddedContext"`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// SessionCapabilities advertises the session/list and session/delete methods.
|
|
43
|
+
type SessionCapabilities struct {
|
|
44
|
+
List map[string]any `json:"list"`
|
|
45
|
+
Delete map[string]any `json:"delete"`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// NewSessionRequest is `session/new` params.
|
|
49
|
+
type NewSessionRequest struct {
|
|
50
|
+
Cwd string `json:"cwd"`
|
|
51
|
+
McpServers []any `json:"mcpServers"`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// LoadSessionRequest is `session/load` params.
|
|
55
|
+
type LoadSessionRequest struct {
|
|
56
|
+
SessionID string `json:"sessionId"`
|
|
57
|
+
Cwd string `json:"cwd"`
|
|
58
|
+
McpServers []any `json:"mcpServers"`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ConfigSelectOption is one option of a select config option.
|
|
62
|
+
type ConfigSelectOption struct {
|
|
63
|
+
Value string `json:"value"`
|
|
64
|
+
Name string `json:"name"`
|
|
65
|
+
Description *string `json:"description"`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ConfigOption is a session config option (this adapter only advertises selects).
|
|
69
|
+
type ConfigOption struct {
|
|
70
|
+
Type string `json:"type"`
|
|
71
|
+
ID string `json:"id"`
|
|
72
|
+
Category string `json:"category"`
|
|
73
|
+
Name string `json:"name"`
|
|
74
|
+
Description string `json:"description"`
|
|
75
|
+
CurrentValue string `json:"currentValue"`
|
|
76
|
+
Options []ConfigSelectOption `json:"options"`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// AdvertisedModel is one model in the unstable `models` state.
|
|
80
|
+
type AdvertisedModel struct {
|
|
81
|
+
ModelID string `json:"modelId"`
|
|
82
|
+
Name string `json:"name"`
|
|
83
|
+
Description *string `json:"description"`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ModelState is the unstable session model state.
|
|
87
|
+
type ModelState struct {
|
|
88
|
+
AvailableModels []AdvertisedModel `json:"availableModels"`
|
|
89
|
+
CurrentModelID string `json:"currentModelId"`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Mode is one thinking level, advertised as an ACP session mode.
|
|
93
|
+
type Mode struct {
|
|
94
|
+
ID string `json:"id"`
|
|
95
|
+
Name string `json:"name"`
|
|
96
|
+
Description *string `json:"description"`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ModeState is the session mode state.
|
|
100
|
+
type ModeState struct {
|
|
101
|
+
AvailableModes []Mode `json:"availableModes"`
|
|
102
|
+
CurrentModeID string `json:"currentModeId"`
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// NewSessionResponse is `session/new` result.
|
|
106
|
+
type NewSessionResponse struct {
|
|
107
|
+
SessionID string `json:"sessionId"`
|
|
108
|
+
ConfigOptions []ConfigOption `json:"configOptions"`
|
|
109
|
+
Models *ModelState `json:"models"`
|
|
110
|
+
Modes ModeState `json:"modes"`
|
|
111
|
+
Meta map[string]any `json:"_meta"`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// LoadSessionResponse is `session/load` result.
|
|
115
|
+
type LoadSessionResponse struct {
|
|
116
|
+
ConfigOptions []ConfigOption `json:"configOptions"`
|
|
117
|
+
Models *ModelState `json:"models"`
|
|
118
|
+
Modes ModeState `json:"modes"`
|
|
119
|
+
Meta map[string]any `json:"_meta"`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// PromptRequest is `session/prompt` params.
|
|
123
|
+
type PromptRequest struct {
|
|
124
|
+
SessionID string `json:"sessionId"`
|
|
125
|
+
Prompt []ContentBlock `json:"prompt"`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// PromptResponse is `session/prompt` result.
|
|
129
|
+
type PromptResponse struct {
|
|
130
|
+
StopReason StopReason `json:"stopReason"`
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ListSessionsRequest is `session/list` params.
|
|
134
|
+
type ListSessionsRequest struct {
|
|
135
|
+
Cwd *string `json:"cwd"`
|
|
136
|
+
Cursor *string `json:"cursor"`
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// SessionInfo is one entry of `session/list`.
|
|
140
|
+
type SessionInfo struct {
|
|
141
|
+
SessionID string `json:"sessionId"`
|
|
142
|
+
Cwd string `json:"cwd"`
|
|
143
|
+
Title *string `json:"title"`
|
|
144
|
+
UpdatedAt *string `json:"updatedAt"`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ListSessionsResponse is `session/list` result.
|
|
148
|
+
type ListSessionsResponse struct {
|
|
149
|
+
Sessions []SessionInfo `json:"sessions"`
|
|
150
|
+
NextCursor *string `json:"nextCursor"`
|
|
151
|
+
Meta map[string]any `json:"_meta"`
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// DeleteSessionRequest is `session/delete` params.
|
|
155
|
+
type DeleteSessionRequest struct {
|
|
156
|
+
SessionID string `json:"sessionId"`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// SetSessionModeRequest is `session/set_mode` params. ModeID is any so a non-string value
|
|
160
|
+
// reaches the adapter's own validation.
|
|
161
|
+
type SetSessionModeRequest struct {
|
|
162
|
+
SessionID string `json:"sessionId"`
|
|
163
|
+
ModeID any `json:"modeId"`
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// SetSessionConfigOptionRequest is `session/set_config_option` params. Value is any so a
|
|
167
|
+
// non-string value reaches the adapter's own validation.
|
|
168
|
+
type SetSessionConfigOptionRequest struct {
|
|
169
|
+
SessionID string `json:"sessionId"`
|
|
170
|
+
ConfigID string `json:"configId"`
|
|
171
|
+
Value any `json:"value"`
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// SetSessionConfigOptionResponse is `session/set_config_option` result.
|
|
175
|
+
type SetSessionConfigOptionResponse struct {
|
|
176
|
+
ConfigOptions []ConfigOption `json:"configOptions"`
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// SetSessionModelRequest is the unstable `session/set_model` params.
|
|
180
|
+
type SetSessionModelRequest struct {
|
|
181
|
+
SessionID string `json:"sessionId"`
|
|
182
|
+
ModelID string `json:"modelId"`
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// AuthenticateRequest is `authenticate` params.
|
|
186
|
+
type AuthenticateRequest struct {
|
|
187
|
+
MethodID string `json:"methodId"`
|
|
188
|
+
}
|