rightmodeler 0.2.1 → 0.4.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/dist-bundle/cli.js +18011 -12067
- package/dist-bundle/provenance.js +89 -0
- package/dist-bundle/proxy/container-supervisor.mjs +6 -0
- package/dist-bundle/proxy/proxy-runtime.mjs +120 -21
- package/dist-bundle/transport/stream.js +8 -2
- package/docs/commands.md +149 -22
- package/docs/evaluators.md +88 -9
- package/docs/exit-codes.md +47 -2
- package/docs/gateways.md +109 -0
- package/docs/getting-started.md +170 -6
- package/docs/github-actions.md +328 -0
- package/docs/github.md +110 -0
- package/docs/modeb.md +34 -0
- package/docs/model-routes.md +111 -0
- package/package.json +4 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const DATED_SNAPSHOT = /^\d{2,4}(?:-?\d{2}){1,2}$/;
|
|
2
|
+
const PORTKEY_CACHE_HITS = new Set(["hit", "semantic hit"]);
|
|
3
|
+
function objectOf(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
5
|
+
? value
|
|
6
|
+
: undefined;
|
|
7
|
+
}
|
|
8
|
+
function nonEmptyString(value) {
|
|
9
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
10
|
+
}
|
|
11
|
+
export function servedModel(body) {
|
|
12
|
+
return nonEmptyString(objectOf(body)?.model);
|
|
13
|
+
}
|
|
14
|
+
function modelParts(id) {
|
|
15
|
+
const segments = id.toLowerCase().split("/");
|
|
16
|
+
return {
|
|
17
|
+
name: segments[segments.length - 1],
|
|
18
|
+
...(segments.length > 1 ? { vendor: segments[segments.length - 2] } : {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function sameModel(requested, served) {
|
|
22
|
+
const want = modelParts(requested);
|
|
23
|
+
const got = modelParts(served);
|
|
24
|
+
if (want.vendor !== undefined &&
|
|
25
|
+
got.vendor !== undefined &&
|
|
26
|
+
want.vendor !== got.vendor) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (got.name === want.name)
|
|
30
|
+
return true;
|
|
31
|
+
const prefix = `${want.name}-`;
|
|
32
|
+
return (got.name.startsWith(prefix) &&
|
|
33
|
+
DATED_SNAPSHOT.test(got.name.slice(prefix.length)));
|
|
34
|
+
}
|
|
35
|
+
function header(headers, name) {
|
|
36
|
+
if (headers instanceof Headers)
|
|
37
|
+
return headers.get(name) ?? undefined;
|
|
38
|
+
const value = headers[name];
|
|
39
|
+
return typeof value === "string" ? value : value?.join(", ");
|
|
40
|
+
}
|
|
41
|
+
function substitution(kind, evidence) {
|
|
42
|
+
return { kind, evidence: evidence.slice(0, 200) };
|
|
43
|
+
}
|
|
44
|
+
export function responseSubstitution(input) {
|
|
45
|
+
const body = objectOf(input.body);
|
|
46
|
+
const extra = objectOf(body?.extra_fields);
|
|
47
|
+
const served = servedModel(body);
|
|
48
|
+
if (served !== undefined && !sameModel(input.requestedModel, served)) {
|
|
49
|
+
return substitution("model", `served ${served} for requested ${input.requestedModel}`);
|
|
50
|
+
}
|
|
51
|
+
const fallback = nonEmptyString(objectOf(extra?.routing_info)?.server_side_fallback_model);
|
|
52
|
+
if (fallback !== undefined) {
|
|
53
|
+
return substitution("model", `bifrost server-side fallback served ${fallback}`);
|
|
54
|
+
}
|
|
55
|
+
const cacheStatus = header(input.headers, "x-portkey-cache-status");
|
|
56
|
+
if (cacheStatus !== undefined &&
|
|
57
|
+
PORTKEY_CACHE_HITS.has(cacheStatus.toLowerCase())) {
|
|
58
|
+
return substitution("cache", `x-portkey-cache-status: ${cacheStatus}`);
|
|
59
|
+
}
|
|
60
|
+
const cacheDebug = objectOf(extra?.cache_debug);
|
|
61
|
+
if (cacheDebug?.cache_hit === true) {
|
|
62
|
+
const hitType = cacheDebug.hit_type;
|
|
63
|
+
return substitution("cache", `bifrost cache hit${typeof hitType === "string" ? ` (${hitType})` : ""}`);
|
|
64
|
+
}
|
|
65
|
+
const hookResults = objectOf(body?.hook_results);
|
|
66
|
+
const transformed = [
|
|
67
|
+
hookResults?.before_request_hooks,
|
|
68
|
+
hookResults?.after_request_hooks,
|
|
69
|
+
]
|
|
70
|
+
.flatMap((hooks) => (Array.isArray(hooks) ? hooks : []))
|
|
71
|
+
.map(objectOf)
|
|
72
|
+
.find((hook) => hook?.transformed === true);
|
|
73
|
+
if (transformed !== undefined) {
|
|
74
|
+
const id = nonEmptyString(transformed.id) ?? "unnamed";
|
|
75
|
+
return substitution("request", `portkey hook ${id} transformed the call`);
|
|
76
|
+
}
|
|
77
|
+
const dropped = [
|
|
78
|
+
extra?.dropped_compat_plugin_params,
|
|
79
|
+
extra?.dropped_unsupported_tools,
|
|
80
|
+
].flatMap((items) => (Array.isArray(items) ? items : []));
|
|
81
|
+
if (dropped.length > 0) {
|
|
82
|
+
return substitution("request", `bifrost dropped ${dropped.join(", ")}`);
|
|
83
|
+
}
|
|
84
|
+
const converted = nonEmptyString(extra?.converted_request_type);
|
|
85
|
+
if (converted !== undefined) {
|
|
86
|
+
return substitution("request", `bifrost converted the request to ${converted}`);
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
@@ -30,6 +30,11 @@ const heartbeat = setInterval(
|
|
|
30
30
|
500,
|
|
31
31
|
);
|
|
32
32
|
heartbeat.unref();
|
|
33
|
+
const deadlineMs = Number(process.env.RM_DEADLINE_MS ?? 0);
|
|
34
|
+
let active = null;
|
|
35
|
+
if (deadlineMs > 0) {
|
|
36
|
+
setTimeout(() => active?.kill("SIGKILL"), deadlineMs).unref();
|
|
37
|
+
}
|
|
33
38
|
|
|
34
39
|
function start(command, stdoutPath, stderrPath, flags = "w") {
|
|
35
40
|
const stdout = createWriteStream(stdoutPath, { flags });
|
|
@@ -38,6 +43,7 @@ function start(command, stdoutPath, stderrPath, flags = "w") {
|
|
|
38
43
|
cwd: APP_ROOT,
|
|
39
44
|
stdio: ["ignore", "pipe", "pipe"],
|
|
40
45
|
});
|
|
46
|
+
active = child;
|
|
41
47
|
child.stdout.pipe(stdout);
|
|
42
48
|
child.stderr.pipe(stderr);
|
|
43
49
|
const closed = new Promise((resolve, reject) => {
|
|
@@ -12,6 +12,7 @@ import { join } from "node:path";
|
|
|
12
12
|
import { PassThrough } from "node:stream";
|
|
13
13
|
|
|
14
14
|
import { hopByHopHeaders } from "./headers.js";
|
|
15
|
+
import { responseSubstitution, servedModel } from "../provenance.js";
|
|
15
16
|
import { classifyStream } from "../transport/stream.js";
|
|
16
17
|
|
|
17
18
|
const maxRequestBytes = 10 * 1024 * 1024;
|
|
@@ -39,6 +40,12 @@ function isObject(value) {
|
|
|
39
40
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
// The same 1e-12 snap as roundUsd in ../budget.ts, which this runtime ships without:
|
|
44
|
+
// lease sums then do not depend on the order requests settle in.
|
|
45
|
+
function roundUsd(value) {
|
|
46
|
+
return Number(value.toFixed(12));
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
function parseConfig() {
|
|
43
50
|
const rawSwapPolicy = jsonEnv("RM_SWAP_POLICY");
|
|
44
51
|
if (
|
|
@@ -69,7 +76,32 @@ function parseConfig() {
|
|
|
69
76
|
"RM_PRICING_TABLE values must contain non-negative input and output prices",
|
|
70
77
|
);
|
|
71
78
|
}
|
|
72
|
-
|
|
79
|
+
if (
|
|
80
|
+
pricing.maxOutputTokens !== undefined &&
|
|
81
|
+
(!Number.isSafeInteger(pricing.maxOutputTokens) ||
|
|
82
|
+
pricing.maxOutputTokens <= 0)
|
|
83
|
+
) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
"RM_PRICING_TABLE maxOutputTokens must be a positive integer",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
pricingTable[model] = {
|
|
89
|
+
input: pricing.input,
|
|
90
|
+
output: pricing.output,
|
|
91
|
+
...(pricing.maxOutputTokens === undefined
|
|
92
|
+
? {}
|
|
93
|
+
: { maxOutputTokens: pricing.maxOutputTokens }),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const defaultMaxOutputTokens = Number(
|
|
98
|
+
requiredEnv("RM_DEFAULT_MAX_OUTPUT_TOKENS"),
|
|
99
|
+
);
|
|
100
|
+
if (
|
|
101
|
+
!Number.isSafeInteger(defaultMaxOutputTokens) ||
|
|
102
|
+
defaultMaxOutputTokens <= 0
|
|
103
|
+
) {
|
|
104
|
+
throw new Error("RM_DEFAULT_MAX_OUTPUT_TOKENS must be a positive integer");
|
|
73
105
|
}
|
|
74
106
|
|
|
75
107
|
const lease = jsonEnv("RM_BUDGET_LEASE");
|
|
@@ -88,6 +120,21 @@ function parseConfig() {
|
|
|
88
120
|
throw new Error("RM_EGRESS_URL must use http or https");
|
|
89
121
|
}
|
|
90
122
|
|
|
123
|
+
const requestHeaders =
|
|
124
|
+
process.env.RM_REQUEST_HEADERS === undefined
|
|
125
|
+
? {}
|
|
126
|
+
: jsonEnv("RM_REQUEST_HEADERS");
|
|
127
|
+
if (
|
|
128
|
+
!isObject(requestHeaders) ||
|
|
129
|
+
Object.entries(requestHeaders).some(
|
|
130
|
+
([name, value]) => name.length === 0 || typeof value !== "string",
|
|
131
|
+
)
|
|
132
|
+
) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
"RM_REQUEST_HEADERS must map header names to string values",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
91
138
|
return {
|
|
92
139
|
runId: requiredEnv("RM_RUN_ID"),
|
|
93
140
|
caseId: requiredEnv("RM_CASE_ID"),
|
|
@@ -98,7 +145,9 @@ function parseConfig() {
|
|
|
98
145
|
egressUrl,
|
|
99
146
|
swapPolicy,
|
|
100
147
|
pricingTable,
|
|
148
|
+
defaultMaxOutputTokens,
|
|
101
149
|
lease,
|
|
150
|
+
requestHeaders,
|
|
102
151
|
};
|
|
103
152
|
}
|
|
104
153
|
|
|
@@ -184,11 +233,11 @@ function loadState(spoolPath, checkpointPath) {
|
|
|
184
233
|
state.lastAttemptGroup = Math.max(state.lastAttemptGroup, row.attemptGroup);
|
|
185
234
|
state.groups.set(row.logicalCallId, row.attemptGroup);
|
|
186
235
|
state.attemptIds.add(row.attemptId);
|
|
187
|
-
state.spentUsd
|
|
236
|
+
state.spentUsd = roundUsd(state.spentUsd + row.costUsd);
|
|
188
237
|
}
|
|
189
238
|
|
|
190
239
|
for (const reservation of state.reservations.values()) {
|
|
191
|
-
state.spentUsd
|
|
240
|
+
state.spentUsd = roundUsd(state.spentUsd + reservation.reservedUsd);
|
|
192
241
|
}
|
|
193
242
|
|
|
194
243
|
return state;
|
|
@@ -222,12 +271,13 @@ function responseHeaders(headers) {
|
|
|
222
271
|
return forwarded;
|
|
223
272
|
}
|
|
224
273
|
|
|
225
|
-
function requestHeaders(headers, bodyLength) {
|
|
274
|
+
function requestHeaders(headers, bodyLength, configured) {
|
|
226
275
|
const forwarded = {};
|
|
227
276
|
for (const [name, value] of Object.entries(headers)) {
|
|
228
277
|
if (
|
|
229
278
|
value !== undefined &&
|
|
230
279
|
!hopByHopHeaders.has(name) &&
|
|
280
|
+
name !== "accept-encoding" &&
|
|
231
281
|
name !== "authorization" &&
|
|
232
282
|
name !== "host" &&
|
|
233
283
|
name !== "content-length"
|
|
@@ -235,6 +285,8 @@ function requestHeaders(headers, bodyLength) {
|
|
|
235
285
|
forwarded[name] = value;
|
|
236
286
|
}
|
|
237
287
|
}
|
|
288
|
+
Object.assign(forwarded, configured);
|
|
289
|
+
forwarded["accept-encoding"] = "identity";
|
|
238
290
|
forwarded["content-length"] = String(bodyLength);
|
|
239
291
|
return forwarded;
|
|
240
292
|
}
|
|
@@ -288,7 +340,11 @@ function requestUpstream(
|
|
|
288
340
|
return new Promise((resolve, reject) => {
|
|
289
341
|
const request = send(
|
|
290
342
|
url,
|
|
291
|
-
{
|
|
343
|
+
{
|
|
344
|
+
method,
|
|
345
|
+
path: `${url.pathname.replace(/\/$/, "")}${requestTarget}`,
|
|
346
|
+
headers,
|
|
347
|
+
},
|
|
292
348
|
(response) => {
|
|
293
349
|
clearTimeout(deadline);
|
|
294
350
|
resolve(response);
|
|
@@ -437,6 +493,7 @@ async function forwardStreaming(upstream, outgoing, status, spoolSink) {
|
|
|
437
493
|
spoolPath: result.spoolPath ?? null,
|
|
438
494
|
finishedWithoutSentinel: result.finishedWithoutSentinel === true,
|
|
439
495
|
upstreamFailed,
|
|
496
|
+
model: result.model ?? null,
|
|
440
497
|
};
|
|
441
498
|
}
|
|
442
499
|
|
|
@@ -489,6 +546,7 @@ async function forwardNonStreaming(upstream, outgoing, status) {
|
|
|
489
546
|
streamOutcome: "completed",
|
|
490
547
|
usage: isObject(body) ? (body.usage ?? null) : null,
|
|
491
548
|
upstreamFailed: false,
|
|
549
|
+
body,
|
|
492
550
|
};
|
|
493
551
|
} catch {
|
|
494
552
|
return { streamOutcome: "truncated", usage: null, upstreamFailed: false };
|
|
@@ -624,13 +682,14 @@ async function main() {
|
|
|
624
682
|
return;
|
|
625
683
|
}
|
|
626
684
|
|
|
627
|
-
const
|
|
685
|
+
const requestedLimit =
|
|
686
|
+
parsed?.max_completion_tokens ?? parsed?.max_tokens ?? undefined;
|
|
628
687
|
if (
|
|
629
688
|
!isObject(parsed) ||
|
|
630
689
|
typeof parsed.model !== "string" ||
|
|
631
690
|
parsed.model.length === 0 ||
|
|
632
|
-
|
|
633
|
-
|
|
691
|
+
(requestedLimit !== undefined &&
|
|
692
|
+
(!Number.isSafeInteger(requestedLimit) || requestedLimit < 0))
|
|
634
693
|
) {
|
|
635
694
|
recordLost({
|
|
636
695
|
attemptGroup,
|
|
@@ -640,13 +699,26 @@ async function main() {
|
|
|
640
699
|
startedAt,
|
|
641
700
|
});
|
|
642
701
|
sendJson(outgoing, 400, {
|
|
643
|
-
error:
|
|
702
|
+
error:
|
|
703
|
+
"Request body requires model; max_completion_tokens or max_tokens must be a non-negative integer when present.",
|
|
644
704
|
});
|
|
645
705
|
return;
|
|
646
706
|
}
|
|
647
707
|
|
|
708
|
+
const swapped = stepId in config.swapPolicy;
|
|
648
709
|
const model = config.swapPolicy[stepId] ?? parsed.model;
|
|
649
|
-
const rewritten = {
|
|
710
|
+
const rewritten = {
|
|
711
|
+
...parsed,
|
|
712
|
+
model,
|
|
713
|
+
...(parsed.stream === true
|
|
714
|
+
? {
|
|
715
|
+
stream_options: {
|
|
716
|
+
...(isObject(parsed.stream_options) ? parsed.stream_options : {}),
|
|
717
|
+
include_usage: true,
|
|
718
|
+
},
|
|
719
|
+
}
|
|
720
|
+
: {}),
|
|
721
|
+
};
|
|
650
722
|
const forwardedBody = Buffer.from(JSON.stringify(rewritten));
|
|
651
723
|
const pricing = config.pricingTable[model];
|
|
652
724
|
if (pricing === undefined) {
|
|
@@ -662,13 +734,18 @@ async function main() {
|
|
|
662
734
|
});
|
|
663
735
|
return;
|
|
664
736
|
}
|
|
737
|
+
const maxTokens =
|
|
738
|
+
requestedLimit ??
|
|
739
|
+
pricing.maxOutputTokens ??
|
|
740
|
+
config.defaultMaxOutputTokens;
|
|
665
741
|
|
|
666
742
|
const estimatedInputTokens = forwardedBody.length;
|
|
667
743
|
const estimatedWorstCaseUsd =
|
|
668
744
|
estimatedInputTokens * pricing.input + maxTokens * pricing.output;
|
|
669
|
-
const requiredLeaseUsd =
|
|
670
|
-
state.spentUsd + reservedUsd + estimatedWorstCaseUsd
|
|
671
|
-
|
|
745
|
+
const requiredLeaseUsd = roundUsd(
|
|
746
|
+
state.spentUsd + reservedUsd + estimatedWorstCaseUsd,
|
|
747
|
+
);
|
|
748
|
+
if (requiredLeaseUsd > roundUsd(config.lease.maxUsd)) {
|
|
672
749
|
appendRow(spoolPath, {
|
|
673
750
|
kind: "blocked",
|
|
674
751
|
runId: config.runId,
|
|
@@ -693,7 +770,7 @@ async function main() {
|
|
|
693
770
|
return;
|
|
694
771
|
}
|
|
695
772
|
|
|
696
|
-
reservedUsd
|
|
773
|
+
reservedUsd = roundUsd(reservedUsd + estimatedWorstCaseUsd);
|
|
697
774
|
const attemptId = nextAttemptId(state);
|
|
698
775
|
const responseSpoolPath = join(streamDirectory, `${attemptId}.txt`);
|
|
699
776
|
appendRow(spoolPath, {
|
|
@@ -726,21 +803,25 @@ async function main() {
|
|
|
726
803
|
upstreamSource: null,
|
|
727
804
|
upstreamFailed: false,
|
|
728
805
|
};
|
|
806
|
+
let provenance = {};
|
|
729
807
|
try {
|
|
730
808
|
const upstream = await requestUpstream(
|
|
731
809
|
config.egressUrl,
|
|
732
810
|
incoming.url ?? "/",
|
|
733
811
|
incoming.method ?? "POST",
|
|
734
|
-
requestHeaders(
|
|
812
|
+
requestHeaders(
|
|
813
|
+
incoming.headers,
|
|
814
|
+
forwardedBody.length,
|
|
815
|
+
config.requestHeaders,
|
|
816
|
+
),
|
|
735
817
|
forwardedBody,
|
|
736
818
|
streamHardDeadlineMs,
|
|
737
819
|
);
|
|
738
820
|
const status = upstream.statusCode ?? 502;
|
|
739
|
-
const declaredSource = upstream.headers[egressSourceHeader];
|
|
740
821
|
const upstreamSource =
|
|
741
|
-
|
|
742
|
-
?
|
|
743
|
-
:
|
|
822
|
+
upstream.headers[egressSourceHeader] === "egress"
|
|
823
|
+
? "egress"
|
|
824
|
+
: "provider";
|
|
744
825
|
const forwarded =
|
|
745
826
|
rewritten.stream === true && status < 400
|
|
746
827
|
? await forwardStreaming(upstream, outgoing, status, {
|
|
@@ -755,6 +836,23 @@ async function main() {
|
|
|
755
836
|
upstreamStatus: status,
|
|
756
837
|
upstreamSource: forwarded.upstreamFailed ? "egress" : upstreamSource,
|
|
757
838
|
};
|
|
839
|
+
if (swapped && status < 400) {
|
|
840
|
+
const answered =
|
|
841
|
+
forwarded.body ??
|
|
842
|
+
(typeof forwarded.model === "string"
|
|
843
|
+
? { model: forwarded.model }
|
|
844
|
+
: undefined);
|
|
845
|
+
const served = servedModel(answered);
|
|
846
|
+
const substitution = responseSubstitution({
|
|
847
|
+
requestedModel: model,
|
|
848
|
+
headers: upstream.headers,
|
|
849
|
+
body: answered,
|
|
850
|
+
});
|
|
851
|
+
provenance = {
|
|
852
|
+
...(served === undefined ? {} : { servedModel: served }),
|
|
853
|
+
...(substitution === undefined ? {} : { substitution }),
|
|
854
|
+
};
|
|
855
|
+
}
|
|
758
856
|
} catch {
|
|
759
857
|
if (!outgoing.headersSent) {
|
|
760
858
|
sendJson(outgoing, 502, { error: "Egress request failed." });
|
|
@@ -790,6 +888,7 @@ async function main() {
|
|
|
790
888
|
...(result.finishedWithoutSentinel
|
|
791
889
|
? { finishedWithoutSentinel: true }
|
|
792
890
|
: {}),
|
|
891
|
+
...provenance,
|
|
793
892
|
usage,
|
|
794
893
|
responseSpoolPath: result.spoolPath,
|
|
795
894
|
costUsd: leaseChargeUsd,
|
|
@@ -800,10 +899,10 @@ async function main() {
|
|
|
800
899
|
endedAt: new Date().toISOString(),
|
|
801
900
|
});
|
|
802
901
|
state.reservations.delete(attemptId);
|
|
803
|
-
state.spentUsd
|
|
902
|
+
state.spentUsd = roundUsd(state.spentUsd + leaseChargeUsd);
|
|
804
903
|
if (!outgoing.destroyed && !outgoing.writableEnded) outgoing.end();
|
|
805
904
|
} finally {
|
|
806
|
-
reservedUsd
|
|
905
|
+
reservedUsd = roundUsd(reservedUsd - estimatedWorstCaseUsd);
|
|
807
906
|
}
|
|
808
907
|
}
|
|
809
908
|
|
|
@@ -65,13 +65,13 @@ function parseUsage(value) {
|
|
|
65
65
|
if (!isRecord(value) ||
|
|
66
66
|
typeof value.prompt_tokens !== "number" ||
|
|
67
67
|
typeof value.completion_tokens !== "number" ||
|
|
68
|
-
typeof value.total_tokens !== "number") {
|
|
68
|
+
(value.total_tokens !== undefined && typeof value.total_tokens !== "number")) {
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
71
|
return {
|
|
72
72
|
inputTokens: value.prompt_tokens,
|
|
73
73
|
outputTokens: value.completion_tokens,
|
|
74
|
-
totalTokens: value.total_tokens,
|
|
74
|
+
totalTokens: value.total_tokens ?? value.prompt_tokens + value.completion_tokens,
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
function parseEvent(data) {
|
|
@@ -121,6 +121,9 @@ function parseEvent(data) {
|
|
|
121
121
|
usage: value.usage === undefined || value.usage === null
|
|
122
122
|
? null
|
|
123
123
|
: parseUsage(value.usage),
|
|
124
|
+
...(typeof value.model === "string" && value.model.length > 0
|
|
125
|
+
? { model: value.model }
|
|
126
|
+
: {}),
|
|
124
127
|
};
|
|
125
128
|
}
|
|
126
129
|
function eventData(event) {
|
|
@@ -144,6 +147,7 @@ export async function classifyStream(byteStream, options) {
|
|
|
144
147
|
const collector = new ContentCollector(options.spoolSink, processingController.signal);
|
|
145
148
|
let chunks = 0;
|
|
146
149
|
let usage = null;
|
|
150
|
+
let model;
|
|
147
151
|
let sawFinish = false;
|
|
148
152
|
let selectedResult;
|
|
149
153
|
const result = (outcome, reason, finishedWithoutSentinel = false) => {
|
|
@@ -154,6 +158,7 @@ export async function classifyStream(byteStream, options) {
|
|
|
154
158
|
usage,
|
|
155
159
|
chunks,
|
|
156
160
|
...(finishedWithoutSentinel ? { finishedWithoutSentinel: true } : {}),
|
|
161
|
+
...(model === undefined ? {} : { model }),
|
|
157
162
|
};
|
|
158
163
|
return selectedResult;
|
|
159
164
|
};
|
|
@@ -283,6 +288,7 @@ export async function classifyStream(byteStream, options) {
|
|
|
283
288
|
throw error;
|
|
284
289
|
}
|
|
285
290
|
usage = event.usage ?? usage;
|
|
291
|
+
model ??= event.model;
|
|
286
292
|
sawFinish ||= event.finished;
|
|
287
293
|
}
|
|
288
294
|
}
|