@foldrun/core 2.0.0 → 2.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/dist/src/catalog.d.ts +7 -0
- package/dist/src/catalog.js +16 -1
- package/dist/src/deploy.d.ts +6 -1
- package/dist/src/deploy.js +8 -5
- package/dist/src/model-credential.js +1 -1
- package/dist/src/model-env.js +1 -1
- package/dist/src/run-container.d.ts +2 -5
- package/dist/src/run-container.js +8 -1
- package/dist/src/runner.d.ts +2 -0
- package/dist/src/runner.js +11 -11
- package/dist/src/step-exec.d.ts +23 -5
- package/dist/src/step-exec.js +30 -23
- package/dist/src/store.d.ts +46 -12
- package/dist/src/store.js +60 -4
- package/dist/src/tool-loop.d.ts +4 -1
- package/dist/src/tool-loop.js +2 -0
- package/dist/src/translator.d.ts +12 -0
- package/dist/src/translator.js +36 -13
- package/package.json +1 -1
- package/src/catalog.ts +18 -2
- package/src/deploy.ts +11 -4
- package/src/model-credential.ts +1 -1
- package/src/model-env.ts +1 -1
- package/src/run-container.ts +10 -3
- package/src/runner.ts +12 -11
- package/src/step-exec.ts +44 -27
- package/src/store.ts +90 -10
- package/src/tool-loop.ts +6 -1
- package/src/translator.ts +39 -15
package/src/translator.ts
CHANGED
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
// and their results, streaming — and drops what a Chat-Completions endpoint
|
|
23
23
|
// has no word for (cache_control, top_k, server-side tools, thinking on
|
|
24
24
|
// endpoints that lack reasoning_effort). Anything dropped is said once in
|
|
25
|
-
// the run trace.
|
|
25
|
+
// the run trace. Those endpoints cache prompt prefixes on their own side;
|
|
26
|
+
// the cached share they report comes back as cache_read_input_tokens, so a
|
|
27
|
+
// step's record says how much of its context was re-read. The translation is pure functions over JSON so the tests
|
|
26
28
|
// can drive every mapping without a network; the server around them is a
|
|
27
29
|
// few dozen lines.
|
|
28
30
|
|
|
@@ -37,6 +39,34 @@ import { captureRefusalHeaders, isRefusalStatus, refusalLine } from "./refusal.t
|
|
|
37
39
|
|
|
38
40
|
type Json = Record<string, unknown>;
|
|
39
41
|
|
|
42
|
+
/**
|
|
43
|
+
* An OpenAI-shaped usage object as Anthropic's: fresh input, cached input,
|
|
44
|
+
* output. Both OpenAI shapes count the cached part INSIDE the prompt total
|
|
45
|
+
* (Chat's prompt_tokens_details.cached_tokens, Responses'
|
|
46
|
+
* input_tokens_details.cached_tokens; DeepSeek says prompt_cache_hit_tokens),
|
|
47
|
+
* so it is taken out of input here or the step would count it twice.
|
|
48
|
+
*/
|
|
49
|
+
export function anthropicUsage(u: Json | undefined, wire: TranslatorWire): { input: number; cached: number; output: number } {
|
|
50
|
+
if (!u) return { input: 0, cached: 0, output: 0 };
|
|
51
|
+
const total = Number((wire === "chat" ? u.prompt_tokens : u.input_tokens) ?? 0);
|
|
52
|
+
const details = (wire === "chat" ? u.prompt_tokens_details : u.input_tokens_details) as Json | undefined;
|
|
53
|
+
const raw = Number(details?.cached_tokens ?? u.prompt_cache_hit_tokens ?? 0);
|
|
54
|
+
const cached = Number.isFinite(raw) ? Math.max(0, Math.min(raw, total)) : 0;
|
|
55
|
+
return {
|
|
56
|
+
input: total - cached,
|
|
57
|
+
cached,
|
|
58
|
+
output: Number((wire === "chat" ? u.completion_tokens : u.output_tokens) ?? 0),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function usageBlock(u: { input: number; cached: number; output: number } | null): Json {
|
|
63
|
+
return {
|
|
64
|
+
input_tokens: u?.input ?? 0,
|
|
65
|
+
output_tokens: u?.output ?? 0,
|
|
66
|
+
...(u && u.cached > 0 ? { cache_read_input_tokens: u.cached } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
export type TranslatorWire = "chat" | "responses";
|
|
41
71
|
|
|
42
72
|
export interface TranslatorSpec {
|
|
@@ -493,10 +523,7 @@ export function fromChatCompletion(res: Json, requestedModel: string, stopSequen
|
|
|
493
523
|
content,
|
|
494
524
|
stop_reason: stop.reason,
|
|
495
525
|
stop_sequence: stop.sequence,
|
|
496
|
-
usage:
|
|
497
|
-
input_tokens: Number(usage.prompt_tokens ?? 0),
|
|
498
|
-
output_tokens: Number(usage.completion_tokens ?? 0),
|
|
499
|
-
},
|
|
526
|
+
usage: usageBlock(anthropicUsage(usage, "chat")),
|
|
500
527
|
};
|
|
501
528
|
}
|
|
502
529
|
|
|
@@ -515,7 +542,7 @@ export class StreamTranslator {
|
|
|
515
542
|
private tools = new Map<number, { block: number; argsSeen: boolean }>();
|
|
516
543
|
/** The last choice's stop fields: finish_reason and any matched stop. */
|
|
517
544
|
private finish: Json = {};
|
|
518
|
-
private usage: { input: number; output: number } | null = null;
|
|
545
|
+
private usage: { input: number; cached: number; output: number } | null = null;
|
|
519
546
|
private id = `msg_${crypto.randomBytes(8).toString("hex")}`;
|
|
520
547
|
private readonly model: string;
|
|
521
548
|
private readonly stops: string[];
|
|
@@ -561,7 +588,7 @@ export class StreamTranslator {
|
|
|
561
588
|
const out = this.start(chunk);
|
|
562
589
|
const u = chunk.usage as Json | undefined;
|
|
563
590
|
if (u && (u.prompt_tokens !== undefined || u.completion_tokens !== undefined)) {
|
|
564
|
-
this.usage =
|
|
591
|
+
this.usage = anthropicUsage(u, "chat");
|
|
565
592
|
}
|
|
566
593
|
const choice = ((chunk.choices as Json[] | undefined) ?? [])[0];
|
|
567
594
|
if (!choice) return out;
|
|
@@ -650,7 +677,7 @@ export class StreamTranslator {
|
|
|
650
677
|
this.event("message_delta", {
|
|
651
678
|
type: "message_delta",
|
|
652
679
|
delta: { stop_reason: stop.reason, stop_sequence: stop.sequence },
|
|
653
|
-
usage:
|
|
680
|
+
usage: usageBlock(this.usage),
|
|
654
681
|
}),
|
|
655
682
|
);
|
|
656
683
|
out.push(this.event("message_stop", { type: "message_stop" }));
|
|
@@ -691,10 +718,7 @@ export function fromResponses(res: Json, requestedModel: string): Json {
|
|
|
691
718
|
content,
|
|
692
719
|
stop_reason: stop,
|
|
693
720
|
stop_sequence: null,
|
|
694
|
-
usage:
|
|
695
|
-
input_tokens: Number(usage.input_tokens ?? 0),
|
|
696
|
-
output_tokens: Number(usage.output_tokens ?? 0),
|
|
697
|
-
},
|
|
721
|
+
usage: usageBlock(anthropicUsage(usage, "responses")),
|
|
698
722
|
};
|
|
699
723
|
}
|
|
700
724
|
|
|
@@ -710,7 +734,7 @@ export class ResponsesStreamTranslator {
|
|
|
710
734
|
private nextIndex = 0;
|
|
711
735
|
/** Responses output_index → Anthropic block index and kind. */
|
|
712
736
|
private blocks = new Map<number, { block: number; kind: "text" | "tool"; argsSeen: boolean }>();
|
|
713
|
-
private usage: { input: number; output: number } | null = null;
|
|
737
|
+
private usage: { input: number; cached: number; output: number } | null = null;
|
|
714
738
|
private incomplete: string | null = null;
|
|
715
739
|
private sawTool = false;
|
|
716
740
|
private sawRefusal = false;
|
|
@@ -812,7 +836,7 @@ export class ResponsesStreamTranslator {
|
|
|
812
836
|
if (type === "response.completed" || type === "response.incomplete" || type === "response.failed") {
|
|
813
837
|
const r = (ev.response as Json | undefined) ?? {};
|
|
814
838
|
const u = r.usage as Json | undefined;
|
|
815
|
-
if (u) this.usage =
|
|
839
|
+
if (u) this.usage = anthropicUsage(u, "responses");
|
|
816
840
|
const reason = (r.incomplete_details as Json | undefined)?.reason;
|
|
817
841
|
if (typeof reason === "string") this.incomplete = reason;
|
|
818
842
|
return out;
|
|
@@ -834,7 +858,7 @@ export class ResponsesStreamTranslator {
|
|
|
834
858
|
this.event("message_delta", {
|
|
835
859
|
type: "message_delta",
|
|
836
860
|
delta: { stop_reason: reason, stop_sequence: null },
|
|
837
|
-
usage:
|
|
861
|
+
usage: usageBlock(this.usage),
|
|
838
862
|
}),
|
|
839
863
|
);
|
|
840
864
|
out.push(this.event("message_stop", { type: "message_stop" }));
|