@shpitdev/convex-cliproxy-observability 0.3.5 → 0.3.7
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 +20 -0
- package/README.md +2 -2
- package/dist/capture/content-health.d.ts +29 -0
- package/dist/capture/content-health.d.ts.map +1 -0
- package/dist/capture/content-health.js +29 -0
- package/dist/capture/content-health.js.map +1 -0
- package/dist/capture/index.d.ts +26 -2
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +99 -7
- package/dist/capture/index.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -0
- package/dist/client.js.map +1 -1
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/ingest.d.ts +13 -0
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +4 -0
- package/dist/component/ingest.js.map +1 -1
- package/dist/model-call/index.d.ts +3 -1
- package/dist/model-call/index.d.ts.map +1 -1
- package/dist/model-call/index.js.map +1 -1
- package/dist/projection.d.ts +6 -3
- package/dist/projection.d.ts.map +1 -1
- package/dist/projection.js +55 -7
- package/dist/projection.js.map +1 -1
- package/dist/protocols/body-parts.d.ts +19 -0
- package/dist/protocols/body-parts.d.ts.map +1 -0
- package/dist/protocols/body-parts.js +56 -0
- package/dist/protocols/body-parts.js.map +1 -0
- package/dist/protocols/checkpoint.d.ts +16 -1
- package/dist/protocols/checkpoint.d.ts.map +1 -1
- package/dist/protocols/checkpoint.js +69 -24
- package/dist/protocols/checkpoint.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 +2 -0
- package/dist/protocols/index.js.map +1 -1
- package/dist/protocols/stock-block.d.ts +3 -0
- package/dist/protocols/stock-block.d.ts.map +1 -0
- package/dist/protocols/stock-block.js +17 -0
- package/dist/protocols/stock-block.js.map +1 -0
- package/package.json +1 -1
- package/src/capture/content-health.ts +32 -0
- package/src/capture/index.ts +139 -9
- package/src/client.ts +3 -0
- package/src/component/_generated/api.ts +5 -6
- package/src/component/_generated/component.ts +6 -6
- package/src/component/ingest.ts +3 -0
- package/src/model-call/index.ts +3 -1
- package/src/projection.ts +71 -16
- package/src/protocols/body-parts.ts +58 -0
- package/src/protocols/checkpoint.ts +76 -22
- package/src/protocols/index.ts +4 -1
- package/src/protocols/stock-block.ts +18 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stockHookPieces } from "./body-parts.js";
|
|
1
2
|
import {
|
|
2
3
|
providerIdentity,
|
|
3
4
|
TOKEN_FIELDS,
|
|
@@ -6,7 +7,7 @@ import {
|
|
|
6
7
|
type PrivateContentReference,
|
|
7
8
|
} from "../model-call/index.js";
|
|
8
9
|
import type { CaptureObservationV1 } from "../capture/index.js";
|
|
9
|
-
import { decodeBody } from "../capture/index.js";
|
|
10
|
+
import { continuesBodyPart, decodeBody } from "../capture/index.js";
|
|
10
11
|
import { assembleResponsesStream } from "./responses.js";
|
|
11
12
|
import { SSEReader, type SSECheckpoint } from "./framing.js";
|
|
12
13
|
import { count, list, nanoTime, object, parseObject } from "./values.js";
|
|
@@ -27,6 +28,10 @@ export type ProjectionCheckpoint = {
|
|
|
27
28
|
streamed?: boolean;
|
|
28
29
|
inputSeen?: boolean;
|
|
29
30
|
chatFinished?: boolean;
|
|
31
|
+
/** First segment of an unfinished split body; projection reassembles from there. */
|
|
32
|
+
bodyPartSegment?: number;
|
|
33
|
+
/** Last part of an unfinished split body; the next observation must continue it. */
|
|
34
|
+
openBodyPart?: Pick<CaptureObservationV1, "kind" | "sequence" | "bodyPart">;
|
|
30
35
|
};
|
|
31
36
|
const usageFields = [
|
|
32
37
|
"input_tokens",
|
|
@@ -185,19 +190,55 @@ function observeProtocol(
|
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Split bodies up to this size are reassembled for scalar projection. Larger ones
|
|
195
|
+
* stay complete in raw segments; projection does not allocate them in a 64 MiB
|
|
196
|
+
* Convex action.
|
|
197
|
+
*/
|
|
198
|
+
export const MAX_PROJECTED_SPLIT_BYTES = 8 * 1024 * 1024;
|
|
199
|
+
/**
|
|
200
|
+
* Earlier parts of a split body only advance the sequence. The final part needs
|
|
201
|
+
* the exact reassembled body (`BodyPartAssembler`) as `assembled`, or `null` when
|
|
202
|
+
* the body exceeds `MAX_PROJECTED_SPLIT_BYTES` and its semantics are not projected.
|
|
203
|
+
*/
|
|
188
204
|
export function applyObservation(
|
|
189
205
|
call: ModelCallV1,
|
|
190
206
|
state: ProjectionCheckpoint,
|
|
191
207
|
o: CaptureObservationV1,
|
|
208
|
+
assembled?: Uint8Array | null,
|
|
192
209
|
): void {
|
|
193
210
|
if (o.sequence !== call.capture.projectedThroughSequence + 1)
|
|
194
211
|
throw new Error("projection sequence gap");
|
|
212
|
+
if (o.captureIncomplete) call.capture.incomplete = true;
|
|
213
|
+
if (o.lostContentBytes !== undefined)
|
|
214
|
+
call.capture.lostContentBytes = Math.max(
|
|
215
|
+
call.capture.lostContentBytes ?? 0,
|
|
216
|
+
o.lostContentBytes,
|
|
217
|
+
);
|
|
195
218
|
if (o.gap) {
|
|
196
219
|
state.rawGap = true;
|
|
197
220
|
if (!call.capture.gaps.includes(o.gap) && call.capture.gaps.length < 8)
|
|
198
221
|
call.capture.gaps.push(o.gap.slice(0, 128));
|
|
199
222
|
}
|
|
200
|
-
const
|
|
223
|
+
const part = o.bodyPart;
|
|
224
|
+
// Segments validate only their own neighbors; continuity across them is checked here.
|
|
225
|
+
if (
|
|
226
|
+
(state.openBodyPart || (part && part.index > 0)) &&
|
|
227
|
+
!(state.openBodyPart && continuesBodyPart(state.openBodyPart, o))
|
|
228
|
+
)
|
|
229
|
+
throw new Error("body part interrupted");
|
|
230
|
+
const deferred = part !== undefined && part.index < part.count - 1;
|
|
231
|
+
state.openBodyPart = deferred
|
|
232
|
+
? { kind: o.kind, sequence: o.sequence, bodyPart: part }
|
|
233
|
+
: undefined;
|
|
234
|
+
if (part && !deferred && assembled === undefined)
|
|
235
|
+
throw new Error("split body requires reassembly");
|
|
236
|
+
const unprojected = part !== undefined && !deferred && assembled === null;
|
|
237
|
+
if (unprojected && part.totalBytes <= MAX_PROJECTED_SPLIT_BYTES)
|
|
238
|
+
throw new Error("split body within projection bound");
|
|
239
|
+
// Like an over-limit SSE frame: content is retained, its semantics are not decoded.
|
|
240
|
+
if (unprojected && o.kind !== "request") state.invalid = state.frameLimit = true;
|
|
241
|
+
const body = deferred || unprojected ? new Uint8Array() : (assembled ?? decodeBody(o));
|
|
201
242
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
202
243
|
// A new after-auth event replaces the selection, including unknown fields.
|
|
203
244
|
// Stream initialization may omit auth; an explicit selection replaces the pair.
|
|
@@ -220,14 +261,21 @@ export function applyObservation(
|
|
|
220
261
|
call.correlationConflicts = o.correlationConflicts ?? [];
|
|
221
262
|
call.traceId = o.correlation?.traceId;
|
|
222
263
|
call.sourceTraceId = o.sourceTraceId;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
264
|
+
if (!deferred && !unprojected) {
|
|
265
|
+
try {
|
|
266
|
+
const input = parseObject(decoder.decode(body));
|
|
267
|
+
state.streamed = input.stream === true;
|
|
268
|
+
call.streamed = state.streamed;
|
|
269
|
+
} catch {
|
|
270
|
+
state.invalid = true;
|
|
271
|
+
}
|
|
229
272
|
}
|
|
230
273
|
}
|
|
274
|
+
// An unparsed request (e.g. a split body over the projection bound) takes
|
|
275
|
+
// streaming from the protocol evidence that follows it.
|
|
276
|
+
if (call.streamed === undefined && (o.kind === "stream_init" || o.kind === "stream_chunk"))
|
|
277
|
+
call.streamed = true;
|
|
278
|
+
else if (call.streamed === undefined && o.kind === "response") call.streamed = false;
|
|
231
279
|
// Derived read fields: reads fill them only for summaries projected before 0.3.0.
|
|
232
280
|
Object.assign(call, providerIdentity(call), { costProvenance: call.cost.kind });
|
|
233
281
|
if (o.kind === "response" || o.kind === "stream_chunk" || o.kind === "completion") {
|
|
@@ -244,9 +292,13 @@ export function applyObservation(
|
|
|
244
292
|
}
|
|
245
293
|
if ((o.kind === "stream_chunk" || o.kind === "completion") && body.length) {
|
|
246
294
|
const reader = new SSEReader(state.sse);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
295
|
+
const pieces = o.bodyFraming === "stock_hook_block" ? stockHookPieces(o, body) : [body];
|
|
296
|
+
const frames = pieces.flatMap((piece) =>
|
|
297
|
+
o.bodyFraming === "stock_hook_chunk" || o.bodyFraming === "stock_hook_block"
|
|
298
|
+
? reader.feedStock(piece, call.clientProtocol, o.observedAt)
|
|
299
|
+
: reader.feed(piece, o.observedAt),
|
|
300
|
+
);
|
|
301
|
+
for (const frame of frames) {
|
|
250
302
|
if (frame.data === "[DONE]") {
|
|
251
303
|
if (call.clientProtocol === "chat_completions" && state.chatFinished)
|
|
252
304
|
state.terminal = "completed";
|
|
@@ -287,17 +339,19 @@ export function applyObservation(
|
|
|
287
339
|
call.latencyMs = Number(BigInt(call.endTimeUnixNano) - BigInt(start)) / 1e6;
|
|
288
340
|
call.capture.terminalSequence = o.sequence;
|
|
289
341
|
call.state =
|
|
290
|
-
o.completionOutcome ===
|
|
291
|
-
? "
|
|
292
|
-
: o.completionOutcome === "
|
|
293
|
-
? "
|
|
294
|
-
: o.completionOutcome === "
|
|
295
|
-
? "
|
|
296
|
-
:
|
|
297
|
-
? "
|
|
298
|
-
: state.terminal === "
|
|
299
|
-
? "
|
|
300
|
-
: "
|
|
342
|
+
o.completionOutcome === undefined && o.captureIncomplete
|
|
343
|
+
? "unknown"
|
|
344
|
+
: o.completionOutcome === "canceled"
|
|
345
|
+
? "aborted"
|
|
346
|
+
: o.completionOutcome === "failed"
|
|
347
|
+
? "failed"
|
|
348
|
+
: o.completionOutcome === "rejected"
|
|
349
|
+
? "rejected"
|
|
350
|
+
: state.terminal === "completed"
|
|
351
|
+
? "succeeded"
|
|
352
|
+
: state.terminal === "failed" || state.terminal === "incomplete"
|
|
353
|
+
? "failed"
|
|
354
|
+
: "unknown";
|
|
301
355
|
}
|
|
302
356
|
call.protocolTerminal = state.terminal;
|
|
303
357
|
const final =
|
package/src/protocols/index.ts
CHANGED
|
@@ -25,7 +25,7 @@ export function projectCapturedPayloads(args: {
|
|
|
25
25
|
response?: Uint8Array;
|
|
26
26
|
chunks?: readonly Uint8Array[];
|
|
27
27
|
complete?: boolean;
|
|
28
|
-
/** Set only for
|
|
28
|
+
/** Set only for expanded raw stock hooks, never historical canonical SSE. */
|
|
29
29
|
stockHookChunks?: boolean;
|
|
30
30
|
}): CliproxyProjection {
|
|
31
31
|
const projection: CliproxyProjection = {
|
|
@@ -142,3 +142,6 @@ export function projectCapturedPayloads(args: {
|
|
|
142
142
|
}
|
|
143
143
|
return projection;
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
export { expandStockHookBlock } from "./stock-block.js";
|
|
147
|
+
export { BodyPartAssembler, stockHookPieces } from "./body-parts.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Recover stock parser boundaries without altering the captured bytes. */
|
|
2
|
+
export function expandStockHookBlock(
|
|
3
|
+
body: Uint8Array,
|
|
4
|
+
lengths: readonly number[],
|
|
5
|
+
): readonly Uint8Array[] {
|
|
6
|
+
if (!Array.isArray(lengths) || lengths.length === 0 || lengths.length > 1024)
|
|
7
|
+
throw new Error("invalid stock hook block lengths");
|
|
8
|
+
const pieces: Uint8Array[] = [];
|
|
9
|
+
let offset = 0;
|
|
10
|
+
for (const length of lengths) {
|
|
11
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > body.byteLength - offset)
|
|
12
|
+
throw new Error("invalid stock hook block length");
|
|
13
|
+
pieces.push(body.subarray(offset, offset + length));
|
|
14
|
+
offset += length;
|
|
15
|
+
}
|
|
16
|
+
if (offset !== body.byteLength) throw new Error("stock hook block byte mismatch");
|
|
17
|
+
return pieces;
|
|
18
|
+
}
|