@bivelio/savings-layer 0.4.25 → 0.4.26
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/README.md +215 -11
- package/dist/adapters/anthropic-sdk.js +20 -19
- package/dist/adapters/langchain.js +54 -53
- package/dist/gateway/bin.js +892 -616
- package/dist/index.d.ts +674 -7
- package/dist/index.js +1285 -992
- package/docs/terminales/README.md +59 -11
- package/docs/terminales/aider.md +74 -0
- package/docs/terminales/continue.md +47 -0
- package/docs/terminales/copilot.md +114 -0
- package/docs/terminales/kilo.md +54 -31
- package/docs/terminales/no-soportados.md +41 -0
- package/docs/terminales/opencode.md +33 -11
- package/package.json +21 -3
package/README.md
CHANGED
|
@@ -74,12 +74,14 @@ products charge a flat subscription, so there is no per‑token bill to cut. The
|
|
|
74
74
|
story, in plain words:
|
|
75
75
|
**[Where does the saving install?](https://savings.bivelio.com/en/how-it-works)**
|
|
76
76
|
|
|
77
|
-
**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
up in your dashboard, token by token — nothing to configure, nothing
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
**A coding agent with an API key?** The package also ships a local measuring
|
|
78
|
+
gateway: `bvsala claude` (or `bvsala codex`, `goose`, `qwen`, `opencode`, `kilo`,
|
|
79
|
+
`copilot` with your own Anthropic key, `aider`) launches the agent through it and your real
|
|
80
|
+
spend shows up in your dashboard, token by token — nothing to configure, nothing
|
|
81
|
+
persisted. On exit it tells you how many requests it metered and which ones it
|
|
82
|
+
could not, and why. For Crush, Droid, Cline, Continue or Copilot in VS Code,
|
|
83
|
+
`bvsala doctor` prints the exact config block. Gemini CLI and Cursor cannot be
|
|
84
|
+
metered today, and the doctor says so. Like the SDK, the gateway is
|
|
83
85
|
fail‑closed: it starts only with your service key (`BIVELIO_SERVICE_KEY`) and an
|
|
84
86
|
active BiVelio license, which it collects with that key and verifies against the
|
|
85
87
|
key embedded in the package. Once running it never interrupts a request in flight:
|
|
@@ -94,7 +96,8 @@ as for any other use.
|
|
|
94
96
|
|
|
95
97
|
**The gateway forwards what your terminal sends.** With no lever armed, your
|
|
96
98
|
provider receives the request body byte for byte and every header your client
|
|
97
|
-
sends
|
|
99
|
+
sends except `x-bvsala-report`, which asks the gateway itself for a report (see
|
|
100
|
+
the limits): `cache_control` and its 1-hour TTL, tool search and
|
|
98
101
|
`tool_reference` blocks, the advisor, every `anthropic-beta` value. An armed lever
|
|
99
102
|
changes only what it declares, and a request it cannot rewrite exactly travels
|
|
100
103
|
untouched. A test suite checks this against a fake upstream on every change
|
|
@@ -140,6 +143,17 @@ is measurement, not savings: the saving levers of the SDK are TypeScript, and th
|
|
|
140
143
|
gateway's own levers apply to Anthropic `/v1/messages` traffic with a PRO license.
|
|
141
144
|
`bvsala doctor` prints the recipe for each SDK.
|
|
142
145
|
|
|
146
|
+
Each request can also bring its report back to your code. Send the header
|
|
147
|
+
`x-bvsala-report: 1` (in Python, `default_headers={"x-bvsala-report": "1"}` on
|
|
148
|
+
the client) and the gateway answers with `x-bvsala-request-id`. On the same
|
|
149
|
+
gateway, `GET /_bvsala/v1/reports/<that id>` returns what it recorded for your
|
|
150
|
+
dashboard: `metered` with the `SavingsReport` (the provider's token counts, the
|
|
151
|
+
cost at list price and `cost.basis`), or `not_metered` with the reason from the
|
|
152
|
+
log line. `?response_id=<the provider's response id>` finds it too, and
|
|
153
|
+
`wait_ms` waits for a request still in flight. The header never reaches your
|
|
154
|
+
provider, and a request without it is not changed at all:
|
|
155
|
+
[the details](./docs/terminales/README.md#what-the-gateway-guarantees-it-forwards-what-your-client-sends).
|
|
156
|
+
|
|
143
157
|
## Check it before you install
|
|
144
158
|
|
|
145
159
|
What you can check before paying is what we have measured: the published benchmark,
|
|
@@ -149,6 +163,22 @@ and the commission calculator on the front page, which runs on the same price
|
|
|
149
163
|
catalogue that bills you. The verifier that replays the optimizer on your own data
|
|
150
164
|
lives in your dashboard once you sign in.
|
|
151
165
|
|
|
166
|
+
## Set it up in one command
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npx @bivelio/savings-layer@latest init
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
It reads your `package.json` (Next.js, Express, the Vercel AI SDK, Mastra,
|
|
173
|
+
LangChain.js, the Anthropic SDK or the OpenAI SDK) and prints the three steps below,
|
|
174
|
+
written for your stack — the install line for your package manager, which keys are
|
|
175
|
+
missing, the exact code for your adapter — plus a free check: a question the layer
|
|
176
|
+
answers on your machine, so the model is not called. It reads `package.json` files only
|
|
177
|
+
(yours, and those in `node_modules` for the installed versions), never a `.env` file or a
|
|
178
|
+
key's value; it uses no network of its own and writes nothing.
|
|
179
|
+
`--write` creates the new files after asking you, and never overwrites one; `--json`
|
|
180
|
+
prints the same plan for a script or a coding agent.
|
|
181
|
+
|
|
152
182
|
## Install in three steps
|
|
153
183
|
|
|
154
184
|
**1 · The package**
|
|
@@ -332,8 +362,10 @@ shared here. `emitReportPart: true` also streams each report as a
|
|
|
332
362
|
|
|
333
363
|
### LangChain.js
|
|
334
364
|
|
|
335
|
-
`pnpm add @bivelio/savings-layer langchain @langchain/core` — an agent
|
|
336
|
-
`createAgent` in `langchain` 1.x
|
|
365
|
+
`pnpm add @bivelio/savings-layer langchain @langchain/core @langchain/openai` — an agent
|
|
366
|
+
middleware for `createAgent` in `langchain` 1.x. The model string (`"openai:…"`) is loaded
|
|
367
|
+
through its integration package, so `@langchain/openai` (or `@langchain/anthropic`) has
|
|
368
|
+
to be installed:
|
|
337
369
|
|
|
338
370
|
```ts
|
|
339
371
|
import { createAgent } from "langchain";
|
|
@@ -402,6 +434,19 @@ single-shot requests only: requests that declare tools, carry tool calls or resu
|
|
|
402
434
|
hold more than one user turn are left out unless you set
|
|
403
435
|
`savings.semanticCache: "include-agentic"`.
|
|
404
436
|
|
|
437
|
+
**Calibrated reuse (opt-in).** A fixed similarity floor means something different for
|
|
438
|
+
every embedder and every workload, so you can make the cache earn its floor on your own
|
|
439
|
+
traffic instead: `new InMemorySemanticCache({ embed, calibration: { maxErrorRate: 0.02 } })`.
|
|
440
|
+
It then serves no reworded answer until your labels show, at 95 % confidence, that at
|
|
441
|
+
most 2 % of the answers it would reuse from some score up are wrong, and it re-tests that
|
|
442
|
+
floor as labels accumulate. Until then every candidate goes to your provider, exactly as
|
|
443
|
+
it would without the cache, and the fresh answer is compared with the stored one by your
|
|
444
|
+
`judge` (by default only identical answers count, which suits classification and
|
|
445
|
+
structured output; pass your own for free text). Each calibrated reuse carries its bound
|
|
446
|
+
and the labels behind it in `report.qualityLedger.cacheMatch.calibration`. Calibration
|
|
447
|
+
costs reuses, never an extra call, and it does not change how anything is billed. If you
|
|
448
|
+
already have labeled pairs, `calibrateSemanticFloor()` runs the same test offline.
|
|
449
|
+
|
|
405
450
|
And a promise you can hold it to: **never worse than your JSON.** Payload encodings
|
|
406
451
|
are chosen by counting real tokens with the destination model's own tokenizer, and
|
|
407
452
|
BV‑SALA only moves away from plain JSON when the saving clearly clears the bar —
|
|
@@ -465,8 +510,8 @@ changes the answer, and what they are worth is reported next to the four ledgers
|
|
|
465
510
|
never folded into `cost.*`, never part of any commission.
|
|
466
511
|
|
|
467
512
|
**`savings.deferrable`** asks for the provider's Flex tier: OpenAI, and Gemini through
|
|
468
|
-
its OpenAI‑compatible endpoint. Anthropic has no cheaper synchronous tier
|
|
469
|
-
discount lives only in Message Batches, which
|
|
513
|
+
its OpenAI‑compatible endpoint. Anthropic has no cheaper synchronous tier: its
|
|
514
|
+
discount lives only in Message Batches, which `layer.batches` (below) sends to.
|
|
470
515
|
Bedrock and DeepSeek off‑peak pricing are not supported.
|
|
471
516
|
|
|
472
517
|
- **Latency.** OpenAI publishes no latency commitment for Flex; Google publishes a
|
|
@@ -514,6 +559,124 @@ report.deferred?.tierSaving; // { amount, basis, standardCost, appliedCost, pric
|
|
|
514
559
|
report.cacheWrites; // { mode, breakpoints, avoidedWritePremium?, reason }
|
|
515
560
|
```
|
|
516
561
|
|
|
562
|
+
**`layer.batches`** sends requests that can wait hours to the provider's own batch API,
|
|
563
|
+
billed at its published batch rate card: Anthropic's Message Batches through
|
|
564
|
+
`AnthropicProvider`, and OpenAI's Batch API through `OpenAICompatibleProvider` on
|
|
565
|
+
`api.openai.com`. Gemini's batch mode is not supported yet.
|
|
566
|
+
|
|
567
|
+
- **What runs.** Each request is translated by the same provider adapter as
|
|
568
|
+
`generate()` and sent as you wrote it: the layer's own per‑request savings (caching,
|
|
569
|
+
compression, model cascade, retrieval) do not run on batches. The layer splits the
|
|
570
|
+
job at the provider's limits (and, on OpenAI, one model per file).
|
|
571
|
+
- **Latency.** The provider's: results arrive within its batch window, not on a
|
|
572
|
+
request/response path. `submit()` returns a plain‑JSON job you can store and read
|
|
573
|
+
from another process.
|
|
574
|
+
- **What it is worth.** Each result's usage counter at the standard and at the batch
|
|
575
|
+
published rate cards, component by component, with the same `measured` /
|
|
576
|
+
`estimated` rule as above. The tier is read from the provider: Anthropic reports it
|
|
577
|
+
on every result; on OpenAI it is the batch the result came from. Nothing is
|
|
578
|
+
metered and the batch saving is never billed.
|
|
579
|
+
|
|
580
|
+
```ts
|
|
581
|
+
const tickets = [{ id: "t-1", text: "My invoice is wrong." }]; // your deferrable work
|
|
582
|
+
|
|
583
|
+
const job = await layer.batches.submit(
|
|
584
|
+
tickets.map((t) => ({
|
|
585
|
+
customId: t.id,
|
|
586
|
+
request: { model: "openai/gpt-4o-mini", messages: [{ role: "user", content: t.text }] },
|
|
587
|
+
})),
|
|
588
|
+
);
|
|
589
|
+
// …later, even from another process (store `job` as JSON):
|
|
590
|
+
const results = await layer.batches.results(job);
|
|
591
|
+
results.ended; // false while the provider is still working
|
|
592
|
+
results.items; // one per request: { customId, outcome, text, usage, tierSaving?, reason }
|
|
593
|
+
results.summary; // { succeeded, errored, expired, credited, tierSaving, basis, pricesCheckedOn, reason, … }
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
</details>
|
|
597
|
+
|
|
598
|
+
<details>
|
|
599
|
+
<summary><b>Tool outputs, compacted where they are born (opt-in, off by default)</b></summary>
|
|
600
|
+
|
|
601
|
+
In an agent loop your history is re-sent on every turn and your provider reads it
|
|
602
|
+
from its prompt cache at a fraction of the input price. Rewriting anything it has
|
|
603
|
+
already cached turns everything after it back into full-price cache writes, which is
|
|
604
|
+
how "fewer tokens" ends up as a bigger bill. So this lever only ever changes a tool
|
|
605
|
+
output on the turn it arrives, and re-sends exactly the same bytes afterwards.
|
|
606
|
+
|
|
607
|
+
It removes terminal colour codes, superseded progress-bar states, whitespace between
|
|
608
|
+
JSON tokens and runs of identical lines (folded into one line plus a count), and
|
|
609
|
+
nothing else: no summaries, no dropped words. Each change either reverses exactly or
|
|
610
|
+
leaves what a terminal would display. Outputs of calls that name a file are left
|
|
611
|
+
verbatim, so an agent that edits by exact match still sees the file as it is.
|
|
612
|
+
|
|
613
|
+
```ts
|
|
614
|
+
const agentLayer = createSavingsLayer({
|
|
615
|
+
provider,
|
|
616
|
+
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
617
|
+
toolOutputCompaction: true, // or { exceptTools: ["read_logs"], minSavedChars: 512 }
|
|
618
|
+
});
|
|
619
|
+
// or per request: savings: { toolOutputCompaction: true }
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
- **Switch it on for whole conversations.** The decision depends only on each
|
|
623
|
+
output's own text, so every process and every turn produces the same bytes. A
|
|
624
|
+
conversation that started without it is rewritten once, on its first compacted
|
|
625
|
+
request.
|
|
626
|
+
- **Counted, not billed.** What it removes shows in `report.tokens.compressedInput`
|
|
627
|
+
and in `report.traces`; it never enters `cost.*` or any commission. Your provider's
|
|
628
|
+
invoice shows the effect.
|
|
629
|
+
- **Inside a framework,** call `compactToolOutput(text)` in your tool before you
|
|
630
|
+
return its result: your history then holds the compacted text itself.
|
|
631
|
+
- **Measure it by conversation, not by request.** A verification copy re-sends one
|
|
632
|
+
request with the original history, which your provider has not cached, so on
|
|
633
|
+
turns that re-send compacted outputs the copy costs more than going without the
|
|
634
|
+
layer would have. Compare whole conversations with and without it.
|
|
635
|
+
|
|
636
|
+
</details>
|
|
637
|
+
|
|
638
|
+
<details>
|
|
639
|
+
<summary><b>Prompt-cache breakpoints placed from your traffic (opt-in)</b></summary>
|
|
640
|
+
|
|
641
|
+
By default the layer writes the provider's prompt cache at two fixed places: the end
|
|
642
|
+
of the system header and the end of the history before your last user message, and
|
|
643
|
+
stops marking a prefix whose measured hit rate does not pay the write premium.
|
|
644
|
+
|
|
645
|
+
`cacheBreakpoints: "auto"` places them from what your traffic actually does. The layer
|
|
646
|
+
compares each request's prefix with the ones it sent before, per model, tool set and
|
|
647
|
+
system header, and learns how often each part is sent again and after how long. That
|
|
648
|
+
costs nothing: it compares locally and marks nothing to find out. Once the evidence
|
|
649
|
+
is conclusive:
|
|
650
|
+
|
|
651
|
+
- **Tail.** When requests continue each other (agent tool loops, conversations), it
|
|
652
|
+
also marks the last block, so the next turn reads the whole prompt from cache.
|
|
653
|
+
Tool results arrive after your last user message, so the fixed placement never
|
|
654
|
+
caches them.
|
|
655
|
+
- **History.** When the history before the question is not sent again (one-off
|
|
656
|
+
requests with their own document), it moves the breakpoint back to the system
|
|
657
|
+
header, so no write premium is paid on a prefix nobody reads.
|
|
658
|
+
- **One-hour lifetime** (Anthropic). When turns come back after more than five
|
|
659
|
+
minutes but within the hour, and the size of the request makes the 2x write pay.
|
|
660
|
+
- **Explicit mode** (GPT‑5.6 and later, on OpenAI's own API). When continuations are
|
|
661
|
+
rare, your question is not written to the cache. `savings.standalone` still wins
|
|
662
|
+
when you set it, and `report.cacheWrites` says when the layer chose it.
|
|
663
|
+
|
|
664
|
+
Until then it behaves exactly like the default. Every decision and its reason is in
|
|
665
|
+
`report.traces` (`prefix-cache-placement`), and the effect is read from the provider's
|
|
666
|
+
own counters: if they do not confirm the cache reads the layer predicted, that
|
|
667
|
+
traffic goes back to the default placement. The write premium it causes, one-hour
|
|
668
|
+
writes at 2x included, is charged to `cost.actual`; the cache discount it earns is
|
|
669
|
+
counted in ledger 4 like any provider cache read, never credited as the layer's
|
|
670
|
+
saving and never part of any commission.
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
const placedLayer = createSavingsLayer({
|
|
674
|
+
provider, // Anthropic, or OpenAI GPT-5.6+ on OpenAI's own API
|
|
675
|
+
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
676
|
+
cacheBreakpoints: "auto",
|
|
677
|
+
});
|
|
678
|
+
```
|
|
679
|
+
|
|
517
680
|
</details>
|
|
518
681
|
|
|
519
682
|
<details>
|
|
@@ -587,6 +750,47 @@ if (res.toolCalls?.length) {
|
|
|
587
750
|
A tool‑call turn is never served from cache and never counted as an avoided call:
|
|
588
751
|
an action that has not run yet cannot be a saving.
|
|
589
752
|
|
|
753
|
+
**Large catalogues: let the provider search them (opt‑in).** With hundreds of tools
|
|
754
|
+
(several MCP servers, say), `savings: { toolSearch: "auto" }` sends every tool deferred
|
|
755
|
+
behind the provider's own tool search instead of letting the layer's selector keep a
|
|
756
|
+
few. A catalogue that declares no `intents` or `risk` gives the selector nothing to go
|
|
757
|
+
on, so it keeps the first ones and the model cannot call the rest; with the provider's
|
|
758
|
+
search the model looks up what it needs, no tool you declared is out of reach, and the
|
|
759
|
+
cached prompt prefix stays the same from one question to the next.
|
|
760
|
+
|
|
761
|
+
- **Where.** The Anthropic adapter, on models in Anthropic's tool search compatibility
|
|
762
|
+
table. OpenAI documents its tool search for the Responses and Agents APIs, not for
|
|
763
|
+
Chat Completions, which is what this SDK's OpenAI adapter speaks, so OpenAI requests
|
|
764
|
+
keep the selector.
|
|
765
|
+
- **When.** `"auto"` applies it when the adapter talks to Anthropic's own API, the
|
|
766
|
+
catalogue is over the provider's documented size, and deferring it pays for one
|
|
767
|
+
search on every request even with a warm prompt cache. `"always"` skips those checks
|
|
768
|
+
(for example behind a proxy that forwards the search blocks). Requests with an output
|
|
769
|
+
contract, or with the model cascade on, keep the selector.
|
|
770
|
+
- **Whose saving.** The provider's. Your usage counter shows it; the layer credits
|
|
771
|
+
nothing for it and charges no commission on it.
|
|
772
|
+
|
|
773
|
+
```ts
|
|
774
|
+
import { AnthropicProvider } from "@bivelio/savings-layer";
|
|
775
|
+
|
|
776
|
+
const claude = createSavingsLayer({
|
|
777
|
+
provider: new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
778
|
+
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
const mcpTools: ToolDescriptor[] = []; // every tool your MCP servers list
|
|
782
|
+
|
|
783
|
+
const { report: searched } = await claude.generate({
|
|
784
|
+
model: "anthropic/claude-opus-5-5",
|
|
785
|
+
messages: [{ role: "user", content: "Which orders from service 17 are still open?" }],
|
|
786
|
+
tools: mcpTools,
|
|
787
|
+
savings: { toolSearch: { mode: "auto", alwaysLoaded: ["read_file"] } },
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
// Which path ran and why, and how many searches the provider ran.
|
|
791
|
+
console.log(searched.traces.filter((t) => t.stage === "tool-search"));
|
|
792
|
+
```
|
|
793
|
+
|
|
590
794
|
</details>
|
|
591
795
|
|
|
592
796
|
<details>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {createSavingsLayer,LicenseError,sha256,stableStringify}from'@bivelio/savings-layer';import {AsyncLocalStorage}from'async_hooks';var w=t=>typeof t=="number"&&Number.isFinite(t)&&t>0?t:0;function N(t){if(!Array.isArray(t))return null;let e={input:0,output:0,cacheRead:0,cacheWrite:0,cacheWrite1h:0,iteraciones:0};for(let n of t){if(n===
|
|
2
2
|
null||typeof n!="object")continue;let r=n;if(r.type!=="compaction")continue;e.iteraciones++,e.input+=w(r.input_tokens),e.output+=w(r.output_tokens),e.cacheRead+=w(r.cache_read_input_tokens);let o=r.cache_creation,
|
|
3
3
|
s=w(o?.ephemeral_5m_input_tokens),a=w(o?.ephemeral_1h_input_tokens),i=typeof r.cache_creation_input_tokens=="number"?w(r.cache_creation_input_tokens):s+a;e.cacheWrite+=i,e.cacheWrite1h+=Math.min(a,i);}
|
|
4
|
-
return e.iteraciones>0?e:null}function W(t,e,n){let r=t??{},o=N(r.iterations),s=J(r.cache_read_input_tokens,o?.cacheRead),a=J(r.cache_creation_input_tokens,o?.cacheWrite),i=
|
|
5
|
-
(o?.
|
|
6
|
-
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
return e.iteraciones>0?e:null}function W(t,e,n){let r=t??{},o=N(r.iterations),s=J(r.cache_read_input_tokens,o?.cacheRead),a=J(r.cache_creation_input_tokens,o?.cacheWrite),i=r.cache_creation?.ephemeral_1h_input_tokens,u=typeof i=="\
|
|
5
|
+
number"||(o?.cacheWrite1h??0)>0?Math.min(Math.max(0,(typeof i=="number"?i:0)+(o?.cacheWrite1h??0)),a??0):void 0,d=(r.input_tokens??0)+(o?.input??0)+(a??0)+(s??0),g=(e??0)+(o?.output??0),m=typeof r.input_tokens!=
|
|
6
|
+
"number",h=typeof e!="number";return {usage:{inputTokens:d,outputTokens:g,...s!=null?{cachedInputTokens:s}:{},...a!=null?{cacheWriteInputTokens:a}:{},...u!==void 0?{cacheWrite1hInputTokens:u}:{},...n||
|
|
7
|
+
h||o?{}:{reasoningTokens:0},...m||h?{complete:false}:{}},entradaAusente:m,salidaAusente:h}}function J(t,e){return e?(t??0)+e:t}function B(t){let e=t?.service_tier;return typeof e=="string"&&e!==""?e:void 0}
|
|
8
|
+
function q(t,e){let n=Object.fromEntries(Object.entries(e??{}).filter(([,o])=>o!=null)),r=t?.cache_creation||e?.cache_creation?{...t?.cache_creation??{},...e?.cache_creation??{}}:void 0;return {...t??{},
|
|
9
|
+
...n,...r?{cache_creation:r}:{}}}function F(t){return t==="thinking"||t==="redacted_thinking"}function V(t){switch(t){case "end_turn":return "stop";case "max_tokens":return "length";case "tool_use":return "\
|
|
10
|
+
tool_calls";case "refusal":return "content_filter";default:return t}}var M=class{decoder;resto="";crPendiente=false;alPrincipio=true;datos="";evento="";ultimoId;push(e){let n=typeof e=="string"?e:(this.decoder??=new TextDecoder("utf-8")).decode(e,{stream:true});return this.procesar(
|
|
10
11
|
n)}end(){let e=[];if(this.decoder){let n=this.decoder.decode();n!==""&&e.push(...this.procesar(n));}return this.resto!==""&&(this.linea(this.resto,e),this.resto=""),this.despachar(e),this.crPendiente=false,
|
|
11
12
|
e}procesar(e){let n=[];if(e==="")return n;let r=e;if(this.alPrincipio&&(this.alPrincipio=false,r.charCodeAt(0)===65279&&(r=r.slice(1)),r===""))return n;let o=0;this.crPendiente&&(this.crPendiente=false,r.charCodeAt(
|
|
12
13
|
0)===10&&(o=1));let s=o;for(;o<r.length;){let a=r.charCodeAt(o);if(a!==10&&a!==13){o++;continue}this.linea(this.resto+r.slice(s,o),n),this.resto="",a===13&&(o+1<r.length?r.charCodeAt(o+1)===10&&o++:this.
|
|
@@ -23,8 +24,8 @@ ultCostModel","exactCache","exactCacheTtlMs","resolvers","exactTokenizer","silen
|
|
|
23
24
|
t.completada}var ee="[bvsala] the model answered, but the savings layer failed while accounting for it; the answer is returned unchanged, without a savings report and without a usage record.";function te(t,e){
|
|
24
25
|
let n=X(),r={};for(let a of Se)e[a]!==void 0&&(r[a]=e[a]);let o=createSavingsLayer({...r,provider:Q(t,n)}),s=(a,i)=>H(e.silenceWarnings,`${t}:${a}`,i);return {ejecutar(a,i){return n.run(i,()=>o.generate(a))},async generar(a,i){
|
|
25
26
|
try{let u=await n.run(i,()=>o.generate(a));return i.invocada?{tipo:"vivo",result:u}:{tipo:"atajo",result:u}}catch(u){if(Y(i,u))return s("sin-informe",ee),{tipo:"sin-informe"};throw u}},async transmitir(a,i,u){
|
|
26
|
-
let
|
|
27
|
-
"sin-informe",ee),{tipo:"sin-informe"};throw
|
|
27
|
+
let g=n.run(i,()=>o.generateStream(a,u?{signal:u}:{})).result;i.resultado=g,g.catch(()=>{});let m;try{m=await Promise.race([i.abierta.then(()=>"abierta"),g.then(()=>"resuelta")]);}catch(b){if(Y(i,b))return s(
|
|
28
|
+
"sin-informe",ee),{tipo:"sin-informe"};throw b}if(m==="abierta")return {tipo:"abierto"};let h=await g;return i.invocada?{tipo:"entero",result:h}:{tipo:"atajo",result:h}},informe(a){let i=JSON.parse(JSON.
|
|
28
29
|
stringify(a));if(e.onReport)try{e.onReport(i);}catch{s("onreport","[bvsala] `onReport` threw; the error was caught and the model call was not affected.");}return i},avisar:s,close(){o.close();}}}function ne(){
|
|
29
30
|
let t;return {abierta:new Promise(n=>{t=n;}),abrir:t}}var C=class{pendientes=[];esperando;terminada=false;fallo;push(e){if(this.terminada||this.fallo)return;let n=this.esperando;if(n){this.esperando=void 0,n.resolver({value:e,done:false});return}this.pendientes.
|
|
30
31
|
push(e);}end(){if(this.terminada||this.fallo)return;this.terminada=true;let e=this.esperando;e&&this.pendientes.length===0&&(this.esperando=void 0,e.resolver({value:void 0,done:true}));}fail(e){if(this.terminada||
|
|
@@ -81,19 +82,19 @@ stringify(i.input)}})),a+=s("content_block_stop",{type:"content_block_stop",inde
|
|
|
81
82
|
a+=s("message_stop",{type:"message_stop"}),new Response(a,{status:200,headers:{...o,"content-type":"text/event-stream"}})}var E=class extends Error{constructor(n){super(`[bvsala] provider responded ${n.
|
|
82
83
|
status}`);this.respuesta=n;}respuesta};function et(t){let e=te("anthropic-sdk",t),n=Math.max(1,Math.floor(t.reportCacheSize??1e3)),r=new Map,o=(a,i)=>{if(a)for(r.delete(a),r.set(a,i);r.size>n;)r.delete(
|
|
83
84
|
r.keys().next().value);};return {middleware:async(a,i,u)=>{if(!De(a))return i(a);if(typeof a.body!="string")throw new Error(I);let d;try{let c=JSON.parse(a.body);if(!p(c))throw new Error(I);d=c;}catch{throw new Error(
|
|
84
|
-
I)}let
|
|
85
|
+
I)}let g=u.options,m=ie(g?.bivelio),h=t.context?.({body:d,requestOptions:g}),b=(Array.isArray(d.tools)?d.tools:[]).filter(p).map(c=>oe(t,{nombre:String(c.name??""),descripcion:typeof c.description=="s\
|
|
85
86
|
tring"?c.description:void 0,parametros:c.input_schema,delProveedor:c.input_schema===void 0&&typeof c.type=="string"&&c.type!=="custom"})),l=await se(t,{model:`anthropic/${String(d.model??"")}`,messages:Ce(
|
|
86
|
-
d),tools:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
R;try{R=await e.generar(l,
|
|
90
|
-
false)):(o(
|
|
91
|
-
bvsala] the model stream ended before completion; nothing was measured.")},()=>{let c=new C;return
|
|
87
|
+
d),tools:b,temperature:typeof d.temperature=="number"?d.temperature:void 0,context:ae(h,m.context),savings:m.savings,proyeccion:Pe(d,a.headers.get("anthropic-beta")),restricciones:{sinResolvers:Me(d)}}),
|
|
88
|
+
_={model:l.model,messages:l.messages,...l.tools?{tools:l.tools}:{}},v=async c=>{let f;try{f=await i(a);}catch(y){throw c.errorDelMarco={error:y},y}if(!f.ok){let y=new E(f);throw c.errorDelMarco={error:y},
|
|
89
|
+
y}return c.completada=true,f},A=c=>{if(c instanceof E)return c.respuesta;throw c};if(d.stream!==true){let c,f,y=U(_,async()=>{c=await v(y);let D=new T;return D.json(await c.clone().json()),f=D.id,D.respuesta()}),
|
|
90
|
+
R;try{R=await e.generar(l,y);}catch(D){return A(D)}if(R.tipo==="sin-informe")return c;let $=e.informe(R.result.report);return R.tipo==="atajo"?(o(`msg_bvsala_${R.result.requestId}`,$),ue(R.result,d.model,
|
|
91
|
+
false)):(o(f,$),c)}let{abierta:pe,abrir:z}=ne(),j,O,k=Object.assign(U(_,async()=>{let c=await v(k),f=new C;O=le(c,f,e,k,o),z();for await(let y of f)if(y.type==="done")return y.response;throw new Error("[\
|
|
92
|
+
bvsala] the model stream ended before completion; nothing was measured.")},()=>{let c=new C;return v(k).then(f=>{j=le(f,c,e,k,o),z();},f=>c.fail(f)),c}),{abierta:pe}),S;try{S=await e.transmitir(l,k,a.signal??
|
|
92
93
|
void 0);}catch(c){return A(c)}if(S.tipo==="abierto")return j??O;if(S.tipo==="atajo"){let c=e.informe(S.result.report);return o(`msg_bvsala_${S.result.requestId}`,c),ue(S.result,d.model,true)}return j??O},
|
|
93
94
|
reportFor(a){let i=typeof a=="string"?a:a?.id;return typeof i=="string"?r.get(i):void 0},close(){e.close();}}}function le(t,e,n,r,o){let s=new T,a,i=async()=>{try{let l=await r.resultado;o(s.id,n.informe(
|
|
94
95
|
l.report));}catch{}},u=()=>{e.cerrada||(s.error?e.fail(new Error(`[bvsala] provider stream error: ${s.error}`)):s.vioDelta||s.vioStop?(e.push({type:"done",response:s.respuesta()}),e.end(),a=i()):e.fail(
|
|
95
96
|
new Error("[bvsala] the model stream ended before completion; nothing was measured.")));};if((t.headers.get("content-type")??"").includes("application/json"))return t.clone().json().then(l=>{s.json(p(l)?
|
|
96
|
-
l:{}),s.texto!==""&&e.push({type:"text",delta:s.texto}),u();},l=>e.fail(l)),t;let d=t.body;if(!d)return e.fail(new Error("[bvsala] the model stream had no body; nothing was measured.")),t;let
|
|
97
|
-
|
|
98
|
-
l.error(A);return}if(
|
|
99
|
-
g was measured.")),
|
|
97
|
+
l:{}),s.texto!==""&&e.push({type:"text",delta:s.texto}),u();},l=>e.fail(l)),t;let d=t.body;if(!d)return e.fail(new Error("[bvsala] the model stream had no body; nothing was measured.")),t;let g=d.getReader(),
|
|
98
|
+
m=new M,h=l=>{for(let _ of l){let v=s.evento(_);v!==void 0&&e.push({type:"text",delta:v}),(s.vioStop||s.error)&&u();}},b=new ReadableStream({async pull(l){let _;try{_=await g.read();}catch(A){e.fail(A),
|
|
99
|
+
l.error(A);return}if(_.done){h(m.end()),u(),await a,l.close();return}let v=_.value;l.enqueue(v),h(m.push(v));},cancel(l){return e.fail(new Error("[bvsala] the stream was cancelled by its reader; nothin\
|
|
100
|
+
g was measured.")),g.cancel(l)}});return new Response(b,{status:t.status,statusText:t.statusText,headers:t.headers})}export{et as anthropicSavings};
|
|
@@ -1,57 +1,58 @@
|
|
|
1
|
-
import {createMiddleware}from'langchain';import {AIMessage}from'@langchain/core/messages';import {toJsonSchema}from'@langchain/core/utils/json_schema';import {createSavingsLayer,LicenseError,sha256,stableStringify}from'@bivelio/savings-layer';import {AsyncLocalStorage}from'async_hooks';var D=new Set;function _(e,
|
|
1
|
+
import {createMiddleware}from'langchain';import {AIMessage}from'@langchain/core/messages';import {toJsonSchema}from'@langchain/core/utils/json_schema';import {createSavingsLayer,LicenseError,sha256,stableStringify}from'@bivelio/savings-layer';import {AsyncLocalStorage}from'async_hooks';var D=new Set;function _(n,e,t){n===true||D.has(e)||(D.add(e),console.warn(t));}var K="[bvsala] internal: the layer changed a request this adapter must forward unchanged; noth\
|
|
2
2
|
ing was sent.",H="[bvsala] internal: the layer asked for a second model call within one framework call; nothing more was sent.",G="[bvsala] internal: the layer called the model outside a framework cal\
|
|
3
|
-
l; nothing was sent.";function
|
|
4
|
-
errorDelMarco={error:t},t}}function X(e
|
|
5
|
-
return
|
|
6
|
-
K);return o.invocada=true,o};return {id:`bivelio-adapter:${
|
|
3
|
+
l; nothing was sent.";function P(n,e,t){return {renderizado:n,generar:e,...{},invocada:false,completada:false}}async function L(n,e){try{let t=await e();return n.completada=!0,t}catch(t){throw n.
|
|
4
|
+
errorDelMarco={error:t},t}}function X(n,e){if(n===e)return true;try{return stableStringify(n)===stableStringify(e)}catch{return false}}function C(n,e){let t=n??[],r=e??[];return t.length===r.length&&t.every((o,s)=>X(o,r[s]))}function Z(n,e){
|
|
5
|
+
return n.model===e.model&&C(n.messages,e.messages)&&C(n.tools,e.tools)}function T(n,e){let t=r=>{let o=e.getStore();if(!o)throw new Error(G);if(o.invocada)throw new Error(H);if(!Z(r,o.renderizado))throw new Error(
|
|
6
|
+
K);return o.invocada=true,o};return {id:`bivelio-adapter:${n}`,async generate(r,o){return t(r).generar(o?.signal)},async*generateStream(r,o){let s=t(r);if(s.transmitir){yield*s.transmitir(o?.signal);return}
|
|
7
7
|
let a=await s.generar(o?.signal);a.text!==""&&(yield {type:"text",delta:a.text}),yield {type:"done",response:a};}}}function O(){return new AsyncLocalStorage}var ee=["licenseKey","licenseServerUrl","serviceAccountKey","licenseRefresh","licenseRevocation","licensePollIntervalSeconds","licenseFetch","metering","meteringUrl","meteringFetch","costModels","defa\
|
|
8
|
-
ultCostModel","exactCache","exactCacheTtlMs","resolvers","exactTokenizer","silenceWarnings","failOpen"];function
|
|
9
|
-
|
|
10
|
-
let t=O(),r={};for(let a of ee)
|
|
11
|
-
try{let l=await t.run(i,()=>o.generate(a));return i.invocada?{tipo:"vivo",result:l}:{tipo:"atajo",result:l}}catch(l){if(
|
|
12
|
-
let u=t.run(i,()=>o.generateStream(a,l?{signal:l}:{})).result;i.resultado=u,u.catch(()=>{});let f;try{f=await Promise.race([i.abierta.then(()=>"abierta"),u.then(()=>"resuelta")]);}catch(h){if(
|
|
13
|
-
"sin-informe",
|
|
14
|
-
stringify(a));if(
|
|
15
|
-
function re(e,
|
|
16
|
-
erministicResolvers")&&t.sinResolvers!==true}}function oe(e
|
|
17
|
-
let t=
|
|
18
|
-
...typeof
|
|
19
|
-
|
|
20
|
-
return
|
|
21
|
-
if(r instanceof Date)return {$date:Number.isNaN(r.getTime())?"invalid":r.toISOString()};if(t.has(r))return "$cycle";t.add(r);try{if(Array.isArray(r))return r.map(s=>{let a=y(s,
|
|
22
|
-
a});if(r instanceof Map)return {$map:[...r.entries()].map(s=>y(s,
|
|
23
|
-
a!==void 0&&(o[s]=a);}return o}finally{t.delete(r);}}async function E(
|
|
24
|
-
2,"0")).join("")}let t="";for(let r=0;r<
|
|
25
|
-
all(
|
|
26
|
-
vsala-adapter:${t??"none"}`;return {model:
|
|
27
|
-
{seed:
|
|
28
|
-
return typeof
|
|
29
|
-
`):""}function U(
|
|
30
|
-
"string"?{id:t.id}:{},name:String(t.name??""),arguments:U(t.args)}))}function le(e
|
|
8
|
+
ultCostModel","exactCache","exactCacheTtlMs","resolvers","exactTokenizer","silenceWarnings","failOpen"];function j(n,e){return e instanceof LicenseError||n.errorDelMarco&&n.errorDelMarco.error===e?false:n.invocada&&
|
|
9
|
+
n.completada}var I="[bvsala] the model answered, but the savings layer failed while accounting for it; the answer is returned unchanged, without a savings report and without a usage record.";function q(n,e){
|
|
10
|
+
let t=O(),r={};for(let a of ee)e[a]!==void 0&&(r[a]=e[a]);let o=createSavingsLayer({...r,provider:T(n,t)}),s=(a,i)=>_(e.silenceWarnings,`${n}:${a}`,i);return {ejecutar(a,i){return t.run(i,()=>o.generate(a))},async generar(a,i){
|
|
11
|
+
try{let l=await t.run(i,()=>o.generate(a));return i.invocada?{tipo:"vivo",result:l}:{tipo:"atajo",result:l}}catch(l){if(j(i,l))return s("sin-informe",I),{tipo:"sin-informe"};throw l}},async transmitir(a,i,l){
|
|
12
|
+
let u=t.run(i,()=>o.generateStream(a,l?{signal:l}:{})).result;i.resultado=u,u.catch(()=>{});let f;try{f=await Promise.race([i.abierta.then(()=>"abierta"),u.then(()=>"resuelta")]);}catch(h){if(j(i,h))return s(
|
|
13
|
+
"sin-informe",I),{tipo:"sin-informe"};throw h}if(f==="abierta")return {tipo:"abierto"};let g=await u;return i.invocada?{tipo:"entero",result:g}:{tipo:"atajo",result:g}},informe(a){let i=JSON.parse(JSON.
|
|
14
|
+
stringify(a));if(e.onReport)try{e.onReport(i);}catch{s("onreport","[bvsala] `onReport` threw; the error was caught and the model call was not affected.");}return i},avisar:s,close(){o.close();}}}var te={router:false,compression:false,elision:false,serialization:false,toolDiscovery:false,modelCascade:"off",outputCodebook:false,deferrable:false,semanticCache:false,verification:false};
|
|
15
|
+
function re(n,e,t={}){let r=o=>n?.[o]!==false&&e?.[o]!==false;return {...te,exactCache:r("exactCache")&&t.sinReutilizar!==true,singleflight:r("singleflight")&&t.sinReutilizar!==true,deterministicResolvers:r("det\
|
|
16
|
+
erministicResolvers")&&t.sinResolvers!==true}}function oe(n,e){let t=n.readOnlyTools;if(typeof t=="function")try{return t(e)===!0}catch{return false}return Array.isArray(t)?t.includes(e):false}function R(n,e){
|
|
17
|
+
let t=e.delProveedor!==true&&oe(n,e.nombre);return {id:e.nombre,intents:[],risk:t?"read":"write",...e.parametros!==void 0&&e.parametros!==null&&typeof e.parametros=="object"?{parameters:e.parametros}:{},
|
|
18
|
+
...typeof e.descripcion=="string"?{description:e.descripcion}:{}}}function y(n,e,t){if(n===null)return null;switch(typeof n){case "string":case "boolean":return n;case "number":return Number.isFinite(n)?
|
|
19
|
+
n:String(n);case "bigint":return `${n.toString()}n`;case "undefined":case "function":case "symbol":return}let r=n;if(r instanceof Uint8Array)return e.push(r),{$bytes:e.length-1};if(r instanceof ArrayBuffer)
|
|
20
|
+
return e.push(new Uint8Array(r)),{$bytes:e.length-1};if(ArrayBuffer.isView(r))return e.push(new Uint8Array(r.buffer,r.byteOffset,r.byteLength)),{$bytes:e.length-1};if(r instanceof URL)return {$url:r.href};
|
|
21
|
+
if(r instanceof Date)return {$date:Number.isNaN(r.getTime())?"invalid":r.toISOString()};if(t.has(r))return "$cycle";t.add(r);try{if(Array.isArray(r))return r.map(s=>{let a=y(s,e,t);return a===void 0?null:
|
|
22
|
+
a});if(r instanceof Map)return {$map:[...r.entries()].map(s=>y(s,e,t))};if(r instanceof Set)return {$set:[...r.values()].map(s=>y(s,e,t))};let o={};for(let s of Object.keys(r).sort()){let a=y(r[s],e,t);
|
|
23
|
+
a!==void 0&&(o[s]=a);}return o}finally{t.delete(r);}}async function E(n){let e=globalThis.crypto?.subtle;if(e){let r=new Uint8Array(await e.digest("SHA-256",n));return Array.from(r,o=>o.toString(16).padStart(
|
|
24
|
+
2,"0")).join("")}let t="";for(let r=0;r<n.length;r+=32768)t+=String.fromCharCode(...n.subarray(r,r+32768));return sha256(t)}async function ae(n){try{let e=[],t=JSON.stringify(y(n,e,new WeakSet))??"null",r=await Promise.
|
|
25
|
+
all(e.map(E)),o=new TextEncoder().encode(`${t}|${r.join(",")}`);return await E(o)}catch{return null}}async function $(n,e){let t=await ae(e.proyeccion),r=`${e.context?.authorizationScope??"default"}|b\
|
|
26
|
+
vsala-adapter:${t??"none"}`;return {model:e.model,messages:e.messages,...e.tools.length>0?{tools:e.tools}:{},...typeof e.temperature=="number"?{temperature:e.temperature}:{},...typeof e.seed=="number"?
|
|
27
|
+
{seed:e.seed}:{},context:{...e.context??{},authorizationScope:r},savings:re(n.savings,e.savings,{...e.restricciones,...t===null?{sinReutilizar:true}:{}})}}function c(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)}function N(n){let e=n;try{if(typeof e.getType=="function")return e.getType();if(typeof e._getType=="function")return e._getType()}catch{}
|
|
28
|
+
return typeof e.type=="string"?e.type:""}function v(n){return typeof n=="string"?n:Array.isArray(n)?n.filter(e=>c(e)&&e.type==="text"&&typeof e.text=="string").map(e=>e.text).join(`
|
|
29
|
+
`):""}function U(n){if(typeof n=="string")return n;try{return JSON.stringify(n??{})}catch{return "{}"}}function b(n){return (Array.isArray(n.tool_calls)?n.tool_calls:[]).filter(c).map(t=>({...typeof t.id==
|
|
30
|
+
"string"?{id:t.id}:{},name:String(t.name??""),arguments:U(t.args)}))}function le(n,e){let t=[],r=v(n?.content);r!==""&&t.push({role:"system",content:r});for(let o of e){if(!c(o))continue;let s=N(o);if(s===
|
|
31
31
|
"system")t.push({role:"system",content:v(o.content)});else if(s==="human")t.push({role:"user",content:v(o.content)});else if(s==="ai"){let a=b(o);t.push({role:"assistant",content:v(o.content),...a.length>
|
|
32
32
|
0?{toolCalls:a}:{}});}else s==="tool"&&t.push({role:"tool",content:typeof o.content=="string"?o.content:U(o.content),...typeof o.tool_call_id=="string"?{toolCallId:o.tool_call_id}:{},...typeof o.name==
|
|
33
|
-
"string"?{name:o.name}:{}});}return t}function
|
|
34
|
-
ction_call"||o.startsWith("__")||(r[o]=s);return {type:t,name:
|
|
35
|
-
|
|
36
|
-
try{a=toJsonSchema(o.schema);}catch{i=false;}let l=R(
|
|
37
|
-
description,parametros:a,returnDirect:o.returnDirect});continue}let s=c(o.function)?o.function:void 0;if(o.type==="function"&&s&&typeof s.name=="string"){t.push(R(
|
|
38
|
-
description=="string"?s.description:void 0,parametros:s.parameters})),r.push(o);continue}t.push(R(
|
|
39
|
-
function F(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
void 0&&o!==void 0
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
33
|
+
"string"?{name:o.name}:{}});}return t}function z(n){if(!c(n))return n;let e=n.content,t=N(n),r={};for(let[o,s]of Object.entries(c(n.additional_kwargs)?n.additional_kwargs:{}))o==="tool_calls"||o==="fun\
|
|
34
|
+
ction_call"||o.startsWith("__")||(r[o]=s);return {type:t,name:n.name,content:t==="tool"?e:typeof e=="string"?"$text":Array.isArray(e)?e.map(o=>c(o)&&o.type==="text"?{...o,text:void 0,id:void 0}:o):e,tool_calls:b(
|
|
35
|
+
n).map(o=>({name:o.name,arguments:o.arguments})),status:n.status,additional_kwargs:r}}function ue(n,e){let t=[],r=[];for(let o of e){if(!c(o))continue;if(typeof o.name=="string"&&"schema"in o){let a,i=true;
|
|
36
|
+
try{a=toJsonSchema(o.schema);}catch{i=false;}let l=R(n,{nombre:o.name,descripcion:typeof o.description=="string"?o.description:void 0,parametros:a});t.push(i?l:{...l,risk:"write"}),r.push({name:o.name,description:o.
|
|
37
|
+
description,parametros:a,returnDirect:o.returnDirect});continue}let s=c(o.function)?o.function:void 0;if(o.type==="function"&&s&&typeof s.name=="string"){t.push(R(n,{nombre:s.name,descripcion:typeof s.
|
|
38
|
+
description=="string"?s.description:void 0,parametros:s.parameters})),r.push(o);continue}t.push(R(n,{nombre:String(o.name??o.type??"server-tool"),delProveedor:true})),r.push(o);}return {tools:t,definiciones:r}}
|
|
39
|
+
function F(n){try{let e=n._llmType?.();return typeof e=="string"?e:""}catch{return ""}}function de(n){if(typeof n=="string")return n.replace(":","/");let e=n,t=e._defaultConfig;if(t&&typeof t=="object"&&
|
|
40
|
+
typeof t.model=="string"&&t.model!=="")return t.model.includes("/")?t.model:typeof t.modelProvider=="string"&&t.modelProvider!==""?`${t.modelProvider}/${t.model}`:t.model;let r=typeof e.model=="string"?
|
|
41
|
+
e.model:typeof e.modelName=="string"?e.modelName:"";if(r.includes("/"))return r;let o=F(n);return o!==""?`${o}/${r}`:r}function fe(n){try{return n.invocationParams?.({})??null}catch{return null}}function pe(n){
|
|
42
|
+
let e=n.toolChoice;return e==="required"||c(e)?true:n.responseFormat!==void 0}function p(n){return typeof n=="number"&&Number.isFinite(n)?n:void 0}function ge(n,e){let t=c(n.usage_metadata)?n.usage_metadata:
|
|
43
|
+
void 0,r=p(t?.input_tokens),o=p(t?.output_tokens),s=c(t?.input_token_details)?t.input_token_details:{},a=c(t?.output_token_details)?t.output_token_details:{},i=p(s.cache_read),l=p(s.cache_creation),d=p(
|
|
44
|
+
a.reasoning),u=r!==void 0&&(i??0)+(l??0)<=r;return {inputTokens:r??0,outputTokens:o??0,...i!==void 0?{cachedInputTokens:i}:{},...l!==void 0?{cacheWriteInputTokens:l}:{},...d!==void 0?{reasoningTokens:d}:
|
|
45
|
+
{},...e&&r!==void 0&&o!==void 0&&u?{}:{complete:false}}}function me(n){let e=c(n.response_metadata)?n.response_metadata:{},t=e.finish_reason??e.stop_reason??e.finishReason;switch(t){case "end_turn":case "s\
|
|
46
|
+
top":case "STOP":return "stop";case "max_tokens":case "length":case "MAX_TOKENS":return "length";case "tool_use":case "tool_calls":return "tool_calls";default:return typeof t=="string"?t:void 0}}function ye(n,e){
|
|
47
|
+
if(e!=="stop")return false;if(Array.isArray(n.content)){for(let r of n.content)if(!c(r)||r.type!=="text"||Array.isArray(r.citations)&&r.citations.length>0||Array.isArray(r.annotations)&&r.annotations.length>
|
|
48
|
+
0)return false}let t=c(n.additional_kwargs)?n.additional_kwargs:{};return !("reasoning"in t||"reasoning_content"in t||"audio"in t)}function ve(n,e){let t=me(n),r=b(n);return {text:v(n.content),...r.length>0?
|
|
49
|
+
{toolCalls:r}:{},...t?{finishReason:t}:{},usage:ge(n,e),...ye(n,t)?{}:{replayable:false}}}function he(n,e){return new AIMessage({content:n.text,tool_calls:(n.toolCalls??[]).map((t,r)=>{let o={};try{let s=JSON.parse(
|
|
50
|
+
t.arguments||"{}");c(s)&&(o=s);}catch{o={};}return {id:t.id??`bvsala-${n.requestId}-${r}`,name:t.name,args:o,type:"tool_call"}}),usage_metadata:{input_tokens:0,output_tokens:0,total_tokens:0},response_metadata:e?
|
|
51
|
+
{bivelio:{savingsReport:e}}:{}})}var we="[bvsala] this LangChain model has its own `cache` set, so a usage counter can be a cached copy rather than a provider bill; its calls are reported as unmeasure\
|
|
52
|
+
d.";function qe(n){let e=q("langchain",n),t=n.reportInMetadata!==false,r=createMiddleware({name:"BivelioSavings",wrapModelCall:async(o,s)=>{let a=o,i=a.model,l=F(i),d=c(i)&&i.cache!==void 0&&i.cache!==null&&i.cache!==
|
|
53
|
+
false;d&&e.avisar("cache-del-modelo",we);let u=!l.toLowerCase().includes("fake")&&!d,f=n.modelId?n.modelId(i):de(i),g=Array.isArray(a.messages)?a.messages:[],{tools:h,definiciones:J}=ue(n,Array.isArray(a.
|
|
54
|
+
tools)?a.tools:[]),B=pe(a),W=p(i?.temperature),m=await $(n,{model:f,messages:le(a.systemMessage,g),tools:h,temperature:W,context:n.context?.(o),proyeccion:{modelo:f,parametros:fe(i),sistema:z(a.systemMessage),
|
|
55
|
+
mensajes:g.map(z),herramientas:J,toolChoice:a.toolChoice,responseFormat:a.responseFormat,modelSettings:a.modelSettings},restricciones:{sinResolvers:B,sinReutilizar:a.responseFormat!==void 0}}),w,k=P({
|
|
56
|
+
model:m.model,messages:m.messages,...m.tools?{tools:m.tools}:{}},async()=>(w=await L(k,async()=>s(o)),ve(w,u))),S=await e.generar(m,k);if(S.tipo==="sin-informe")return w;let x=e.informe(S.result.report);
|
|
57
|
+
if(S.tipo==="atajo")return he(S.result,t?x:void 0);let A=w;return t&&(A.response_metadata={...A.response_metadata??{},bivelio:{savingsReport:x}}),A}});return Object.assign(r,{close:()=>e.close()})}function Ee(n){
|
|
58
|
+
let e=n?.response_metadata,t=c(e)?e.bivelio:void 0,r=c(t)?t.savingsReport:void 0;if(!(!c(r)||typeof r.requestId!="string"))return r}export{Ee as getSavingsReportFromMessage,qe as savingsAgentMiddleware};
|