@poodle64/librarian 2026.9.15 → 2026.9.16
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/dist/client.js +42 -19
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -79,8 +79,14 @@ export async function* ask(options) {
|
|
|
79
79
|
// A session that expired mid-conversation is the shape this catches: the
|
|
80
80
|
// POST is redirected to a login page, which answers 200 with HTML, and a
|
|
81
81
|
// reader whose stream "opened" then waits for frames that can never come.
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
//
|
|
83
|
+
// A response that DECLARES something other than an event stream is refused;
|
|
84
|
+
// one that declares nothing is read anyway. A route behind a proxy that
|
|
85
|
+
// drops the header is still streaming, and refusing it here would be this
|
|
86
|
+
// layer breaking a working consumer over a header — the terminal-frame
|
|
87
|
+
// check at the end of this function catches it if it really says nothing.
|
|
88
|
+
const kind = (response.headers.get('content-type') ?? '').toLowerCase();
|
|
89
|
+
if (!response.ok || !response.body || (kind && !kind.includes('text/event-stream'))) {
|
|
84
90
|
yield { type: 'library_error' };
|
|
85
91
|
return;
|
|
86
92
|
}
|
|
@@ -92,6 +98,25 @@ export async function* ask(options) {
|
|
|
92
98
|
// rather than left to the renderer, which cannot tell a stream that died
|
|
93
99
|
// from one still arriving.
|
|
94
100
|
let terminal = false;
|
|
101
|
+
/** One frame's `data:` lines, as the event they carry. */
|
|
102
|
+
function parse(frame) {
|
|
103
|
+
// `data:` only — the type lives inside the payload where Claude Code
|
|
104
|
+
// puts it, so there is no second place to look.
|
|
105
|
+
const data = frame
|
|
106
|
+
.split('\n')
|
|
107
|
+
.filter((l) => l.startsWith('data:'))
|
|
108
|
+
.map((l) => l.slice(5).trim())
|
|
109
|
+
.join('');
|
|
110
|
+
if (!data)
|
|
111
|
+
return null;
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(data);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// A partial frame at the end of a stream is not an error.
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
95
120
|
for (;;) {
|
|
96
121
|
let chunk;
|
|
97
122
|
try {
|
|
@@ -111,26 +136,24 @@ export async function* ask(options) {
|
|
|
111
136
|
const frame = buffered.slice(0, boundary);
|
|
112
137
|
buffered = buffered.slice(boundary + 2);
|
|
113
138
|
boundary = buffered.indexOf('\n\n');
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const data = frame
|
|
117
|
-
.split('\n')
|
|
118
|
-
.filter((l) => l.startsWith('data:'))
|
|
119
|
-
.map((l) => l.slice(5).trim())
|
|
120
|
-
.join('');
|
|
121
|
-
if (!data)
|
|
139
|
+
const event = parse(frame);
|
|
140
|
+
if (!event)
|
|
122
141
|
continue;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
terminal = true;
|
|
127
|
-
yield event;
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
/* a partial frame at end of stream is not an error */
|
|
131
|
-
}
|
|
142
|
+
if (event.type === 'result' || event.type === 'library_error')
|
|
143
|
+
terminal = true;
|
|
144
|
+
yield event;
|
|
132
145
|
}
|
|
133
146
|
}
|
|
147
|
+
// The last frame of a stream that closed cleanly without its blank line.
|
|
148
|
+
// It is usually the terminal `result`, carrying the duration and the
|
|
149
|
+
// sources, so dropping it both loses the answer's furniture and makes a
|
|
150
|
+
// finished turn look like one that died.
|
|
151
|
+
const last = parse(buffered);
|
|
152
|
+
if (last) {
|
|
153
|
+
if (last.type === 'result' || last.type === 'library_error')
|
|
154
|
+
terminal = true;
|
|
155
|
+
yield last;
|
|
156
|
+
}
|
|
134
157
|
if (!terminal && !options.signal?.aborted)
|
|
135
158
|
yield { type: 'library_error' };
|
|
136
159
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poodle64/librarian",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.16",
|
|
4
4
|
"description": "An agent's conversation surface as a consumable Svelte 5 package: the stream client, transcript state and self-styling chat components (transcript, composer, markdown) every household app renders instead of rebuilding, speaking as whichever persona the app names.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|