@shpitdev/convex-cliproxy-observability 0.1.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/LICENSE +201 -0
- package/README.md +40 -0
- package/dist/capture/index.d.ts +79 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +182 -0
- package/dist/capture/index.js.map +1 -0
- package/dist/client.d.ts +129 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +232 -0
- package/dist/client.js.map +1 -0
- package/dist/component/_generated/api.d.ts +12 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +7 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +17 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +3 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +8 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +3 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +21 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +11 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +4 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +4 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/ingest.d.ts +64 -0
- package/dist/component/ingest.d.ts.map +1 -0
- package/dist/component/ingest.js +309 -0
- package/dist/component/ingest.js.map +1 -0
- package/dist/component/queries.d.ts +136 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +219 -0
- package/dist/component/queries.js.map +1 -0
- package/dist/component/schema.d.ts +159 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +74 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/content/index.d.ts +8 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +41 -0
- package/dist/content/index.js.map +1 -0
- package/dist/model-call/index.d.ts +120 -0
- package/dist/model-call/index.d.ts.map +1 -0
- package/dist/model-call/index.js +96 -0
- package/dist/model-call/index.js.map +1 -0
- package/dist/projection.d.ts +19 -0
- package/dist/projection.d.ts.map +1 -0
- package/dist/projection.js +83 -0
- package/dist/projection.js.map +1 -0
- package/dist/protocols/anthropic-messages.d.ts +11 -0
- package/dist/protocols/anthropic-messages.d.ts.map +1 -0
- package/dist/protocols/anthropic-messages.js +114 -0
- package/dist/protocols/anthropic-messages.js.map +1 -0
- package/dist/protocols/chat-completions.d.ts +10 -0
- package/dist/protocols/chat-completions.d.ts.map +1 -0
- package/dist/protocols/chat-completions.js +89 -0
- package/dist/protocols/chat-completions.js.map +1 -0
- package/dist/protocols/checkpoint.d.ts +20 -0
- package/dist/protocols/checkpoint.d.ts.map +1 -0
- package/dist/protocols/checkpoint.js +318 -0
- package/dist/protocols/checkpoint.js.map +1 -0
- package/dist/protocols/content.d.ts +34 -0
- package/dist/protocols/content.d.ts.map +1 -0
- package/dist/protocols/content.js +40 -0
- package/dist/protocols/content.js.map +1 -0
- package/dist/protocols/framing.d.ts +20 -0
- package/dist/protocols/framing.d.ts.map +1 -0
- package/dist/protocols/framing.js +67 -0
- package/dist/protocols/framing.js.map +1 -0
- package/dist/protocols/index.d.ts +20 -0
- package/dist/protocols/index.d.ts.map +1 -0
- package/dist/protocols/index.js +139 -0
- package/dist/protocols/index.js.map +1 -0
- package/dist/protocols/output.d.ts +4 -0
- package/dist/protocols/output.d.ts.map +1 -0
- package/dist/protocols/output.js +81 -0
- package/dist/protocols/output.js.map +1 -0
- package/dist/protocols/responses.d.ts +12 -0
- package/dist/protocols/responses.d.ts.map +1 -0
- package/dist/protocols/responses.js +189 -0
- package/dist/protocols/responses.js.map +1 -0
- package/dist/protocols/types.d.ts +27 -0
- package/dist/protocols/types.d.ts.map +1 -0
- package/dist/protocols/types.js +2 -0
- package/dist/protocols/types.js.map +1 -0
- package/dist/protocols/usage.d.ts +3 -0
- package/dist/protocols/usage.d.ts.map +1 -0
- package/dist/protocols/usage.js +27 -0
- package/dist/protocols/usage.js.map +1 -0
- package/dist/protocols/values.d.ts +10 -0
- package/dist/protocols/values.d.ts.map +1 -0
- package/dist/protocols/values.js +32 -0
- package/dist/protocols/values.js.map +1 -0
- package/dist/test.d.ts +155 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +8 -0
- package/dist/test.js.map +1 -0
- package/package.json +90 -0
- package/src/capture/index.ts +277 -0
- package/src/client.ts +286 -0
- package/src/component/_generated/api.ts +19 -0
- package/src/component/_generated/component.ts +32 -0
- package/src/component/_generated/dataModel.ts +15 -0
- package/src/component/_generated/server.ts +43 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/ingest.ts +328 -0
- package/src/component/queries.ts +248 -0
- package/src/component/schema.ts +73 -0
- package/src/content/index.ts +53 -0
- package/src/model-call/index.ts +224 -0
- package/src/projection.ts +99 -0
- package/src/protocols/anthropic-messages.ts +115 -0
- package/src/protocols/chat-completions.ts +94 -0
- package/src/protocols/checkpoint.ts +342 -0
- package/src/protocols/content.ts +41 -0
- package/src/protocols/framing.ts +62 -0
- package/src/protocols/index.ts +140 -0
- package/src/protocols/output.ts +78 -0
- package/src/protocols/responses.ts +188 -0
- package/src/protocols/types.ts +26 -0
- package/src/protocols/usage.ts +36 -0
- package/src/protocols/values.ts +35 -0
- package/src/test.ts +16 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { RecordValue } from "./types.js";
|
|
2
|
+
import { append, count, list, object } from "./values.js";
|
|
3
|
+
|
|
4
|
+
/** Reconstruct only emitted Responses content; a completed item is not a completed call. */
|
|
5
|
+
export function assembleResponsesStream(events: RecordValue[]) {
|
|
6
|
+
const response: RecordValue = {};
|
|
7
|
+
const items = new Map<number, RecordValue>();
|
|
8
|
+
let terminal = false;
|
|
9
|
+
let terminalUsage = false;
|
|
10
|
+
let terminalSnapshot = false;
|
|
11
|
+
let recognized = false;
|
|
12
|
+
let invalid = false;
|
|
13
|
+
let unsupported = false;
|
|
14
|
+
for (const event of events) {
|
|
15
|
+
const type = String(event.type);
|
|
16
|
+
if (
|
|
17
|
+
[
|
|
18
|
+
"response.created",
|
|
19
|
+
"response.queued",
|
|
20
|
+
"response.in_progress",
|
|
21
|
+
"response.completed",
|
|
22
|
+
"response.failed",
|
|
23
|
+
"response.incomplete",
|
|
24
|
+
].includes(type)
|
|
25
|
+
) {
|
|
26
|
+
recognized = true;
|
|
27
|
+
const snapshot = object(event.response);
|
|
28
|
+
if (snapshot.object !== "response" || typeof snapshot.id !== "string") {
|
|
29
|
+
invalid = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (["response.completed", "response.failed", "response.incomplete"].includes(type)) {
|
|
33
|
+
if (snapshot.status !== type.slice("response.".length) || !Array.isArray(snapshot.output)) {
|
|
34
|
+
invalid = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
terminal = true;
|
|
38
|
+
terminalSnapshot = true;
|
|
39
|
+
terminalUsage = snapshot.usage !== undefined && snapshot.usage !== null;
|
|
40
|
+
}
|
|
41
|
+
Object.assign(response, snapshot);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (type === "error") {
|
|
45
|
+
recognized = true;
|
|
46
|
+
terminal = true;
|
|
47
|
+
response.status = "failed";
|
|
48
|
+
response.error = event.error ?? {
|
|
49
|
+
type: event.type,
|
|
50
|
+
code: event.code,
|
|
51
|
+
message: event.message,
|
|
52
|
+
};
|
|
53
|
+
terminalUsage = false;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (type === "response.output_item.added" || type === "response.output_item.done") {
|
|
57
|
+
recognized = true;
|
|
58
|
+
const index = count(event.output_index);
|
|
59
|
+
const item = object(event.item);
|
|
60
|
+
if (index === undefined || typeof item.type !== "string") invalid = true;
|
|
61
|
+
else items.set(index, item);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (
|
|
65
|
+
[
|
|
66
|
+
"response.content_part.added",
|
|
67
|
+
"response.content_part.done",
|
|
68
|
+
"response.output_text.delta",
|
|
69
|
+
"response.output_text.done",
|
|
70
|
+
"response.output_text.annotation.added",
|
|
71
|
+
"response.refusal.delta",
|
|
72
|
+
"response.refusal.done",
|
|
73
|
+
"response.function_call_arguments.delta",
|
|
74
|
+
"response.function_call_arguments.done",
|
|
75
|
+
"response.reasoning_summary_part.added",
|
|
76
|
+
"response.reasoning_summary_part.done",
|
|
77
|
+
"response.reasoning_summary_text.delta",
|
|
78
|
+
"response.reasoning_summary_text.done",
|
|
79
|
+
].includes(type)
|
|
80
|
+
) {
|
|
81
|
+
recognized = true;
|
|
82
|
+
const index = count(event.output_index);
|
|
83
|
+
const item = index === undefined ? undefined : items.get(index);
|
|
84
|
+
if (!item || item.id !== event.item_id) {
|
|
85
|
+
invalid = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (type.startsWith("response.function_call_arguments.")) {
|
|
89
|
+
if (type.endsWith(".delta")) append(item, "arguments", event.delta);
|
|
90
|
+
else item.arguments = event.arguments;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const summary = type.startsWith("response.reasoning_summary_");
|
|
94
|
+
const partIndex = count(summary ? event.summary_index : event.content_index);
|
|
95
|
+
if (partIndex === undefined) {
|
|
96
|
+
invalid = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const field = summary ? "summary" : "content";
|
|
100
|
+
const parts = list(item[field]);
|
|
101
|
+
// Keep sparse provider indices in a map-like object rather than allocating a huge array.
|
|
102
|
+
if (partIndex > parts.length) {
|
|
103
|
+
invalid = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (type.includes("_part.")) parts[partIndex] = object(event.part);
|
|
107
|
+
else {
|
|
108
|
+
const refusal = type.startsWith("response.refusal.");
|
|
109
|
+
const part = object(parts[partIndex]);
|
|
110
|
+
part.type ??= summary ? "summary_text" : refusal ? "refusal" : "output_text";
|
|
111
|
+
const key = refusal ? "refusal" : "text";
|
|
112
|
+
if (type === "response.output_text.annotation.added") {
|
|
113
|
+
const annotations = list(part.annotations);
|
|
114
|
+
const annotationIndex = count(event.annotation_index);
|
|
115
|
+
if (annotationIndex === undefined || annotationIndex > annotations.length) {
|
|
116
|
+
invalid = true;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
annotations[annotationIndex] = event.annotation;
|
|
120
|
+
part.annotations = annotations;
|
|
121
|
+
} else if (type.endsWith(".delta")) append(part, key, event.delta);
|
|
122
|
+
else part[key] = event[key];
|
|
123
|
+
parts[partIndex] = part;
|
|
124
|
+
}
|
|
125
|
+
item[field] = parts;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (
|
|
129
|
+
[
|
|
130
|
+
"response.web_search_call.in_progress",
|
|
131
|
+
"response.web_search_call.searching",
|
|
132
|
+
"response.web_search_call.completed",
|
|
133
|
+
"response.file_search_call.in_progress",
|
|
134
|
+
"response.file_search_call.searching",
|
|
135
|
+
"response.file_search_call.completed",
|
|
136
|
+
"response.code_interpreter_call.in_progress",
|
|
137
|
+
"response.code_interpreter_call.interpreting",
|
|
138
|
+
"response.code_interpreter_call.completed",
|
|
139
|
+
"response.image_generation_call.in_progress",
|
|
140
|
+
"response.image_generation_call.generating",
|
|
141
|
+
"response.image_generation_call.completed",
|
|
142
|
+
"response.mcp_call.in_progress",
|
|
143
|
+
"response.mcp_call.completed",
|
|
144
|
+
"response.mcp_call.failed",
|
|
145
|
+
"response.mcp_list_tools.in_progress",
|
|
146
|
+
"response.mcp_list_tools.completed",
|
|
147
|
+
"response.mcp_list_tools.failed",
|
|
148
|
+
].includes(type)
|
|
149
|
+
) {
|
|
150
|
+
// Tool lifecycle frames carry no content or call usage. Keep them in the raw
|
|
151
|
+
// transcript; only a response terminal event can establish final usage.
|
|
152
|
+
recognized = true;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// Retained in the raw transcript. Do not pretend an unknown protocol frame was decoded.
|
|
156
|
+
unsupported = true;
|
|
157
|
+
}
|
|
158
|
+
const emitted = [...items.entries()].toSorted(([a], [b]) => a - b).map(([, item]) => item);
|
|
159
|
+
if (!terminalSnapshot || !Array.isArray(response.output)) response.output = emitted;
|
|
160
|
+
else if (response.status === "failed" || response.status === "incomplete") {
|
|
161
|
+
const snapshotIds = new Set(response.output.map((item) => object(item).id));
|
|
162
|
+
// Failed terminal snapshots may omit unfinished items. Preserve emitted
|
|
163
|
+
// evidence while preferring terminal entries with the same exact item ID.
|
|
164
|
+
response.output = [
|
|
165
|
+
...response.output,
|
|
166
|
+
...emitted.filter((item) => typeof item.id !== "string" || !snapshotIds.has(item.id)),
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
return { response, terminal, terminalUsage, recognized, invalid, unsupported };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function responsesMessages(output: unknown): RecordValue[] {
|
|
173
|
+
return list(output).map((raw) => {
|
|
174
|
+
const item = object(raw);
|
|
175
|
+
if (item.type === "function_call")
|
|
176
|
+
return {
|
|
177
|
+
role: "assistant",
|
|
178
|
+
tool_calls: [
|
|
179
|
+
{
|
|
180
|
+
id: item.call_id,
|
|
181
|
+
type: "function",
|
|
182
|
+
function: { name: item.name, arguments: item.arguments },
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
return { ...item, role: item.role ?? "assistant" };
|
|
187
|
+
});
|
|
188
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Native CLIProxy application-log projection. Upstream attempts are never usage sources. */
|
|
2
|
+
export type ProjectionState = "absent" | "invalid" | "unsupported" | "truncated" | "decoded";
|
|
3
|
+
export type RecordValue = Record<string, unknown>;
|
|
4
|
+
export type CliproxyProjection = {
|
|
5
|
+
version: 1;
|
|
6
|
+
protocol: "chat_completions" | "anthropic_messages" | "responses" | "unknown";
|
|
7
|
+
requestState: ProjectionState;
|
|
8
|
+
responseState: ProjectionState;
|
|
9
|
+
input?: RecordValue;
|
|
10
|
+
output?: RecordValue;
|
|
11
|
+
scalars: {
|
|
12
|
+
requestModel?: string;
|
|
13
|
+
responseModel?: string;
|
|
14
|
+
generationId?: string;
|
|
15
|
+
streamed?: boolean;
|
|
16
|
+
startTimeUnixNano?: string;
|
|
17
|
+
finishReason?: string;
|
|
18
|
+
inputTokens?: number;
|
|
19
|
+
outputTokens?: number;
|
|
20
|
+
totalTokens?: number;
|
|
21
|
+
reasoningTokens?: number;
|
|
22
|
+
cachedInputTokens?: number;
|
|
23
|
+
statusJson?: string;
|
|
24
|
+
};
|
|
25
|
+
metadata: RecordValue;
|
|
26
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CliproxyProjection, RecordValue } from "./types.js";
|
|
2
|
+
import { count, object } from "./values.js";
|
|
3
|
+
export function extractUsage(projection: CliproxyProjection, raw: RecordValue) {
|
|
4
|
+
// Anthropic reports disjoint uncached/cache creation/cache read input counts.
|
|
5
|
+
// Never manufacture totals, costs, or token counts from byte/text lengths.
|
|
6
|
+
projection.metadata.usage = raw;
|
|
7
|
+
const anthropic = projection.protocol === "anthropic_messages";
|
|
8
|
+
|
|
9
|
+
const responses = projection.protocol === "responses";
|
|
10
|
+
|
|
11
|
+
const nativeInput = count(raw.input_tokens);
|
|
12
|
+
const cacheRead = count(raw.cache_read_input_tokens);
|
|
13
|
+
const cacheCreated = count(raw.cache_creation_input_tokens);
|
|
14
|
+
const anthropicInput =
|
|
15
|
+
nativeInput !== undefined && cacheRead !== undefined && cacheCreated !== undefined
|
|
16
|
+
? count(nativeInput + cacheRead + cacheCreated)
|
|
17
|
+
: undefined;
|
|
18
|
+
Object.assign(projection.scalars, {
|
|
19
|
+
inputTokens: anthropic ? anthropicInput : responses ? nativeInput : count(raw.prompt_tokens),
|
|
20
|
+
outputTokens: count(raw[anthropic || responses ? "output_tokens" : "completion_tokens"]),
|
|
21
|
+
totalTokens: count(raw.total_tokens),
|
|
22
|
+
cachedInputTokens: count(
|
|
23
|
+
anthropic
|
|
24
|
+
? raw.cache_read_input_tokens
|
|
25
|
+
: object(raw[responses ? "input_tokens_details" : "prompt_tokens_details"]).cached_tokens,
|
|
26
|
+
),
|
|
27
|
+
reasoningTokens: count(
|
|
28
|
+
object(raw[anthropic || responses ? "output_tokens_details" : "completion_tokens_details"])[
|
|
29
|
+
anthropic ? "thinking_tokens" : "reasoning_tokens"
|
|
30
|
+
],
|
|
31
|
+
),
|
|
32
|
+
});
|
|
33
|
+
projection.metadata.tokenSemantics = anthropic
|
|
34
|
+
? "input_is_sum_of_reported_input_cache_creation_cache_read; cached_input_is_cache_read; missing_components_mean_unknown; no_synthesized_total"
|
|
35
|
+
: "input_includes_reported_cached_input; terminal_usage_snapshot";
|
|
36
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { RecordValue } from "./types.js";
|
|
2
|
+
export const object = (value: unknown): RecordValue =>
|
|
3
|
+
value !== null && typeof value === "object" && !Array.isArray(value)
|
|
4
|
+
? (value as RecordValue)
|
|
5
|
+
: {};
|
|
6
|
+
export const list = (value: unknown): unknown[] => (Array.isArray(value) ? value : []);
|
|
7
|
+
export const string = (value: unknown): string | undefined =>
|
|
8
|
+
typeof value === "string" ? value : undefined;
|
|
9
|
+
export const identity = (value: unknown) => {
|
|
10
|
+
const text = string(value);
|
|
11
|
+
return text && text.length <= 256 ? text : undefined;
|
|
12
|
+
};
|
|
13
|
+
export const count = (value: unknown) =>
|
|
14
|
+
typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
15
|
+
export function nanoTime(value: string | undefined) {
|
|
16
|
+
if (!value) return;
|
|
17
|
+
const match = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d{1,9}))?(Z|[+-]\d{2}:\d{2})$/.exec(
|
|
18
|
+
value,
|
|
19
|
+
);
|
|
20
|
+
if (!match) return;
|
|
21
|
+
const seconds = Date.parse(`${match[1]}${match[3]}`);
|
|
22
|
+
return Number.isFinite(seconds)
|
|
23
|
+
? String(BigInt(seconds) * 1_000_000n + BigInt((match[2] ?? "").padEnd(9, "0")))
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
26
|
+
export function parseObject(raw: string): RecordValue {
|
|
27
|
+
const parsed: unknown = JSON.parse(raw);
|
|
28
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
29
|
+
throw new Error("Object required");
|
|
30
|
+
return parsed as RecordValue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function append(target: RecordValue, key: string, value: unknown) {
|
|
34
|
+
if (typeof value === "string") target[key] = (string(target[key]) ?? "") + value;
|
|
35
|
+
}
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
import type { GenericSchema, SchemaDefinition } from "convex/server";
|
|
4
|
+
import type { TestConvex } from "convex-test";
|
|
5
|
+
import schema from "./component/schema.js";
|
|
6
|
+
|
|
7
|
+
const modules = import.meta.glob("./component/**/*.ts");
|
|
8
|
+
|
|
9
|
+
export function register(
|
|
10
|
+
testConvex: TestConvex<SchemaDefinition<GenericSchema, boolean>>,
|
|
11
|
+
name = "cliproxyObservability",
|
|
12
|
+
) {
|
|
13
|
+
testConvex.registerComponent(name, schema, modules);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default { modules, register, schema };
|