@oh-my-pi/pi-ai 14.7.2 → 14.7.4
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/package.json +3 -3
- package/src/providers/anthropic.ts +2 -144
- package/src/utils/event-stream.ts +1 -100
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "14.7.
|
|
4
|
+
"version": "14.7.4",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"@aws-sdk/credential-provider-node": "^3.972.39",
|
|
47
47
|
"@bufbuild/protobuf": "^2.12.0",
|
|
48
48
|
"@google/genai": "^1.52.0",
|
|
49
|
-
"@oh-my-pi/pi-natives": "14.7.
|
|
50
|
-
"@oh-my-pi/pi-utils": "14.7.
|
|
49
|
+
"@oh-my-pi/pi-natives": "14.7.4",
|
|
50
|
+
"@oh-my-pi/pi-utils": "14.7.4",
|
|
51
51
|
"@sinclair/typebox": "^0.34.49",
|
|
52
52
|
"@smithy/node-http-handler": "^4.6.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
MessageParam,
|
|
9
9
|
RawMessageStreamEvent,
|
|
10
10
|
} from "@anthropic-ai/sdk/resources/messages";
|
|
11
|
-
import { $env, abortableSleep, isEnoent } from "@oh-my-pi/pi-utils";
|
|
11
|
+
import { $env, abortableSleep, isEnoent, readSseEvents } from "@oh-my-pi/pi-utils";
|
|
12
12
|
import { hasOpus47ApiRestrictions, mapEffortToAnthropicAdaptiveEffort } from "../model-thinking";
|
|
13
13
|
import { calculateCost } from "../models";
|
|
14
14
|
import { getEnvApiKey, OUTPUT_FALLBACK_BUFFER } from "../stream";
|
|
@@ -658,18 +658,6 @@ function mergeHeaders(...headerSources: (Record<string, string> | undefined)[]):
|
|
|
658
658
|
// We surface the resulting provider error ourselves, so keep the SDK quiet.
|
|
659
659
|
const ANTHROPIC_SDK_LOG_LEVEL = "off" as const;
|
|
660
660
|
|
|
661
|
-
interface ServerSentEvent {
|
|
662
|
-
event: string | null;
|
|
663
|
-
data: string;
|
|
664
|
-
raw: string[];
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
interface SseDecoderState {
|
|
668
|
-
event: string | null;
|
|
669
|
-
data: string[];
|
|
670
|
-
raw: string[];
|
|
671
|
-
}
|
|
672
|
-
|
|
673
661
|
const ANTHROPIC_MESSAGE_EVENTS: ReadonlySet<string> = new Set([
|
|
674
662
|
"message_start",
|
|
675
663
|
"message_delta",
|
|
@@ -679,136 +667,6 @@ const ANTHROPIC_MESSAGE_EVENTS: ReadonlySet<string> = new Set([
|
|
|
679
667
|
"content_block_stop",
|
|
680
668
|
]);
|
|
681
669
|
|
|
682
|
-
function flushSseEvent(state: SseDecoderState): ServerSentEvent | null {
|
|
683
|
-
if (!state.event && state.data.length === 0) {
|
|
684
|
-
return null;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
const event: ServerSentEvent = {
|
|
688
|
-
event: state.event,
|
|
689
|
-
data: state.data.join("\n"),
|
|
690
|
-
raw: [...state.raw],
|
|
691
|
-
};
|
|
692
|
-
state.event = null;
|
|
693
|
-
state.data = [];
|
|
694
|
-
state.raw = [];
|
|
695
|
-
return event;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
function decodeSseLine(line: string, state: SseDecoderState): ServerSentEvent | null {
|
|
699
|
-
if (line === "") {
|
|
700
|
-
return flushSseEvent(state);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
state.raw.push(line);
|
|
704
|
-
if (line.startsWith(":")) {
|
|
705
|
-
return null;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
const delimiterIndex = line.indexOf(":");
|
|
709
|
-
const fieldName = delimiterIndex === -1 ? line : line.slice(0, delimiterIndex);
|
|
710
|
-
let value = delimiterIndex === -1 ? "" : line.slice(delimiterIndex + 1);
|
|
711
|
-
if (value.startsWith(" ")) {
|
|
712
|
-
value = value.slice(1);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
if (fieldName === "event") {
|
|
716
|
-
state.event = value;
|
|
717
|
-
} else if (fieldName === "data") {
|
|
718
|
-
state.data.push(value);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
return null;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
function nextLineBreakIndex(text: string): number {
|
|
725
|
-
const carriageReturnIndex = text.indexOf("\r");
|
|
726
|
-
const newlineIndex = text.indexOf("\n");
|
|
727
|
-
if (carriageReturnIndex === -1) {
|
|
728
|
-
return newlineIndex;
|
|
729
|
-
}
|
|
730
|
-
if (newlineIndex === -1) {
|
|
731
|
-
return carriageReturnIndex;
|
|
732
|
-
}
|
|
733
|
-
return Math.min(carriageReturnIndex, newlineIndex);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function consumeLine(text: string): { line: string; rest: string } | null {
|
|
737
|
-
const lineBreakIndex = nextLineBreakIndex(text);
|
|
738
|
-
if (lineBreakIndex === -1) {
|
|
739
|
-
return null;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
let nextIndex = lineBreakIndex + 1;
|
|
743
|
-
if (text[lineBreakIndex] === "\r" && text[nextIndex] === "\n") {
|
|
744
|
-
nextIndex += 1;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
return {
|
|
748
|
-
line: text.slice(0, lineBreakIndex),
|
|
749
|
-
rest: text.slice(nextIndex),
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
async function* iterateSseMessages(
|
|
754
|
-
body: ReadableStream<Uint8Array>,
|
|
755
|
-
signal?: AbortSignal,
|
|
756
|
-
): AsyncGenerator<ServerSentEvent> {
|
|
757
|
-
const reader = body.getReader();
|
|
758
|
-
const decoder = new TextDecoder();
|
|
759
|
-
const state: SseDecoderState = { event: null, data: [], raw: [] };
|
|
760
|
-
let buffer = "";
|
|
761
|
-
|
|
762
|
-
try {
|
|
763
|
-
while (true) {
|
|
764
|
-
if (signal?.aborted) {
|
|
765
|
-
throw new Error("Request was aborted");
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
const { value, done } = await reader.read();
|
|
769
|
-
if (done) {
|
|
770
|
-
break;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
buffer += decoder.decode(value, { stream: true });
|
|
774
|
-
let consumed = consumeLine(buffer);
|
|
775
|
-
while (consumed) {
|
|
776
|
-
buffer = consumed.rest;
|
|
777
|
-
const event = decodeSseLine(consumed.line, state);
|
|
778
|
-
if (event) {
|
|
779
|
-
yield event;
|
|
780
|
-
}
|
|
781
|
-
consumed = consumeLine(buffer);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
buffer += decoder.decode();
|
|
786
|
-
let consumed = consumeLine(buffer);
|
|
787
|
-
while (consumed) {
|
|
788
|
-
buffer = consumed.rest;
|
|
789
|
-
const event = decodeSseLine(consumed.line, state);
|
|
790
|
-
if (event) {
|
|
791
|
-
yield event;
|
|
792
|
-
}
|
|
793
|
-
consumed = consumeLine(buffer);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
if (buffer.length > 0) {
|
|
797
|
-
const event = decodeSseLine(buffer, state);
|
|
798
|
-
if (event) {
|
|
799
|
-
yield event;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
const trailingEvent = flushSseEvent(state);
|
|
804
|
-
if (trailingEvent) {
|
|
805
|
-
yield trailingEvent;
|
|
806
|
-
}
|
|
807
|
-
} finally {
|
|
808
|
-
reader.releaseLock();
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
|
|
812
670
|
async function* iterateAnthropicEvents(
|
|
813
671
|
response: Response,
|
|
814
672
|
signal?: AbortSignal,
|
|
@@ -820,7 +678,7 @@ async function* iterateAnthropicEvents(
|
|
|
820
678
|
let sawMessageStart = false;
|
|
821
679
|
let sawMessageEnd = false;
|
|
822
680
|
|
|
823
|
-
for await (const sse of
|
|
681
|
+
for await (const sse of readSseEvents(response.body, signal)) {
|
|
824
682
|
if (sse.event === "error") {
|
|
825
683
|
throw new Error(sse.data);
|
|
826
684
|
}
|
|
@@ -105,23 +105,7 @@ export class EventStream<T, R = T> implements AsyncIterable<T> {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
// Delta events that can be batched for throttling
|
|
109
|
-
type DeltaEvent =
|
|
110
|
-
| { type: "text_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
|
|
111
|
-
| { type: "thinking_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
|
|
112
|
-
| { type: "toolcall_delta"; contentIndex: number; delta: string; partial: AssistantMessage };
|
|
113
|
-
|
|
114
|
-
function isDeltaEvent(event: AssistantMessageEvent): event is DeltaEvent {
|
|
115
|
-
return event.type === "text_delta" || event.type === "thinking_delta" || event.type === "toolcall_delta";
|
|
116
|
-
}
|
|
117
|
-
|
|
118
108
|
export class AssistantMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
|
|
119
|
-
// Throttling state
|
|
120
|
-
#deltaBuffer: DeltaEvent[] = [];
|
|
121
|
-
#flushTimer?: NodeJS.Timeout;
|
|
122
|
-
#lastFlushTime = 0;
|
|
123
|
-
readonly #throttleMs = 50; // 20 updates/sec
|
|
124
|
-
|
|
125
109
|
constructor() {
|
|
126
110
|
super(
|
|
127
111
|
event => event.type === "done" || event.type === "error",
|
|
@@ -139,103 +123,20 @@ export class AssistantMessageEventStream extends EventStream<AssistantMessageEve
|
|
|
139
123
|
override push(event: AssistantMessageEvent): void {
|
|
140
124
|
if (this.done) return;
|
|
141
125
|
|
|
142
|
-
//
|
|
126
|
+
// Completion resolves the final result and still emits the terminal event.
|
|
143
127
|
if (this.isComplete(event)) {
|
|
144
|
-
this.#flushDeltas(); // Flush any pending deltas before completing
|
|
145
128
|
this.done = true;
|
|
146
129
|
this.resolveFinalResult(this.extractResult(event));
|
|
147
130
|
}
|
|
148
131
|
|
|
149
|
-
// Delta events get batched and throttled
|
|
150
|
-
if (isDeltaEvent(event)) {
|
|
151
|
-
this.#deltaBuffer.push(event);
|
|
152
|
-
this.#scheduleFlush();
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Non-delta events flush pending deltas immediately, then emit
|
|
157
|
-
this.#flushDeltas();
|
|
158
132
|
this.deliver(event);
|
|
159
133
|
}
|
|
160
134
|
|
|
161
135
|
override end(result?: AssistantMessage): void {
|
|
162
|
-
this.#flushDeltas();
|
|
163
136
|
this.done = true;
|
|
164
137
|
if (result !== undefined) {
|
|
165
138
|
this.resolveFinalResult(result);
|
|
166
139
|
}
|
|
167
140
|
this.endWaiting();
|
|
168
141
|
}
|
|
169
|
-
|
|
170
|
-
override fail(err: unknown): void {
|
|
171
|
-
if (this.#flushTimer) {
|
|
172
|
-
clearTimeout(this.#flushTimer);
|
|
173
|
-
this.#flushTimer = undefined;
|
|
174
|
-
}
|
|
175
|
-
this.#deltaBuffer = [];
|
|
176
|
-
super.fail(err);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
#scheduleFlush(): void {
|
|
180
|
-
if (this.#flushTimer) return; // Already scheduled
|
|
181
|
-
|
|
182
|
-
const now = Bun.nanoseconds();
|
|
183
|
-
const timeSinceLastFlush = (now - this.#lastFlushTime) / 1e6;
|
|
184
|
-
|
|
185
|
-
if (timeSinceLastFlush >= this.#throttleMs) {
|
|
186
|
-
// Flush immediately if throttle window has passed
|
|
187
|
-
this.#flushDeltas();
|
|
188
|
-
} else {
|
|
189
|
-
// Schedule flush for when throttle window expires
|
|
190
|
-
const delay = this.#throttleMs - timeSinceLastFlush;
|
|
191
|
-
this.#flushTimer = setTimeout(() => {
|
|
192
|
-
this.#flushTimer = undefined;
|
|
193
|
-
this.#flushDeltas();
|
|
194
|
-
}, delay);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
#flushDeltas(): void {
|
|
199
|
-
if (this.#flushTimer) {
|
|
200
|
-
clearTimeout(this.#flushTimer);
|
|
201
|
-
this.#flushTimer = undefined;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (this.#deltaBuffer.length === 0) return;
|
|
205
|
-
|
|
206
|
-
// Merge consecutive deltas for the same content block and type
|
|
207
|
-
const merged = this.#mergeDeltas(this.#deltaBuffer);
|
|
208
|
-
this.#deltaBuffer = [];
|
|
209
|
-
this.#lastFlushTime = Bun.nanoseconds();
|
|
210
|
-
|
|
211
|
-
for (const event of merged) {
|
|
212
|
-
this.deliver(event);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
#mergeDeltas(deltas: DeltaEvent[]): AssistantMessageEvent[] {
|
|
217
|
-
if (deltas.length === 0) return [];
|
|
218
|
-
if (deltas.length === 1) return [deltas[0]];
|
|
219
|
-
|
|
220
|
-
const result: AssistantMessageEvent[] = [];
|
|
221
|
-
let current = deltas[0];
|
|
222
|
-
|
|
223
|
-
for (let i = 1; i < deltas.length; i++) {
|
|
224
|
-
const next = deltas[i];
|
|
225
|
-
// Can merge if same type, same content index
|
|
226
|
-
if (next.type === current.type && next.contentIndex === current.contentIndex) {
|
|
227
|
-
current = {
|
|
228
|
-
...current,
|
|
229
|
-
delta: current.delta + next.delta,
|
|
230
|
-
partial: next.partial, // Use latest partial
|
|
231
|
-
} as DeltaEvent;
|
|
232
|
-
} else {
|
|
233
|
-
result.push(current);
|
|
234
|
-
current = next;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
result.push(current);
|
|
238
|
-
|
|
239
|
-
return result;
|
|
240
|
-
}
|
|
241
142
|
}
|