@pi-in-go/pigpen-session-ingest 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 ADDED
@@ -0,0 +1,15 @@
1
+ # Credits
2
+
3
+ `extensions/session-ingest` is original work by **Michael Kinsy**, moved into
4
+ Pigpen from the author's personal PiG configuration and released under Pigpen's
5
+ MIT license (see [LICENSE](LICENSE)). It is not derived from another project.
6
+
7
+ It reads the session file format documented by Pi
8
+ (https://github.com/earendil-works/pi, MIT, Mario Zechner); it contains none of
9
+ Pi's code.
10
+
11
+ Changes made when it was moved: ported to PiG's public Go SDK; the `turn`,
12
+ `tools` and `stats` modes, which the original advertised but did not implement,
13
+ were written; output truncation was made rune-safe; the Next hint appears only
14
+ when there is more to read; `query` searches the full text
15
+ (`maxCharsPerItem` bounds the excerpts, not the search).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Kinsy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # session-ingest
2
+
3
+ A Go extension for PiG that adds one tool, `read_session`. It lets the model (or
4
+ you, by asking it) look inside a Pi or PiG session transcript (`.jsonl`) without
5
+ pasting the whole file into the context window. Every mode caps its output at 50 KB /
6
+ 2000 lines and says how to fetch the next page or narrow the request. `toc`, `tools`
7
+ and `stats` summarize; `turn` hides thinking and tool results unless `include` asks
8
+ for them; `slice` and `query` show message text as recorded, tool results included
9
+ (`maxCharsPerItem` shortens each item).
10
+
11
+ ```text
12
+ read_session(path, mode = toc | query | slice | turn | tools | stats, ...)
13
+ ```
14
+
15
+ | Mode | Answers | Output |
16
+ |---|---|---|
17
+ | `toc` (default) | What happened, turn by turn? | One row per user turn: time, request, tools used, errors, cost. |
18
+ | `query` | Where was X discussed? | Ranked hits (`query`, `regex`, `caseSensitive`), each with turn, role, tool, excerpt and the call that fetches it. |
19
+ | `slice` | Show messages N to M. | A page of normalized messages, optionally filtered by `roles`. |
20
+ | `turn` | Show turn N in full. | The user message, assistant messages and tool activity of one turn. `include` picks `text`, `thinking`, `tool_calls`, `tool_results`, `errors`, `usage` (default: text, tool_calls, errors). |
21
+ | `tools` | Which tools ran, on which files? | Calls, results and errors per tool, and the files touched by `read`, `write` and `edit` (`turn` restricts to one turn). |
22
+ | `stats` | How big was it? | Turns, messages by role, tool calls, errors, tokens, cost, models, duration. |
23
+
24
+ Common parameters: `start` and `limit` (paging), `maxCharsPerItem` (truncate long
25
+ text, 0 = do not), `format` (`compact_markdown` by default, or `json`).
26
+
27
+ Not covered: the tool reads every `message` entry in file order. It does not follow
28
+ the session tree, so a forked session shows both branches in sequence; it ignores
29
+ compaction and other non-message entries.
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ pig install ./components/session-ingest
35
+ ```
36
+
37
+ The extension builds from source on first use (Go toolchain required) or fuses into a
38
+ Piglet Binary. It needs no network and no Node runtime. The tool can read any file
39
+ path it is given, like PiG's own `read` tool, and returns text only.
40
+ `pig package validate ./components/session-ingest` validates the Package.
41
+
42
+ ## Test
43
+
44
+ The tests use PiG's public SDK through a fake host. From the Pigpen root:
45
+
46
+ ```sh
47
+ PIG_BIN=/path/to/pig npm run test:go-ports -- -race
48
+ ```
49
+
50
+ MIT. © Michael Kinsy. See [CREDITS.md](CREDITS.md).
@@ -0,0 +1,68 @@
1
+ package sessioningest
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ // read_session parses the whole session file on every call (then slices, queries or tables it). A long session
12
+ // is tens of MB of JSONL, mostly tool output.
13
+ //
14
+ // go test -run xxx -bench . -benchmem
15
+ func benchSession(b *testing.B, messages int) string {
16
+ b.Helper()
17
+ var s strings.Builder
18
+ s.WriteString(`{"type":"session","id":"0199abcd","version":3,"cwd":"/work/repo","timestamp":"2026-01-01T00:00:00Z"}` + "\n")
19
+ for i := 0; i < messages; i++ {
20
+ fmt.Fprintf(&s, `{"type":"message","id":"u%d","timestamp":"2026-01-01T00:00:%02dZ","message":{"role":"user","content":[{"type":"text","text":"please fix the failing test number %d"}]}}`+"\n", i, i%60, i)
21
+ fmt.Fprintf(&s, `{"type":"message","id":"a%d","timestamp":"2026-01-01T00:00:%02dZ","message":{"role":"assistant","content":[{"type":"text","text":"Looking at it now."},{"type":"toolCall","id":"c%d","name":"bash","arguments":{"command":"go test ./..."}}],"usage":{"input":1200,"output":80,"cacheRead":900,"cacheWrite":0,"totalTokens":2180,"cost":{"total":0.0123}}}}`+"\n", i, i%60, i)
22
+ fmt.Fprintf(&s, `{"type":"message","id":"t%d","timestamp":"2026-01-01T00:00:%02dZ","message":{"role":"toolResult","toolCallId":"c%d","toolName":"bash","content":[{"type":"text","text":%q}]}}`+"\n", i, i%60, i, strings.Repeat("ok \tgithub.com/example/pkg\t0.123s\n", 20))
23
+ }
24
+ path := filepath.Join(b.TempDir(), "session.jsonl")
25
+ if err := os.WriteFile(path, []byte(s.String()), 0o600); err != nil {
26
+ b.Fatal(err)
27
+ }
28
+ return path
29
+ }
30
+
31
+ func BenchmarkParseSessionFile(b *testing.B) {
32
+ path := benchSession(b, 2000)
33
+ info, _ := os.Stat(path)
34
+ b.SetBytes(info.Size())
35
+ b.ReportAllocs()
36
+ b.ResetTimer()
37
+ for i := 0; i < b.N; i++ {
38
+ if _, err := parseSessionFile(path, 2000); err != nil {
39
+ b.Fatal(err)
40
+ }
41
+ }
42
+ }
43
+
44
+ func BenchmarkBuildQueryReport(b *testing.B) {
45
+ session, err := parseSessionFile(benchSession(b, 2000), 2000)
46
+ if err != nil {
47
+ b.Fatal(err)
48
+ }
49
+ b.ReportAllocs()
50
+ b.ResetTimer()
51
+ for i := 0; i < b.N; i++ {
52
+ if _, err := buildQueryReport(session, "failing test", false, false, 0, 10, 300); err != nil {
53
+ b.Fatal(err)
54
+ }
55
+ }
56
+ }
57
+
58
+ func BenchmarkBuildTOCReport(b *testing.B) {
59
+ session, err := parseSessionFile(benchSession(b, 2000), 2000)
60
+ if err != nil {
61
+ b.Fatal(err)
62
+ }
63
+ b.ReportAllocs()
64
+ b.ResetTimer()
65
+ for i := 0; i < b.N; i++ {
66
+ buildTOCReport(session, 0, 50)
67
+ }
68
+ }