@oh-my-pi/pi-ai 16.5.2 → 17.0.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/CHANGELOG.md +11 -0
- package/package.json +4 -4
- package/src/providers/amazon-bedrock.ts +6 -1
- package/src/providers/cursor.ts +46 -49
- package/src/providers/ollama.ts +2 -32
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.0.0] - 2026-07-15
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Improved Ollama streaming performance by parsing NDJSON response bytes directly instead of decoding and buffering network chunks as text.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed Cursor TLS connection resets causing process-fatal uncaught exceptions, allowing the active turn to fail or retry gracefully without terminating the session.
|
|
14
|
+
- Fixed Amazon Bedrock stream error handling to correctly handle non-Error values that cannot be serialized by JSON.stringify.
|
|
15
|
+
|
|
5
16
|
## [16.5.2] - 2026-07-14
|
|
6
17
|
|
|
7
18
|
### Added
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "17.0.0",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.1",
|
|
41
|
-
"@oh-my-pi/pi-catalog": "
|
|
42
|
-
"@oh-my-pi/pi-utils": "
|
|
43
|
-
"@oh-my-pi/pi-wire": "
|
|
41
|
+
"@oh-my-pi/pi-catalog": "17.0.0",
|
|
42
|
+
"@oh-my-pi/pi-utils": "17.0.0",
|
|
43
|
+
"@oh-my-pi/pi-wire": "17.0.0",
|
|
44
44
|
"arktype": "2.2.3",
|
|
45
45
|
"zod": "^4"
|
|
46
46
|
},
|
|
@@ -510,7 +510,12 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
|
|
|
510
510
|
for (const block of output.content) {
|
|
511
511
|
if (block.type === "toolCall") clearStreamingPartialJson(block);
|
|
512
512
|
}
|
|
513
|
-
|
|
513
|
+
let baseMessage: string;
|
|
514
|
+
try {
|
|
515
|
+
baseMessage = error instanceof Error ? error.message : (JSON.stringify(error) ?? String(error));
|
|
516
|
+
} catch {
|
|
517
|
+
baseMessage = String(error);
|
|
518
|
+
}
|
|
514
519
|
// Enrich error with thinking block diagnostics for signature-related failures
|
|
515
520
|
let diagnostics = "";
|
|
516
521
|
if (baseMessage.includes("signature") || baseMessage.includes("thinking")) {
|
package/src/providers/cursor.ts
CHANGED
|
@@ -351,6 +351,8 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
351
351
|
let h2Request: http2.ClientHttp2Stream | null = null;
|
|
352
352
|
let heartbeatTimer: NodeJS.Timeout | null = null;
|
|
353
353
|
let debugResponseLogPromise: Promise<RequestDebugResponseLog | undefined> | undefined;
|
|
354
|
+
const h2Completion = Promise.withResolvers<void>();
|
|
355
|
+
let resolveH2: (() => void) | undefined = h2Completion.resolve;
|
|
354
356
|
|
|
355
357
|
try {
|
|
356
358
|
const apiKey = options?.apiKey;
|
|
@@ -406,6 +408,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
406
408
|
} else {
|
|
407
409
|
h2Client = http2.connect(baseUrl);
|
|
408
410
|
}
|
|
411
|
+
h2Client.on("error", h2Completion.reject);
|
|
409
412
|
|
|
410
413
|
h2Request = h2Client.request(requestHeaders);
|
|
411
414
|
|
|
@@ -449,8 +452,6 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
449
452
|
conversationStateCache.set(conversationId, checkpoint);
|
|
450
453
|
};
|
|
451
454
|
|
|
452
|
-
let resolveH2: (() => void) | undefined;
|
|
453
|
-
|
|
454
455
|
h2Request.on("response", headers => {
|
|
455
456
|
debugResponseLogPromise = debugSession?.openResponseLog(
|
|
456
457
|
`HTTP/2 ${headers[":status"] ?? ""}`.trim(),
|
|
@@ -517,8 +518,6 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
517
518
|
}
|
|
518
519
|
});
|
|
519
520
|
|
|
520
|
-
h2Request.write(frameConnectMessage(requestBytes));
|
|
521
|
-
|
|
522
521
|
const sendHeartbeat = () => {
|
|
523
522
|
if (!h2Request || h2Request.closed) {
|
|
524
523
|
return;
|
|
@@ -530,57 +529,55 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
530
529
|
h2Request.write(frameConnectMessage(heartbeatBytes));
|
|
531
530
|
};
|
|
532
531
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
532
|
+
const closeDebugLog = async (): Promise<void> => {
|
|
533
|
+
const log = await debugResponseLogPromise;
|
|
534
|
+
await log?.close();
|
|
535
|
+
};
|
|
537
536
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
537
|
+
h2Request.on("trailers", trailers => {
|
|
538
|
+
const status = trailers["grpc-status"];
|
|
539
|
+
const msg = trailers["grpc-message"];
|
|
540
|
+
if (status && status !== "0") {
|
|
541
|
+
void closeDebugLog().finally(() => {
|
|
542
|
+
h2Completion.reject(
|
|
543
|
+
new AIError.ProviderResponseError(
|
|
544
|
+
`gRPC error ${status}: ${decodeURIComponent(String(msg || ""))}`,
|
|
545
|
+
{ kind: "envelope" },
|
|
546
|
+
),
|
|
547
|
+
);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
});
|
|
542
551
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
reject(
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
});
|
|
552
|
+
h2Request.on("end", () => {
|
|
553
|
+
resolveH2 = undefined;
|
|
554
|
+
void closeDebugLog()
|
|
555
|
+
.then(() => {
|
|
556
|
+
if (endStreamError) {
|
|
557
|
+
h2Completion.reject(endStreamError);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
h2Completion.resolve();
|
|
561
|
+
})
|
|
562
|
+
.catch(h2Completion.reject);
|
|
563
|
+
});
|
|
557
564
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
.then(() => {
|
|
562
|
-
if (endStreamError) {
|
|
563
|
-
reject(endStreamError);
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
resolve();
|
|
567
|
-
})
|
|
568
|
-
.catch(reject);
|
|
569
|
-
});
|
|
565
|
+
h2Request.on("error", error => {
|
|
566
|
+
void closeDebugLog().finally(() => h2Completion.reject(error));
|
|
567
|
+
});
|
|
570
568
|
|
|
571
|
-
|
|
572
|
-
|
|
569
|
+
if (options?.signal) {
|
|
570
|
+
options.signal.addEventListener("abort", () => {
|
|
571
|
+
h2Request?.close();
|
|
572
|
+
void closeDebugLog().finally(() => {
|
|
573
|
+
h2Completion.reject(new AIError.AbortError());
|
|
574
|
+
});
|
|
573
575
|
});
|
|
576
|
+
}
|
|
574
577
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
void closeDebugLog().finally(() => {
|
|
579
|
-
reject(new AIError.AbortError());
|
|
580
|
-
});
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
});
|
|
578
|
+
h2Request.write(frameConnectMessage(requestBytes));
|
|
579
|
+
heartbeatTimer = setInterval(sendHeartbeat, 5000);
|
|
580
|
+
await h2Completion.promise;
|
|
584
581
|
|
|
585
582
|
endCurrentTextBlock(output, stream, state);
|
|
586
583
|
endCurrentThinkingBlock(output, stream, state);
|
package/src/providers/ollama.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetchWithRetry, parseStreamingJson } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import { fetchWithRetry, parseStreamingJson, readJsonl } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import * as AIError from "../error";
|
|
3
3
|
import { getEnvApiKey } from "../stream";
|
|
4
4
|
import type {
|
|
@@ -347,36 +347,6 @@ async function captureHttpErrorResponse(response: Response): Promise<CapturedHtt
|
|
|
347
347
|
};
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
async function* iterateNdjson(stream: ReadableStream<Uint8Array>): AsyncGenerator<OllamaChatChunk> {
|
|
351
|
-
const reader = stream.getReader();
|
|
352
|
-
const decoder = new TextDecoder();
|
|
353
|
-
let buffer = "";
|
|
354
|
-
while (true) {
|
|
355
|
-
const { done, value } = await reader.read();
|
|
356
|
-
if (done) {
|
|
357
|
-
break;
|
|
358
|
-
}
|
|
359
|
-
buffer += decoder.decode(value, { stream: true });
|
|
360
|
-
while (true) {
|
|
361
|
-
const newlineIndex = buffer.indexOf("\n");
|
|
362
|
-
if (newlineIndex < 0) {
|
|
363
|
-
break;
|
|
364
|
-
}
|
|
365
|
-
const line = buffer.slice(0, newlineIndex).trim();
|
|
366
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
367
|
-
if (!line) {
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
yield JSON.parse(line) as OllamaChatChunk;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
buffer += decoder.decode();
|
|
374
|
-
const tail = buffer.trim();
|
|
375
|
-
if (tail) {
|
|
376
|
-
yield JSON.parse(tail) as OllamaChatChunk;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
350
|
function createEmptyOutput(model: Model<"ollama-chat">): AssistantMessage {
|
|
381
351
|
return {
|
|
382
352
|
role: "assistant",
|
|
@@ -622,7 +592,7 @@ const streamOllamaOnce = (
|
|
|
622
592
|
});
|
|
623
593
|
}
|
|
624
594
|
stream.push({ type: "start", partial: output });
|
|
625
|
-
for await (const chunk of
|
|
595
|
+
for await (const chunk of readJsonl<OllamaChatChunk>(response.body)) {
|
|
626
596
|
if (chunk.message?.thinking) {
|
|
627
597
|
suppressHealedThinking = true;
|
|
628
598
|
endActiveTextBlock();
|