@shpitdev/convex-cliproxy-observability 0.1.1 → 0.2.1
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 +12 -0
- package/README.md +3 -1
- package/dist/capture/index.d.ts +13 -3
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +83 -3
- package/dist/capture/index.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -0
- package/dist/client.js.map +1 -1
- package/dist/model-call/index.d.ts +12 -0
- package/dist/model-call/index.d.ts.map +1 -1
- package/dist/model-call/index.js.map +1 -1
- package/dist/protocols/anthropic-messages.d.ts.map +1 -1
- package/dist/protocols/anthropic-messages.js +6 -0
- package/dist/protocols/anthropic-messages.js.map +1 -1
- package/dist/protocols/checkpoint.d.ts +2 -0
- package/dist/protocols/checkpoint.d.ts.map +1 -1
- package/dist/protocols/checkpoint.js +41 -9
- package/dist/protocols/checkpoint.js.map +1 -1
- package/dist/protocols/framing.d.ts +2 -0
- package/dist/protocols/framing.d.ts.map +1 -1
- package/dist/protocols/framing.js +75 -0
- package/dist/protocols/framing.js.map +1 -1
- package/dist/protocols/index.d.ts +3 -1
- package/dist/protocols/index.d.ts.map +1 -1
- package/dist/protocols/index.js +4 -2
- package/dist/protocols/index.js.map +1 -1
- package/package.json +1 -1
- package/src/capture/index.ts +114 -6
- package/src/client.ts +1 -0
- package/src/model-call/index.ts +13 -0
- package/src/protocols/anthropic-messages.ts +8 -0
- package/src/protocols/checkpoint.ts +44 -9
- package/src/protocols/framing.ts +68 -0
- package/src/protocols/index.ts +6 -2
package/src/protocols/framing.ts
CHANGED
|
@@ -49,6 +49,74 @@ export class SSEReader {
|
|
|
49
49
|
if (this.pending.length > this.maxBytes) throw new Error("SSE frame limit");
|
|
50
50
|
return frames;
|
|
51
51
|
}
|
|
52
|
+
/** Derive frames from pinned stock pre-framer callbacks; stored bytes stay untouched. */
|
|
53
|
+
feedStock(bytes: Uint8Array, protocol?: string, observedAt?: string): SSEFrame[] {
|
|
54
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
55
|
+
if (protocol === "chat_completions" && (this.pending[0] ?? bytes[0]) === 123) {
|
|
56
|
+
if (this.pending.length + bytes.length > this.maxBytes) throw new Error("SSE frame limit");
|
|
57
|
+
const combined = new Uint8Array(this.pending.length + bytes.length);
|
|
58
|
+
combined.set(this.pending);
|
|
59
|
+
combined.set(bytes, this.pending.length);
|
|
60
|
+
this.pending = combined;
|
|
61
|
+
try {
|
|
62
|
+
const data = decoder.decode(combined);
|
|
63
|
+
JSON.parse(data);
|
|
64
|
+
this.pending = new Uint8Array();
|
|
65
|
+
return [{ data, observedAt }];
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (protocol === "responses" && this.pending.length && bytes.length) {
|
|
71
|
+
// Only an event-only field can gain a missing separator. A data: prefix
|
|
72
|
+
// inside fragmented JSON is content, not a new line.
|
|
73
|
+
const event = new TextDecoder().decode(this.pending);
|
|
74
|
+
if (
|
|
75
|
+
/^event: *[a-zA-Z0-9][a-zA-Z0-9._:-]{0,126} *$/.test(event) &&
|
|
76
|
+
bytes[0] === 100 &&
|
|
77
|
+
bytes[1] === 97 &&
|
|
78
|
+
bytes[2] === 116 &&
|
|
79
|
+
bytes[3] === 97 &&
|
|
80
|
+
bytes[4] === 58
|
|
81
|
+
) {
|
|
82
|
+
const line = new Uint8Array(bytes.length + 1);
|
|
83
|
+
line[0] = 10;
|
|
84
|
+
line.set(bytes, 1);
|
|
85
|
+
bytes = line;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const frames = this.feed(bytes, observedAt);
|
|
89
|
+
if (protocol !== "responses") return frames;
|
|
90
|
+
let candidate: string;
|
|
91
|
+
try {
|
|
92
|
+
candidate = decoder.decode(this.pending);
|
|
93
|
+
} catch {
|
|
94
|
+
return frames;
|
|
95
|
+
}
|
|
96
|
+
if (!candidate.trim()) {
|
|
97
|
+
this.pending = new Uint8Array();
|
|
98
|
+
return frames;
|
|
99
|
+
}
|
|
100
|
+
const data: string[] = [];
|
|
101
|
+
let event: string | undefined;
|
|
102
|
+
for (const line of candidate.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n")) {
|
|
103
|
+
if (line.startsWith("data:")) data.push(line.slice(5).replace(/^ /, ""));
|
|
104
|
+
else if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
105
|
+
else if (line !== "" && !line.startsWith(":")) return frames;
|
|
106
|
+
}
|
|
107
|
+
if (!data.length) return frames;
|
|
108
|
+
const payload = data.join("\n");
|
|
109
|
+
if (payload !== "[DONE]") {
|
|
110
|
+
try {
|
|
111
|
+
JSON.parse(payload);
|
|
112
|
+
} catch {
|
|
113
|
+
return frames;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
this.pending = new Uint8Array();
|
|
117
|
+
frames.push({ data: payload, event, observedAt });
|
|
118
|
+
return frames;
|
|
119
|
+
}
|
|
52
120
|
finish(): { truncated: boolean } {
|
|
53
121
|
return {
|
|
54
122
|
truncated: new TextDecoder("utf-8", { fatal: true }).decode(this.pending).trim().length > 0,
|
package/src/protocols/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type { SSEFrame, SSECheckpoint } from "./framing.js";
|
|
|
10
10
|
export type { CliproxyProjection, ProjectionState } from "./types.js";
|
|
11
11
|
export { assembleResponsesStream, assembleChatStream, assembleAnthropicStream };
|
|
12
12
|
export { nanoTime } from "./values.js";
|
|
13
|
-
export const PARSER_VERSION = "client-protocol-
|
|
13
|
+
export const PARSER_VERSION = "client-protocol-v2";
|
|
14
14
|
export function protocolForRoute(route: string): CliproxyProjection["protocol"] {
|
|
15
15
|
if (route === "POST /v1/messages" || route === "POST /v1/messages/count_tokens")
|
|
16
16
|
return "anthropic_messages";
|
|
@@ -25,6 +25,8 @@ export function projectCapturedPayloads(args: {
|
|
|
25
25
|
response?: Uint8Array;
|
|
26
26
|
chunks?: readonly Uint8Array[];
|
|
27
27
|
complete?: boolean;
|
|
28
|
+
/** Set only for capturePolicy hook-body-v1, never historical canonical SSE. */
|
|
29
|
+
stockHookChunks?: boolean;
|
|
28
30
|
}): CliproxyProjection {
|
|
29
31
|
const projection: CliproxyProjection = {
|
|
30
32
|
version: 1,
|
|
@@ -68,7 +70,9 @@ export function projectCapturedPayloads(args: {
|
|
|
68
70
|
if (bytes > 16 * 1024 * 1024)
|
|
69
71
|
throw new Error("bounded replay limit; use event pages for larger calls");
|
|
70
72
|
try {
|
|
71
|
-
for (const frame of
|
|
73
|
+
for (const frame of args.stockHookChunks
|
|
74
|
+
? reader.feedStock(chunk, projection.protocol)
|
|
75
|
+
: reader.feed(chunk)) {
|
|
72
76
|
if (frame.data === "[DONE]") {
|
|
73
77
|
done = true;
|
|
74
78
|
continue;
|