@omnicross/core 0.1.9 → 0.1.10
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/dist/{chunk-EPN67EGP.cjs → chunk-5UK3KLE2.cjs} +9 -1
- package/dist/chunk-6D4W22P4.js +150 -0
- package/dist/{chunk-UAPBLNN2.js → chunk-AQDON6MB.js} +8 -0
- package/dist/{chunk-CQIJCPMF.js → chunk-FEBAQI5A.js} +78 -15
- package/dist/{chunk-7C7DET6S.js → chunk-GXEZ2R3E.js} +156 -1
- package/dist/chunk-QUSEZNYS.cjs +150 -0
- package/dist/{chunk-XELFM7KL.cjs → chunk-TIT74RIL.cjs} +235 -172
- package/dist/{chunk-XPIOJQS2.cjs → chunk-UWDW6PN3.cjs} +159 -4
- package/dist/completion/CompletionService.cjs +5 -5
- package/dist/completion/CompletionService.js +4 -4
- package/dist/completion.cjs +5 -5
- package/dist/completion.js +4 -4
- package/dist/index.cjs +5 -5
- package/dist/index.js +4 -4
- package/dist/outbound-api/auditCapture.cjs +2 -2
- package/dist/outbound-api/auditCapture.d.cts +6 -0
- package/dist/outbound-api/auditCapture.d.ts +6 -0
- package/dist/outbound-api/auditCapture.js +1 -1
- package/dist/outbound-api.cjs +5 -5
- package/dist/outbound-api.d.cts +2 -1
- package/dist/outbound-api.d.ts +2 -1
- package/dist/outbound-api.js +4 -4
- package/dist/provider-proxy/ProviderProxy.cjs +5 -5
- package/dist/provider-proxy/ProviderProxy.js +4 -4
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +5 -5
- package/dist/provider-proxy/ingress/providerProxyShared.js +4 -4
- package/dist/provider-proxy.cjs +5 -5
- package/dist/provider-proxy.js +4 -4
- package/dist/usage/usage-recorder.cjs +2 -2
- package/dist/usage/usage-recorder.d.cts +12 -2
- package/dist/usage/usage-recorder.d.ts +12 -2
- package/dist/usage/usage-recorder.js +1 -1
- package/dist/usage.cjs +20 -3
- package/dist/usage.d.cts +149 -0
- package/dist/usage.d.ts +149 -0
- package/dist/usage.js +20 -3
- package/package.json +2 -2
- package/dist/chunk-7VU7V2E4.js +0 -0
- package/dist/chunk-EYZYXJTJ.cjs +0 -1
|
@@ -10,12 +10,14 @@ var UsageRecorder = class {
|
|
|
10
10
|
this.logger = logger;
|
|
11
11
|
this.defer = _nullishCoalesce(options.defer, () => ( ((fn) => setTimeout(fn, 0))));
|
|
12
12
|
this.onRecord = options.onRecord;
|
|
13
|
+
this.onEvent = options.onEvent;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
|
|
19
21
|
/**
|
|
20
22
|
* Record one LLM request. Returns immediately — the actual store insert is
|
|
21
23
|
* deferred so the caller's response path is unblocked.
|
|
@@ -85,6 +87,12 @@ var UsageRecorder = class {
|
|
|
85
87
|
...input.cacheKeySource !== void 0 ? { cacheKeySource: input.cacheKeySource } : {},
|
|
86
88
|
...input.cacheKeyInjected !== void 0 ? { cacheKeyInjected: input.cacheKeyInjected } : {}
|
|
87
89
|
};
|
|
90
|
+
if (this.onEvent) {
|
|
91
|
+
try {
|
|
92
|
+
this.onEvent(row, Date.now());
|
|
93
|
+
} catch (e3) {
|
|
94
|
+
}
|
|
95
|
+
}
|
|
88
96
|
try {
|
|
89
97
|
return await this.store.insert(row);
|
|
90
98
|
} catch (err) {
|
|
@@ -118,7 +126,7 @@ var UsageRecorder = class {
|
|
|
118
126
|
var safeStringify = (value) => {
|
|
119
127
|
try {
|
|
120
128
|
return JSON.stringify(value);
|
|
121
|
-
} catch (
|
|
129
|
+
} catch (e4) {
|
|
122
130
|
return "";
|
|
123
131
|
}
|
|
124
132
|
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// src/usage/throughputTracker.ts
|
|
2
|
+
var THROUGHPUT_RETENTION_MS = 15 * 6e4;
|
|
3
|
+
var THROUGHPUT_SAMPLE_LIMIT = 2e4;
|
|
4
|
+
var THROUGHPUT_WINDOWS_MS = [6e4, 3e5, 9e5];
|
|
5
|
+
var THROUGHPUT_BUCKET_COUNT = 30;
|
|
6
|
+
var COMPACT_THRESHOLD = 1024;
|
|
7
|
+
var UsageThroughputTracker = class {
|
|
8
|
+
samples = [];
|
|
9
|
+
/** Index of the oldest live sample; slots before it are consumed. */
|
|
10
|
+
head = 0;
|
|
11
|
+
startedAt;
|
|
12
|
+
/** Newest ts dropped by the sample CAP (not by retention). Null when none. */
|
|
13
|
+
evictedThroughTs = null;
|
|
14
|
+
constructor(now = Date.now()) {
|
|
15
|
+
this.startedAt = now;
|
|
16
|
+
}
|
|
17
|
+
/** Record one served request. O(1) amortised; never touches disk. */
|
|
18
|
+
record(input, now = Date.now()) {
|
|
19
|
+
this.samples.push({
|
|
20
|
+
ts: input.ts ?? now,
|
|
21
|
+
inputTokens: input.inputTokens,
|
|
22
|
+
outputTokens: input.outputTokens,
|
|
23
|
+
cacheReadTokens: input.cacheReadTokens,
|
|
24
|
+
cacheCreationTokens: input.cacheCreationTokens,
|
|
25
|
+
reasoningTokens: input.reasoningTokens,
|
|
26
|
+
costUsd: input.costUsd
|
|
27
|
+
});
|
|
28
|
+
this.prune(now);
|
|
29
|
+
}
|
|
30
|
+
/** Aggregate every reported window plus the shared trend series. */
|
|
31
|
+
snapshot(now = Date.now()) {
|
|
32
|
+
this.prune(now);
|
|
33
|
+
return {
|
|
34
|
+
available: true,
|
|
35
|
+
collectedAt: now,
|
|
36
|
+
startedAt: this.startedAt,
|
|
37
|
+
retentionMs: THROUGHPUT_RETENTION_MS,
|
|
38
|
+
bucketMs: THROUGHPUT_RETENTION_MS / THROUGHPUT_BUCKET_COUNT,
|
|
39
|
+
windows: THROUGHPUT_WINDOWS_MS.map((windowMs) => this.window(windowMs, now)),
|
|
40
|
+
buckets: this.buckets(now)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Drop all samples (tests / teardown). `startedAt` is unchanged. */
|
|
44
|
+
clear() {
|
|
45
|
+
this.samples = [];
|
|
46
|
+
this.head = 0;
|
|
47
|
+
this.evictedThroughTs = null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Drop samples past the retention horizon, then enforce the sample cap.
|
|
51
|
+
*
|
|
52
|
+
* Retention pruning uses the CALLER's `now`, which is never later than a
|
|
53
|
+
* subsequent snapshot's `now`, so it can only ever keep MORE than the widest
|
|
54
|
+
* window needs — it can never shave data off the 15-minute window.
|
|
55
|
+
*/
|
|
56
|
+
prune(now) {
|
|
57
|
+
const cutoff = now - THROUGHPUT_RETENTION_MS;
|
|
58
|
+
while (this.head < this.samples.length && this.samples[this.head].ts < cutoff) {
|
|
59
|
+
this.head += 1;
|
|
60
|
+
}
|
|
61
|
+
while (this.samples.length - this.head > THROUGHPUT_SAMPLE_LIMIT) {
|
|
62
|
+
const dropped = this.samples[this.head];
|
|
63
|
+
this.evictedThroughTs = this.evictedThroughTs === null ? dropped.ts : Math.max(this.evictedThroughTs, dropped.ts);
|
|
64
|
+
this.head += 1;
|
|
65
|
+
}
|
|
66
|
+
if (this.head >= COMPACT_THRESHOLD) {
|
|
67
|
+
this.samples = this.samples.slice(this.head);
|
|
68
|
+
this.head = 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
window(windowMs, now) {
|
|
72
|
+
const startTs = now - windowMs;
|
|
73
|
+
const row = {
|
|
74
|
+
windowMs,
|
|
75
|
+
requests: 0,
|
|
76
|
+
inputTokens: 0,
|
|
77
|
+
outputTokens: 0,
|
|
78
|
+
cacheReadTokens: 0,
|
|
79
|
+
cacheCreationTokens: 0,
|
|
80
|
+
reasoningTokens: 0,
|
|
81
|
+
totalTokens: 0,
|
|
82
|
+
costUsd: 0,
|
|
83
|
+
requestsPerMinute: 0,
|
|
84
|
+
tokensPerMinute: 0,
|
|
85
|
+
inputTokensPerMinute: 0,
|
|
86
|
+
outputTokensPerMinute: 0,
|
|
87
|
+
costUsdPerMinute: 0,
|
|
88
|
+
complete: this.evictedThroughTs === null || this.evictedThroughTs < startTs
|
|
89
|
+
};
|
|
90
|
+
for (let i = this.head; i < this.samples.length; i += 1) {
|
|
91
|
+
const sample = this.samples[i];
|
|
92
|
+
if (sample.ts < startTs) continue;
|
|
93
|
+
row.requests += 1;
|
|
94
|
+
row.inputTokens += sample.inputTokens;
|
|
95
|
+
row.outputTokens += sample.outputTokens;
|
|
96
|
+
row.cacheReadTokens += sample.cacheReadTokens;
|
|
97
|
+
row.cacheCreationTokens += sample.cacheCreationTokens;
|
|
98
|
+
row.reasoningTokens += sample.reasoningTokens;
|
|
99
|
+
row.costUsd += sample.costUsd;
|
|
100
|
+
}
|
|
101
|
+
row.totalTokens = row.inputTokens + row.outputTokens + row.cacheReadTokens + row.cacheCreationTokens;
|
|
102
|
+
const minutes = windowMs / 6e4;
|
|
103
|
+
row.requestsPerMinute = row.requests / minutes;
|
|
104
|
+
row.tokensPerMinute = row.totalTokens / minutes;
|
|
105
|
+
row.inputTokensPerMinute = row.inputTokens / minutes;
|
|
106
|
+
row.outputTokensPerMinute = row.outputTokens / minutes;
|
|
107
|
+
row.costUsdPerMinute = row.costUsd / minutes;
|
|
108
|
+
return row;
|
|
109
|
+
}
|
|
110
|
+
buckets(now) {
|
|
111
|
+
const bucketMs = THROUGHPUT_RETENTION_MS / THROUGHPUT_BUCKET_COUNT;
|
|
112
|
+
const endTs = Math.floor(now / bucketMs) * bucketMs + bucketMs;
|
|
113
|
+
const startTs = endTs - THROUGHPUT_RETENTION_MS;
|
|
114
|
+
const out = [];
|
|
115
|
+
for (let i = 0; i < THROUGHPUT_BUCKET_COUNT; i += 1) {
|
|
116
|
+
out.push({ startTs: startTs + i * bucketMs, requests: 0, tokens: 0 });
|
|
117
|
+
}
|
|
118
|
+
for (let i = this.head; i < this.samples.length; i += 1) {
|
|
119
|
+
const sample = this.samples[i];
|
|
120
|
+
if (sample.ts < startTs || sample.ts >= endTs) continue;
|
|
121
|
+
const bucket = out[Math.floor((sample.ts - startTs) / bucketMs)];
|
|
122
|
+
if (!bucket) continue;
|
|
123
|
+
bucket.requests += 1;
|
|
124
|
+
bucket.tokens += sample.inputTokens + sample.outputTokens + sample.cacheReadTokens + sample.cacheCreationTokens;
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var sharedTracker = null;
|
|
130
|
+
function getSharedUsageThroughputTracker() {
|
|
131
|
+
if (!sharedTracker) sharedTracker = new UsageThroughputTracker();
|
|
132
|
+
return sharedTracker;
|
|
133
|
+
}
|
|
134
|
+
function setSharedUsageThroughputTracker(instance) {
|
|
135
|
+
sharedTracker = instance;
|
|
136
|
+
}
|
|
137
|
+
function __resetSharedUsageThroughputTrackerForTests() {
|
|
138
|
+
sharedTracker = null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export {
|
|
142
|
+
THROUGHPUT_RETENTION_MS,
|
|
143
|
+
THROUGHPUT_SAMPLE_LIMIT,
|
|
144
|
+
THROUGHPUT_WINDOWS_MS,
|
|
145
|
+
THROUGHPUT_BUCKET_COUNT,
|
|
146
|
+
UsageThroughputTracker,
|
|
147
|
+
getSharedUsageThroughputTracker,
|
|
148
|
+
setSharedUsageThroughputTracker,
|
|
149
|
+
__resetSharedUsageThroughputTrackerForTests
|
|
150
|
+
};
|
|
@@ -10,12 +10,14 @@ var UsageRecorder = class {
|
|
|
10
10
|
this.logger = logger;
|
|
11
11
|
this.defer = options.defer ?? ((fn) => setTimeout(fn, 0));
|
|
12
12
|
this.onRecord = options.onRecord;
|
|
13
|
+
this.onEvent = options.onEvent;
|
|
13
14
|
}
|
|
14
15
|
store;
|
|
15
16
|
pricing;
|
|
16
17
|
logger;
|
|
17
18
|
defer;
|
|
18
19
|
onRecord;
|
|
20
|
+
onEvent;
|
|
19
21
|
/**
|
|
20
22
|
* Record one LLM request. Returns immediately — the actual store insert is
|
|
21
23
|
* deferred so the caller's response path is unblocked.
|
|
@@ -85,6 +87,12 @@ var UsageRecorder = class {
|
|
|
85
87
|
...input.cacheKeySource !== void 0 ? { cacheKeySource: input.cacheKeySource } : {},
|
|
86
88
|
...input.cacheKeyInjected !== void 0 ? { cacheKeyInjected: input.cacheKeyInjected } : {}
|
|
87
89
|
};
|
|
90
|
+
if (this.onEvent) {
|
|
91
|
+
try {
|
|
92
|
+
this.onEvent(row, Date.now());
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
}
|
|
88
96
|
try {
|
|
89
97
|
return await this.store.insert(row);
|
|
90
98
|
} catch (err) {
|
|
@@ -33,9 +33,11 @@ import {
|
|
|
33
33
|
captureCallerIdentity
|
|
34
34
|
} from "./chunk-XX6NQJMA.js";
|
|
35
35
|
import {
|
|
36
|
+
MATCH_TEXT_PER_MESSAGE_CAP,
|
|
36
37
|
collectMatchText,
|
|
37
38
|
deriveGatewaySessionKey,
|
|
38
|
-
deriveSubscriptionSessionKey
|
|
39
|
+
deriveSubscriptionSessionKey,
|
|
40
|
+
flattenMatchText
|
|
39
41
|
} from "./chunk-GFIZ6LQI.js";
|
|
40
42
|
import {
|
|
41
43
|
emitWebhookEvent
|
|
@@ -104,7 +106,7 @@ import {
|
|
|
104
106
|
} from "./chunk-H5JUT3KV.js";
|
|
105
107
|
import {
|
|
106
108
|
beginAuditCapture
|
|
107
|
-
} from "./chunk-
|
|
109
|
+
} from "./chunk-GXEZ2R3E.js";
|
|
108
110
|
import {
|
|
109
111
|
convertAnthropicToOpenAI,
|
|
110
112
|
convertOpenAIToAnthropic
|
|
@@ -3664,6 +3666,7 @@ function normalizeAudit(raw) {
|
|
|
3664
3666
|
retentionDays: Math.trunc(
|
|
3665
3667
|
clampNumber(a?.retentionDays, 1, 365, DEFAULT_AUDIT_CONFIG.retentionDays)
|
|
3666
3668
|
),
|
|
3669
|
+
compactStreamingBodies: a?.compactStreamingBodies === true,
|
|
3667
3670
|
trustForwardedFor: a?.trustForwardedFor === true
|
|
3668
3671
|
};
|
|
3669
3672
|
}
|
|
@@ -4131,8 +4134,62 @@ function mergeServerConfig(current, patch) {
|
|
|
4131
4134
|
});
|
|
4132
4135
|
}
|
|
4133
4136
|
|
|
4137
|
+
// src/outbound-api/auditSessionKey.ts
|
|
4138
|
+
import { createHash as createHash2 } from "crypto";
|
|
4139
|
+
var EXPLICIT_ID_SOURCES = /* @__PURE__ */ new Set([
|
|
4140
|
+
"session-header",
|
|
4141
|
+
"thread-header",
|
|
4142
|
+
"body-session-id",
|
|
4143
|
+
"body-thread-id",
|
|
4144
|
+
"prompt-cache-key"
|
|
4145
|
+
]);
|
|
4146
|
+
var ANTHROPIC_SESSION_RE = /_session_([A-Za-z0-9][A-Za-z0-9_-]{7,})/;
|
|
4147
|
+
var DIGEST_HEX = 32;
|
|
4148
|
+
function stableDigest(value) {
|
|
4149
|
+
return createHash2("sha256").update(value, "utf8").digest("hex").slice(0, DIGEST_HEX);
|
|
4150
|
+
}
|
|
4151
|
+
function asObject(value) {
|
|
4152
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4153
|
+
}
|
|
4154
|
+
function anthropicSessionId(body) {
|
|
4155
|
+
const userId = asObject(body["metadata"])?.["user_id"];
|
|
4156
|
+
if (typeof userId !== "string" || !userId) return void 0;
|
|
4157
|
+
const matched = ANTHROPIC_SESSION_RE.exec(userId);
|
|
4158
|
+
return matched?.[1];
|
|
4159
|
+
}
|
|
4160
|
+
function anthropicContentFingerprint(body) {
|
|
4161
|
+
const messages = body["messages"];
|
|
4162
|
+
if (!Array.isArray(messages) || messages.length === 0) return void 0;
|
|
4163
|
+
const parts = [];
|
|
4164
|
+
const system = flattenMatchText(body["system"]).trim();
|
|
4165
|
+
if (system) parts.push(system.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
4166
|
+
let firstUser = "";
|
|
4167
|
+
for (const entry of messages) {
|
|
4168
|
+
const message = asObject(entry);
|
|
4169
|
+
if (!message || message["role"] !== "user") continue;
|
|
4170
|
+
const text = flattenMatchText(message["content"]).trim();
|
|
4171
|
+
if (!text) continue;
|
|
4172
|
+
firstUser = text.slice(0, MATCH_TEXT_PER_MESSAGE_CAP);
|
|
4173
|
+
break;
|
|
4174
|
+
}
|
|
4175
|
+
if (parts.length === 0 && !firstUser) return void 0;
|
|
4176
|
+
return `${parts.join("\0")}\0${firstUser}`;
|
|
4177
|
+
}
|
|
4178
|
+
function deriveAuditSessionKey(body, headers = {}, options = {}) {
|
|
4179
|
+
const anthropicSession = anthropicSessionId(body);
|
|
4180
|
+
if (anthropicSession) return stableDigest(`anthropic-session\0${anthropicSession}`);
|
|
4181
|
+
const gateway = deriveGatewaySessionKey(body, headers, {
|
|
4182
|
+
...options.fallbackKey !== void 0 ? { fallbackKey: options.fallbackKey } : {},
|
|
4183
|
+
...options.endpoint !== void 0 ? { endpoint: options.endpoint } : {}
|
|
4184
|
+
});
|
|
4185
|
+
if (EXPLICIT_ID_SOURCES.has(gateway.source)) return gateway.key;
|
|
4186
|
+
const fingerprint = anthropicContentFingerprint(body);
|
|
4187
|
+
if (fingerprint) return stableDigest(`anthropic-content\0${fingerprint}`);
|
|
4188
|
+
return gateway.key;
|
|
4189
|
+
}
|
|
4190
|
+
|
|
4134
4191
|
// src/outbound-api/outboundApiKeyAuth.ts
|
|
4135
|
-
import { createHash as
|
|
4192
|
+
import { createHash as createHash3, randomBytes as randomBytes4 } from "crypto";
|
|
4136
4193
|
|
|
4137
4194
|
// src/outbound-api/keyPolicy.ts
|
|
4138
4195
|
var DAY_MS = 864e5;
|
|
@@ -4197,7 +4254,7 @@ function generateSecret() {
|
|
|
4197
4254
|
return KEY_PREFIX + randomBase62(SECRET_BYTES);
|
|
4198
4255
|
}
|
|
4199
4256
|
function hashKey(secret) {
|
|
4200
|
-
return
|
|
4257
|
+
return createHash3("sha256").update(secret, "utf8").digest("hex");
|
|
4201
4258
|
}
|
|
4202
4259
|
function keyPrefix(secret) {
|
|
4203
4260
|
return secret.slice(0, PREFIX_LEN);
|
|
@@ -4525,7 +4582,7 @@ var OutboundRateLimiter = class {
|
|
|
4525
4582
|
function asArray(v) {
|
|
4526
4583
|
return Array.isArray(v) ? v : [];
|
|
4527
4584
|
}
|
|
4528
|
-
function
|
|
4585
|
+
function asObject2(v) {
|
|
4529
4586
|
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
4530
4587
|
}
|
|
4531
4588
|
function last(arr) {
|
|
@@ -4533,13 +4590,13 @@ function last(arr) {
|
|
|
4533
4590
|
}
|
|
4534
4591
|
function isUserMessageAnthropic(body) {
|
|
4535
4592
|
const messages = asArray(body["messages"]);
|
|
4536
|
-
const lastMsg =
|
|
4593
|
+
const lastMsg = asObject2(last(messages));
|
|
4537
4594
|
if (!lastMsg || lastMsg["role"] !== "user") return false;
|
|
4538
4595
|
const content = lastMsg["content"];
|
|
4539
4596
|
if (typeof content === "string") return true;
|
|
4540
4597
|
if (Array.isArray(content)) {
|
|
4541
4598
|
const hasToolResult = content.some(
|
|
4542
|
-
(block) =>
|
|
4599
|
+
(block) => asObject2(block)?.["type"] === "tool_result"
|
|
4543
4600
|
);
|
|
4544
4601
|
return !hasToolResult;
|
|
4545
4602
|
}
|
|
@@ -4549,7 +4606,7 @@ function isUserMessageResponses(body) {
|
|
|
4549
4606
|
const input = body["input"];
|
|
4550
4607
|
if (typeof input === "string") return input.length > 0;
|
|
4551
4608
|
if (Array.isArray(input)) {
|
|
4552
|
-
const lastItem =
|
|
4609
|
+
const lastItem = asObject2(last(input));
|
|
4553
4610
|
if (!lastItem) return false;
|
|
4554
4611
|
if (lastItem["type"] === "function_call_output") return false;
|
|
4555
4612
|
return lastItem["role"] === "user";
|
|
@@ -4558,22 +4615,22 @@ function isUserMessageResponses(body) {
|
|
|
4558
4615
|
}
|
|
4559
4616
|
function isUserMessageChat(body) {
|
|
4560
4617
|
const messages = asArray(body["messages"]);
|
|
4561
|
-
const lastMsg =
|
|
4618
|
+
const lastMsg = asObject2(last(messages));
|
|
4562
4619
|
if (!lastMsg) return false;
|
|
4563
4620
|
return lastMsg["role"] === "user";
|
|
4564
4621
|
}
|
|
4565
4622
|
function isUserMessageGemini(body) {
|
|
4566
4623
|
const contents = asArray(body["contents"]);
|
|
4567
|
-
const lastContent =
|
|
4624
|
+
const lastContent = asObject2(last(contents));
|
|
4568
4625
|
if (!lastContent || lastContent["role"] !== "user") return false;
|
|
4569
4626
|
const parts = asArray(lastContent["parts"]);
|
|
4570
4627
|
const hasFunctionResponse = parts.some(
|
|
4571
|
-
(part) =>
|
|
4628
|
+
(part) => asObject2(part)?.["functionResponse"] !== void 0
|
|
4572
4629
|
);
|
|
4573
4630
|
return !hasFunctionResponse;
|
|
4574
4631
|
}
|
|
4575
4632
|
function isUserMessageRequest(endpoint, parsedBody) {
|
|
4576
|
-
const body =
|
|
4633
|
+
const body = asObject2(parsedBody);
|
|
4577
4634
|
if (!body) return false;
|
|
4578
4635
|
switch (endpoint) {
|
|
4579
4636
|
case "messages":
|
|
@@ -4723,7 +4780,7 @@ var UserMessageSerialQueue = class {
|
|
|
4723
4780
|
};
|
|
4724
4781
|
|
|
4725
4782
|
// src/outbound-api/voucher.ts
|
|
4726
|
-
import { createHash as
|
|
4783
|
+
import { createHash as createHash4 } from "crypto";
|
|
4727
4784
|
var CODE_ENTROPY_CHARS = 32;
|
|
4728
4785
|
var CODE_PREFIX = "CC_";
|
|
4729
4786
|
var DISPLAY_PREFIX_LEN = 8;
|
|
@@ -4732,7 +4789,7 @@ function generateVoucherCode() {
|
|
|
4732
4789
|
return CODE_PREFIX + randomBase62(CODE_ENTROPY_CHARS);
|
|
4733
4790
|
}
|
|
4734
4791
|
function hashVoucherCode(code) {
|
|
4735
|
-
return
|
|
4792
|
+
return createHash4("sha256").update(code, "utf8").digest("hex");
|
|
4736
4793
|
}
|
|
4737
4794
|
function voucherCodePrefix(code) {
|
|
4738
4795
|
return code.slice(0, DISPLAY_PREFIX_LEN);
|
|
@@ -5185,7 +5242,13 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
5185
5242
|
writeJsonError(res, 400, "Invalid JSON in request body");
|
|
5186
5243
|
return;
|
|
5187
5244
|
}
|
|
5188
|
-
if (audit)
|
|
5245
|
+
if (audit) {
|
|
5246
|
+
audit.setRequestBody(rawBody);
|
|
5247
|
+
audit.sessionKey = deriveAuditSessionKey(parsedBody, req.headers, {
|
|
5248
|
+
fallbackKey: verified.id,
|
|
5249
|
+
endpoint
|
|
5250
|
+
});
|
|
5251
|
+
}
|
|
5189
5252
|
if (endpoint === "gemini" && typeof parsedBody["model"] !== "string") {
|
|
5190
5253
|
const urlModel = extractGeminiModelFromUrl(req.url);
|
|
5191
5254
|
if (urlModel) parsedBody["model"] = urlModel;
|
|
@@ -11,6 +11,156 @@ import {
|
|
|
11
11
|
|
|
12
12
|
// src/outbound-api/auditCapture.ts
|
|
13
13
|
import { randomUUID } from "crypto";
|
|
14
|
+
|
|
15
|
+
// src/outbound-api/auditSseCompact.ts
|
|
16
|
+
var MERGED_FRAMES_FIELD = "_omnicrossMergedFrames";
|
|
17
|
+
function asObject(value) {
|
|
18
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
19
|
+
}
|
|
20
|
+
function scalar(value) {
|
|
21
|
+
return typeof value === "string" || typeof value === "number" ? String(value) : "";
|
|
22
|
+
}
|
|
23
|
+
function classify(payload) {
|
|
24
|
+
const type = scalar(payload["type"]);
|
|
25
|
+
if (type === "content_block_delta") {
|
|
26
|
+
const delta = asObject(payload["delta"]);
|
|
27
|
+
const deltaType = scalar(delta?.["type"]);
|
|
28
|
+
const field = deltaType === "text_delta" ? "text" : deltaType === "thinking_delta" ? "thinking" : deltaType === "input_json_delta" ? "partial_json" : null;
|
|
29
|
+
if (!delta || !field || typeof delta[field] !== "string") return null;
|
|
30
|
+
return {
|
|
31
|
+
channel: "anthropic:" + scalar(payload["index"]) + ":" + deltaType,
|
|
32
|
+
text: delta[field],
|
|
33
|
+
payload,
|
|
34
|
+
textPath: ["delta", field]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (type.endsWith(".delta") && typeof payload["delta"] === "string") {
|
|
38
|
+
const channel = [
|
|
39
|
+
"responses",
|
|
40
|
+
type,
|
|
41
|
+
scalar(payload["item_id"]),
|
|
42
|
+
scalar(payload["output_index"]),
|
|
43
|
+
scalar(payload["content_index"])
|
|
44
|
+
].join(":");
|
|
45
|
+
return { channel, text: payload["delta"], payload, textPath: ["delta"] };
|
|
46
|
+
}
|
|
47
|
+
const choices = payload["choices"];
|
|
48
|
+
if (Array.isArray(choices) && choices.length === 1) {
|
|
49
|
+
const choice = asObject(choices[0]);
|
|
50
|
+
const delta = asObject(choice?.["delta"]);
|
|
51
|
+
if (choice && delta && typeof delta["content"] === "string" && choice["finish_reason"] == null && delta["tool_calls"] === void 0 && delta["function_call"] === void 0) {
|
|
52
|
+
return {
|
|
53
|
+
channel: "chat:" + scalar(choice["index"]),
|
|
54
|
+
text: delta["content"],
|
|
55
|
+
payload,
|
|
56
|
+
textPath: ["choices", "0", "delta", "content"]
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function withMergedText(frame, text, mergedCount) {
|
|
63
|
+
const clone = JSON.parse(JSON.stringify(frame.payload));
|
|
64
|
+
let cursor = clone;
|
|
65
|
+
for (let i = 0; i < frame.textPath.length - 1; i += 1) {
|
|
66
|
+
const step = frame.textPath[i];
|
|
67
|
+
const next = Array.isArray(cursor) ? cursor[Number(step)] : cursor[step];
|
|
68
|
+
if (next === null || typeof next !== "object") return clone;
|
|
69
|
+
cursor = next;
|
|
70
|
+
}
|
|
71
|
+
const leaf = frame.textPath[frame.textPath.length - 1];
|
|
72
|
+
if (Array.isArray(cursor)) cursor[Number(leaf)] = text;
|
|
73
|
+
else cursor[leaf] = text;
|
|
74
|
+
clone[MERGED_FRAMES_FIELD] = mergedCount;
|
|
75
|
+
return clone;
|
|
76
|
+
}
|
|
77
|
+
function splitBlocks(text, nl) {
|
|
78
|
+
const blocks = [];
|
|
79
|
+
let head = [];
|
|
80
|
+
let data = null;
|
|
81
|
+
let raw = [];
|
|
82
|
+
const flush = () => {
|
|
83
|
+
if (raw.length === 0) return;
|
|
84
|
+
blocks.push({ head, data, raw: raw.join(nl) });
|
|
85
|
+
head = [];
|
|
86
|
+
data = null;
|
|
87
|
+
raw = [];
|
|
88
|
+
};
|
|
89
|
+
for (const line of text.split(nl)) {
|
|
90
|
+
if (line.trim() === "") {
|
|
91
|
+
flush();
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
raw.push(line);
|
|
95
|
+
if (line.startsWith("data:")) {
|
|
96
|
+
const payload = line.slice("data:".length).trimStart();
|
|
97
|
+
data = data === null ? payload : data + nl + payload;
|
|
98
|
+
} else {
|
|
99
|
+
head.push(line);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
flush();
|
|
103
|
+
return blocks;
|
|
104
|
+
}
|
|
105
|
+
function compactSseBody(text) {
|
|
106
|
+
if (!text || !text.includes("data:")) return text;
|
|
107
|
+
const nl = "\n";
|
|
108
|
+
const blocks = splitBlocks(text, nl);
|
|
109
|
+
if (blocks.length === 0) return text;
|
|
110
|
+
const out = [];
|
|
111
|
+
let runFrame = null;
|
|
112
|
+
let runBlock = null;
|
|
113
|
+
let runText = "";
|
|
114
|
+
let runCount = 0;
|
|
115
|
+
let merged = 0;
|
|
116
|
+
const flushRun = () => {
|
|
117
|
+
if (runFrame === null || runBlock === null) return;
|
|
118
|
+
if (runCount === 1) {
|
|
119
|
+
out.push(runBlock.raw);
|
|
120
|
+
} else {
|
|
121
|
+
const payload = withMergedText(runFrame, runText, runCount);
|
|
122
|
+
out.push([...runBlock.head, "data: " + JSON.stringify(payload)].join(nl));
|
|
123
|
+
merged += runCount;
|
|
124
|
+
}
|
|
125
|
+
runFrame = null;
|
|
126
|
+
runBlock = null;
|
|
127
|
+
runText = "";
|
|
128
|
+
runCount = 0;
|
|
129
|
+
};
|
|
130
|
+
for (const block of blocks) {
|
|
131
|
+
let frame = null;
|
|
132
|
+
if (block.data !== null && block.data !== "[DONE]") {
|
|
133
|
+
try {
|
|
134
|
+
const parsed = asObject(JSON.parse(block.data));
|
|
135
|
+
if (parsed) frame = classify(parsed);
|
|
136
|
+
} catch {
|
|
137
|
+
frame = null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (frame === null) {
|
|
141
|
+
flushRun();
|
|
142
|
+
out.push(block.raw);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const open = runFrame;
|
|
146
|
+
if (open !== null && open.channel === frame.channel) {
|
|
147
|
+
runText += frame.text;
|
|
148
|
+
runCount += 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
flushRun();
|
|
152
|
+
runFrame = frame;
|
|
153
|
+
runBlock = block;
|
|
154
|
+
runText = frame.text;
|
|
155
|
+
runCount = 1;
|
|
156
|
+
}
|
|
157
|
+
flushRun();
|
|
158
|
+
if (merged === 0) return text;
|
|
159
|
+
const trailer = /\s$/.test(text) ? nl + nl : "";
|
|
160
|
+
return out.join(nl + nl) + trailer;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/outbound-api/auditCapture.ts
|
|
14
164
|
function completeUtf8PrefixLength(buf) {
|
|
15
165
|
if (buf.length === 0) return 0;
|
|
16
166
|
let leadIndex = buf.length - 1;
|
|
@@ -112,15 +262,20 @@ function beginAuditCapture(req, res, now) {
|
|
|
112
262
|
if (!record.provider && usage.provider) record.provider = usage.provider;
|
|
113
263
|
}
|
|
114
264
|
if (ctx.error) record.error = redactAuditText(ctx.error);
|
|
265
|
+
if (ctx.sessionKey) record.sessionKey = ctx.sessionKey;
|
|
115
266
|
if (config.captureBodies) {
|
|
116
267
|
if (requestBody != null && requestBody.length > 0) {
|
|
117
268
|
record.requestBody = requestBody;
|
|
118
269
|
}
|
|
119
270
|
if (responseChunks.length > 0) {
|
|
120
271
|
const captured = Buffer.concat(responseChunks, responseBytes);
|
|
121
|
-
const
|
|
272
|
+
const decoded = decodeCapturedBody(captured, responseTruncated);
|
|
273
|
+
const body = config.compactStreamingBodies ? compactSseBody(decoded) : decoded;
|
|
122
274
|
record.responseBody = truncateToBytes(redactAuditText(body), config.maxBodyBytes);
|
|
123
275
|
}
|
|
276
|
+
if (record.requestBody !== void 0 || record.responseBody !== void 0) {
|
|
277
|
+
record.hasBody = true;
|
|
278
|
+
}
|
|
124
279
|
}
|
|
125
280
|
recordAudit(record);
|
|
126
281
|
} catch {
|