@polpo-ai/sdk 0.15.76 → 0.15.78
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/README.md +52 -4
- package/dist/__tests__/polpo-client.test.js +189 -0
- package/dist/__tests__/polpo-client.test.js.map +1 -1
- package/dist/client/polpo-client.d.ts +28 -6
- package/dist/client/polpo-client.d.ts.map +1 -1
- package/dist/client/polpo-client.js +321 -72
- package/dist/client/polpo-client.js.map +1 -1
- package/dist/client/types.d.ts +25 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -9,6 +9,8 @@ export class ChatCompletionStream {
|
|
|
9
9
|
sessionId = null;
|
|
10
10
|
/** Active Run id used by steering APIs. Available after start() or first next(). */
|
|
11
11
|
runId = null;
|
|
12
|
+
/** Last fully processed durable SSE cursor. */
|
|
13
|
+
lastEventId = null;
|
|
12
14
|
/** If the stream ended with finish_reason "ask_user", this contains the questions. */
|
|
13
15
|
askUser = null;
|
|
14
16
|
/** Suggested next messages emitted before the stream closes. */
|
|
@@ -27,6 +29,7 @@ export class ChatCompletionStream {
|
|
|
27
29
|
aborted = false;
|
|
28
30
|
fetchFn;
|
|
29
31
|
url;
|
|
32
|
+
runsUrl;
|
|
30
33
|
clientHeaders;
|
|
31
34
|
req;
|
|
32
35
|
reader = null;
|
|
@@ -34,18 +37,70 @@ export class ChatCompletionStream {
|
|
|
34
37
|
buffer = "";
|
|
35
38
|
started = false;
|
|
36
39
|
abortController = new AbortController();
|
|
37
|
-
|
|
40
|
+
resumeMode = false;
|
|
41
|
+
resumeAfter;
|
|
42
|
+
detached = false;
|
|
43
|
+
terminal = false;
|
|
44
|
+
connectionStateListeners = new Set();
|
|
45
|
+
constructor(fetchFn, url, runsUrl, clientHeaders, req) {
|
|
38
46
|
this.fetchFn = fetchFn;
|
|
39
47
|
this.url = url;
|
|
48
|
+
this.runsUrl = runsUrl;
|
|
40
49
|
this.clientHeaders = clientHeaders;
|
|
41
50
|
this.req = req;
|
|
42
51
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Abort the in-flight stream. Cancels the fetch request and closes the reader.
|
|
45
|
-
* The server will detect the disconnect and stop generating.
|
|
46
|
-
*/
|
|
52
|
+
/** Backward-compatible cancel command. Use detach() to keep a durable run alive. */
|
|
47
53
|
abort() {
|
|
48
54
|
this.aborted = true;
|
|
55
|
+
void this.cancel().catch(() => { });
|
|
56
|
+
}
|
|
57
|
+
/** Observe transport state without implementing a second SSE parser. */
|
|
58
|
+
subscribeConnectionState(listener) {
|
|
59
|
+
this.connectionStateListeners.add(listener);
|
|
60
|
+
return () => { this.connectionStateListeners.delete(listener); };
|
|
61
|
+
}
|
|
62
|
+
/** Close only this subscriber. A durable run continues server-side. */
|
|
63
|
+
detach() {
|
|
64
|
+
this.detached = true;
|
|
65
|
+
this.closeLocalStream();
|
|
66
|
+
}
|
|
67
|
+
/** Explicitly cancel the underlying run, then close this subscriber. */
|
|
68
|
+
async cancel(reason) {
|
|
69
|
+
this.aborted = true;
|
|
70
|
+
const runId = this.runId;
|
|
71
|
+
this.closeLocalStream();
|
|
72
|
+
if (!runId)
|
|
73
|
+
return;
|
|
74
|
+
const res = await this.fetchFn(`${this.runsUrl}/${encodeURIComponent(runId)}/cancel`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
...authorizationHeader(this.clientHeaders),
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify(reason ? { reason } : {}),
|
|
81
|
+
});
|
|
82
|
+
if (!res.ok)
|
|
83
|
+
throw await responseError(res, "Run cancellation failed");
|
|
84
|
+
}
|
|
85
|
+
/** Reattach this stream to its existing run after the last processed cursor. */
|
|
86
|
+
resume(options = {}) {
|
|
87
|
+
if (!this.runId) {
|
|
88
|
+
throw new PolpoApiError("Cannot resume before the server returns a run id", "VALIDATION_ERROR", 400);
|
|
89
|
+
}
|
|
90
|
+
this.closeLocalStream();
|
|
91
|
+
this.abortController = new AbortController();
|
|
92
|
+
this.decoder = new TextDecoder();
|
|
93
|
+
this.buffer = "";
|
|
94
|
+
this.reader = null;
|
|
95
|
+
this.started = false;
|
|
96
|
+
this.aborted = false;
|
|
97
|
+
this.detached = false;
|
|
98
|
+
this.terminal = false;
|
|
99
|
+
this.resumeMode = true;
|
|
100
|
+
this.resumeAfter = options.after ?? this.lastEventId ?? undefined;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
closeLocalStream() {
|
|
49
104
|
this.abortController.abort();
|
|
50
105
|
this.reader?.cancel().catch(() => { });
|
|
51
106
|
}
|
|
@@ -60,96 +115,231 @@ export class ChatCompletionStream {
|
|
|
60
115
|
this.started = true;
|
|
61
116
|
const headers = {
|
|
62
117
|
"Content-Type": "application/json",
|
|
118
|
+
...authorizationHeader(this.clientHeaders),
|
|
63
119
|
};
|
|
64
|
-
if (this.
|
|
65
|
-
headers["
|
|
120
|
+
if (this.resumeMode && this.resumeAfter !== undefined) {
|
|
121
|
+
headers["Last-Event-ID"] = this.resumeAfter;
|
|
66
122
|
}
|
|
67
123
|
if (this.req.sessionId) {
|
|
68
124
|
headers["x-session-id"] = this.req.sessionId;
|
|
69
125
|
}
|
|
70
126
|
const { sessionId: _, ...body } = this.req;
|
|
71
|
-
const res =
|
|
72
|
-
method: "
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
127
|
+
const res = this.resumeMode
|
|
128
|
+
? await this.fetchFn(`${this.runsUrl}/${encodeURIComponent(this.runId)}/events${this.resumeAfter === undefined ? "" : `?cursor=${encodeURIComponent(this.resumeAfter)}`}`, { method: "GET", headers, signal: this.abortController.signal })
|
|
129
|
+
: await this.fetchFn(this.url, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers,
|
|
132
|
+
body: JSON.stringify({ ...body, stream: true }),
|
|
133
|
+
signal: this.abortController.signal,
|
|
134
|
+
});
|
|
77
135
|
if (!res.ok) {
|
|
78
|
-
|
|
79
|
-
throw new PolpoApiError(err.error?.message ?? "Chat completions failed", res.status === 401 ? "AUTH_REQUIRED" : "INTERNAL_ERROR", res.status);
|
|
136
|
+
throw await responseError(res, this.resumeMode ? "Run resume failed" : "Chat completions failed");
|
|
80
137
|
}
|
|
81
138
|
// Capture session ID from response header
|
|
82
|
-
this.
|
|
83
|
-
|
|
139
|
+
if (!this.resumeMode) {
|
|
140
|
+
this.sessionId = res.headers.get("x-session-id");
|
|
141
|
+
this.runId = res.headers.get("x-polpo-run-id");
|
|
142
|
+
}
|
|
143
|
+
this.terminal = res.headers.get("x-polpo-run-terminal") === "true";
|
|
84
144
|
this.reader = res.body?.getReader() ?? null;
|
|
85
145
|
if (!this.reader)
|
|
86
146
|
throw new PolpoApiError("No response body", "INTERNAL_ERROR", 500);
|
|
147
|
+
this.emitConnectionState("streaming");
|
|
87
148
|
}
|
|
88
149
|
async *[Symbol.asyncIterator]() {
|
|
89
150
|
await this.ensureStarted();
|
|
90
|
-
|
|
151
|
+
if (this.terminal)
|
|
152
|
+
return;
|
|
153
|
+
let reconnectAttempts = 0;
|
|
91
154
|
try {
|
|
92
|
-
while (
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
155
|
+
while (!this.aborted && !this.detached) {
|
|
156
|
+
const reader = this.reader;
|
|
157
|
+
let terminal = false;
|
|
158
|
+
try {
|
|
159
|
+
while (true) {
|
|
160
|
+
const { done, value } = await reader.read();
|
|
161
|
+
if (done)
|
|
162
|
+
break;
|
|
163
|
+
this.buffer += this.decoder.decode(value, { stream: true });
|
|
164
|
+
const parsed = extractSseEvents(this.buffer);
|
|
165
|
+
this.buffer = parsed.remainder;
|
|
166
|
+
for (const event of parsed.events) {
|
|
167
|
+
if (event.id !== undefined)
|
|
168
|
+
this.lastEventId = event.id;
|
|
169
|
+
const projected = projectChatSseData(event.data);
|
|
170
|
+
if (projected.kind === "ignore")
|
|
171
|
+
continue;
|
|
172
|
+
if (projected.kind === "error") {
|
|
173
|
+
throw new PolpoApiError(projected.message, "INTERNAL_ERROR", 500);
|
|
174
|
+
}
|
|
175
|
+
if (projected.kind === "done") {
|
|
176
|
+
terminal = true;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
let chunk;
|
|
180
|
+
try {
|
|
181
|
+
chunk = JSON.parse(projected.data);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
if (this.isDurable()) {
|
|
185
|
+
throw new PolpoApiError("Durable run returned a malformed response chunk", "INTERNAL_ERROR", 502);
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (chunk.polpo?.suggestions) {
|
|
190
|
+
this.suggestions = chunk.polpo.suggestions;
|
|
191
|
+
}
|
|
192
|
+
// Capture ask_user payload from the chunk
|
|
193
|
+
const choice = chunk.choices[0];
|
|
194
|
+
if (choice?.finish_reason === "ask_user" && choice.ask_user) {
|
|
195
|
+
this.askUser = choice.ask_user;
|
|
196
|
+
}
|
|
197
|
+
// Capture mission_preview payload from the chunk
|
|
198
|
+
if (choice?.finish_reason === "mission_preview" && choice.mission_preview) {
|
|
199
|
+
this.missionPreview = choice.mission_preview;
|
|
200
|
+
}
|
|
201
|
+
// Capture vault_preview payload from the chunk
|
|
202
|
+
if (choice?.finish_reason === "vault_preview" && choice.vault_preview) {
|
|
203
|
+
this.vaultPreview = choice.vault_preview;
|
|
204
|
+
}
|
|
205
|
+
// Capture open_file payload from the chunk
|
|
206
|
+
if (choice?.finish_reason === "open_file" && choice.open_file) {
|
|
207
|
+
this.openFile = choice.open_file;
|
|
208
|
+
}
|
|
209
|
+
// Capture navigate_to payload from the chunk
|
|
210
|
+
if (choice?.finish_reason === "navigate_to" && choice.navigate_to) {
|
|
211
|
+
this.navigateTo = choice.navigate_to;
|
|
212
|
+
}
|
|
213
|
+
// Capture open_tab payload from the chunk
|
|
214
|
+
if (choice?.finish_reason === "open_tab" && choice.open_tab) {
|
|
215
|
+
this.openTab = choice.open_tab;
|
|
216
|
+
}
|
|
217
|
+
yield chunk;
|
|
218
|
+
reconnectAttempts = 0;
|
|
110
219
|
}
|
|
111
|
-
// Capture ask_user payload from the chunk
|
|
112
|
-
const choice = chunk.choices[0];
|
|
113
|
-
if (choice?.finish_reason === "ask_user" && choice.ask_user) {
|
|
114
|
-
this.askUser = choice.ask_user;
|
|
115
|
-
}
|
|
116
|
-
// Capture mission_preview payload from the chunk
|
|
117
|
-
if (choice?.finish_reason === "mission_preview" && choice.mission_preview) {
|
|
118
|
-
this.missionPreview = choice.mission_preview;
|
|
119
|
-
}
|
|
120
|
-
// Capture vault_preview payload from the chunk
|
|
121
|
-
if (choice?.finish_reason === "vault_preview" && choice.vault_preview) {
|
|
122
|
-
this.vaultPreview = choice.vault_preview;
|
|
123
|
-
}
|
|
124
|
-
// Capture open_file payload from the chunk
|
|
125
|
-
if (choice?.finish_reason === "open_file" && choice.open_file) {
|
|
126
|
-
this.openFile = choice.open_file;
|
|
127
|
-
}
|
|
128
|
-
// Capture navigate_to payload from the chunk
|
|
129
|
-
if (choice?.finish_reason === "navigate_to" && choice.navigate_to) {
|
|
130
|
-
this.navigateTo = choice.navigate_to;
|
|
131
|
-
}
|
|
132
|
-
// Capture open_tab payload from the chunk
|
|
133
|
-
if (choice?.finish_reason === "open_tab" && choice.open_tab) {
|
|
134
|
-
this.openTab = choice.open_tab;
|
|
135
|
-
}
|
|
136
|
-
yield chunk;
|
|
137
220
|
}
|
|
138
|
-
|
|
139
|
-
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
if (this.aborted || this.detached)
|
|
224
|
+
return;
|
|
225
|
+
if (err instanceof PolpoApiError)
|
|
226
|
+
throw err;
|
|
227
|
+
if (!(err instanceof DOMException && err.name === "AbortError") && !this.isDurable()) {
|
|
228
|
+
throw err;
|
|
140
229
|
}
|
|
141
230
|
}
|
|
231
|
+
if (terminal || this.aborted || this.detached || !this.isDurable() || !this.runId)
|
|
232
|
+
return;
|
|
233
|
+
if (reconnectAttempts >= 5) {
|
|
234
|
+
throw new PolpoApiError("Run stream reconnect limit exceeded", "INTERNAL_ERROR", 503);
|
|
235
|
+
}
|
|
236
|
+
reconnectAttempts += 1;
|
|
237
|
+
this.emitConnectionState("reconnecting");
|
|
238
|
+
await reconnectDelay(reconnectAttempts, this.abortController.signal);
|
|
239
|
+
if (this.aborted || this.detached)
|
|
240
|
+
return;
|
|
241
|
+
this.resume({ after: this.lastEventId ?? undefined });
|
|
242
|
+
await this.ensureStarted();
|
|
142
243
|
}
|
|
143
244
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (err instanceof DOMException && err.name === "AbortError")
|
|
147
|
-
return;
|
|
148
|
-
if (this.aborted)
|
|
149
|
-
return;
|
|
150
|
-
throw err;
|
|
245
|
+
finally {
|
|
246
|
+
this.emitConnectionState("closed");
|
|
151
247
|
}
|
|
152
248
|
}
|
|
249
|
+
isDurable() {
|
|
250
|
+
return this.req.polpo?.delivery?.onDisconnect === "continue";
|
|
251
|
+
}
|
|
252
|
+
emitConnectionState(state) {
|
|
253
|
+
for (const listener of this.connectionStateListeners)
|
|
254
|
+
listener(state);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function extractSseEvents(buffer) {
|
|
258
|
+
const separator = /(?:\r\n|\r|\n){2}/g;
|
|
259
|
+
const blocks = [];
|
|
260
|
+
let offset = 0;
|
|
261
|
+
for (let match = separator.exec(buffer); match; match = separator.exec(buffer)) {
|
|
262
|
+
blocks.push(buffer.slice(offset, match.index));
|
|
263
|
+
offset = match.index + match[0].length;
|
|
264
|
+
}
|
|
265
|
+
const remainder = buffer.slice(offset);
|
|
266
|
+
const events = [];
|
|
267
|
+
for (const block of blocks) {
|
|
268
|
+
let id;
|
|
269
|
+
let event;
|
|
270
|
+
const data = [];
|
|
271
|
+
for (const line of block.split(/\r\n|\r|\n/)) {
|
|
272
|
+
if (line.startsWith(":"))
|
|
273
|
+
continue;
|
|
274
|
+
if (line.startsWith("id:"))
|
|
275
|
+
id = line.slice(3).trimStart();
|
|
276
|
+
else if (line.startsWith("event:"))
|
|
277
|
+
event = line.slice(6).trimStart();
|
|
278
|
+
else if (line.startsWith("data:"))
|
|
279
|
+
data.push(line.slice(5).trimStart());
|
|
280
|
+
}
|
|
281
|
+
if (data.length > 0)
|
|
282
|
+
events.push({ data: data.join("\n"), ...(id === undefined ? {} : { id }), ...(event === undefined ? {} : { event }) });
|
|
283
|
+
}
|
|
284
|
+
return { events, remainder };
|
|
285
|
+
}
|
|
286
|
+
function projectChatSseData(data) {
|
|
287
|
+
if (data === "[DONE]")
|
|
288
|
+
return { kind: "done" };
|
|
289
|
+
try {
|
|
290
|
+
const parsed = JSON.parse(data);
|
|
291
|
+
if ("error" in parsed && parsed.error) {
|
|
292
|
+
return {
|
|
293
|
+
kind: "error",
|
|
294
|
+
message: typeof parsed.error.message === "string"
|
|
295
|
+
? parsed.error.message
|
|
296
|
+
: "Run failed",
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const event = parsed;
|
|
300
|
+
if (event.schemaVersion !== 1 || typeof event.type !== "string") {
|
|
301
|
+
return { kind: "chunk", data };
|
|
302
|
+
}
|
|
303
|
+
if (event.type === "response.done")
|
|
304
|
+
return { kind: "done" };
|
|
305
|
+
if (event.type === "response.chunk" && typeof event.data?.data === "string") {
|
|
306
|
+
return { kind: "chunk", data: event.data.data };
|
|
307
|
+
}
|
|
308
|
+
if (event.type === "run.failed") {
|
|
309
|
+
return {
|
|
310
|
+
kind: "error",
|
|
311
|
+
message: typeof event.data?.message === "string" ? event.data.message : "Run failed",
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (event.type === "run.cancelled")
|
|
315
|
+
return { kind: "done" };
|
|
316
|
+
return { kind: "ignore" };
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return { kind: "chunk", data };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function authorizationHeader(headers) {
|
|
323
|
+
return headers.Authorization ? { Authorization: headers.Authorization } : {};
|
|
324
|
+
}
|
|
325
|
+
async function responseError(response, fallback) {
|
|
326
|
+
const body = await response.json().catch(() => ({ error: { message: response.statusText } }));
|
|
327
|
+
const message = body.error?.message ?? body.error ?? fallback;
|
|
328
|
+
return new PolpoApiError(typeof message === "string" ? message : fallback, response.status === 401 ? "AUTH_REQUIRED" : "INTERNAL_ERROR", response.status);
|
|
329
|
+
}
|
|
330
|
+
function reconnectDelay(attempt, signal) {
|
|
331
|
+
if (signal.aborted)
|
|
332
|
+
return Promise.resolve();
|
|
333
|
+
const delay = Math.min(2_000, 100 * 2 ** (attempt - 1));
|
|
334
|
+
return new Promise((resolve) => {
|
|
335
|
+
const timer = setTimeout(finish, delay);
|
|
336
|
+
function finish() {
|
|
337
|
+
clearTimeout(timer);
|
|
338
|
+
signal.removeEventListener("abort", finish);
|
|
339
|
+
resolve();
|
|
340
|
+
}
|
|
341
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
342
|
+
});
|
|
153
343
|
}
|
|
154
344
|
function scheduleIdentifier(value, label = "Schedule id") {
|
|
155
345
|
if (typeof value !== "string" || !value.trim()) {
|
|
@@ -869,6 +1059,65 @@ export class PolpoClient {
|
|
|
869
1059
|
abortRun(runId, reason) {
|
|
870
1060
|
return this.post(`/runs/${encodeURIComponent(runId)}/abort`, reason === undefined ? {} : { reason });
|
|
871
1061
|
}
|
|
1062
|
+
/** Follow the canonical durable event log for an existing run. */
|
|
1063
|
+
async *streamRunEvents(runId, options = {}) {
|
|
1064
|
+
const query = options.after === undefined
|
|
1065
|
+
? ""
|
|
1066
|
+
: `?cursor=${encodeURIComponent(options.after)}`;
|
|
1067
|
+
const response = await this.fetchFn(`${this.baseUrl}/v1/runs/${encodeURIComponent(runId)}/events${query}`, {
|
|
1068
|
+
method: "GET",
|
|
1069
|
+
headers: {
|
|
1070
|
+
Accept: "text/event-stream",
|
|
1071
|
+
...authorizationHeader(this.headers),
|
|
1072
|
+
...(options.after === undefined ? {} : { "Last-Event-ID": options.after }),
|
|
1073
|
+
},
|
|
1074
|
+
signal: options.signal,
|
|
1075
|
+
});
|
|
1076
|
+
if (!response.ok)
|
|
1077
|
+
throw await responseError(response, "Run event stream failed");
|
|
1078
|
+
const reader = response.body?.getReader();
|
|
1079
|
+
if (!reader)
|
|
1080
|
+
throw new PolpoApiError("No response body", "INTERNAL_ERROR", 500);
|
|
1081
|
+
const decoder = new TextDecoder();
|
|
1082
|
+
let buffer = "";
|
|
1083
|
+
try {
|
|
1084
|
+
while (true) {
|
|
1085
|
+
const { done, value } = await reader.read();
|
|
1086
|
+
if (done)
|
|
1087
|
+
break;
|
|
1088
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1089
|
+
const parsed = extractSseEvents(buffer);
|
|
1090
|
+
buffer = parsed.remainder;
|
|
1091
|
+
for (const item of parsed.events) {
|
|
1092
|
+
try {
|
|
1093
|
+
const event = JSON.parse(item.data);
|
|
1094
|
+
if (event.schemaVersion === 1 && event.runId === runId)
|
|
1095
|
+
yield event;
|
|
1096
|
+
}
|
|
1097
|
+
catch {
|
|
1098
|
+
// Canonical streams ignore malformed transport frames.
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
finally {
|
|
1104
|
+
reader.releaseLock();
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
/** Request idempotent cancellation for a durable run. */
|
|
1108
|
+
async cancelRun(runId, reason) {
|
|
1109
|
+
const response = await this.fetchFn(`${this.baseUrl}/v1/runs/${encodeURIComponent(runId)}/cancel`, {
|
|
1110
|
+
method: "POST",
|
|
1111
|
+
headers: {
|
|
1112
|
+
"Content-Type": "application/json",
|
|
1113
|
+
...authorizationHeader(this.headers),
|
|
1114
|
+
},
|
|
1115
|
+
body: JSON.stringify(reason ? { reason } : {}),
|
|
1116
|
+
});
|
|
1117
|
+
if (!response.ok)
|
|
1118
|
+
throw await responseError(response, "Run cancellation failed");
|
|
1119
|
+
return await response.json();
|
|
1120
|
+
}
|
|
872
1121
|
// ── Chat Completions (OpenAI-compatible) ─────────────────
|
|
873
1122
|
/**
|
|
874
1123
|
* Talk to Polpo via the OpenAI-compatible chat completions endpoint.
|
|
@@ -912,7 +1161,7 @@ export class PolpoClient {
|
|
|
912
1161
|
const reqWithUser = req.user === undefined && this.defaultUser !== undefined
|
|
913
1162
|
? { ...req, user: this.defaultUser }
|
|
914
1163
|
: req;
|
|
915
|
-
return new ChatCompletionStream(this.fetchFn, url, this.headers, reqWithUser);
|
|
1164
|
+
return new ChatCompletionStream(this.fetchFn, url, `${this.baseUrl}/v1/runs`, this.headers, reqWithUser);
|
|
916
1165
|
}
|
|
917
1166
|
// ── Sessions ────────────────────────────────────────────
|
|
918
1167
|
getSessions() {
|