@bivelio/savings-layer 0.4.19 → 0.4.21

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/index.d.ts CHANGED
@@ -205,7 +205,7 @@ declare function precioDeLista(model: string): CostModel | undefined;
205
205
  * on the customer's machine. `test/version-declarada.test.ts` ties this
206
206
  * constant to package.json so the two can never drift.
207
207
  */
208
- declare const BV_SALA_VERSION = "0.4.19";
208
+ declare const BV_SALA_VERSION = "0.4.21";
209
209
 
210
210
  /**
211
211
  * Environment-agnostic UUID generation.
@@ -990,6 +990,15 @@ interface LicenseGate {
990
990
  * que tener una rama por defecto de todas formas.
991
991
  */
992
992
  status?(): string;
993
+ /**
994
+ * Why the key does not open, when it does not; `undefined` when it does. It
995
+ * only refines the MESSAGE, never the verdict: an `"invalid"` whose reason is
996
+ * `"malformed"` (not even the shape of a token: the `bvk_` pasted where the
997
+ * license goes) gets different advice from an `"invalid"` that has the shape
998
+ * but no valid signature. Optional because a gate is implemented by the
999
+ * caller; without it, the generic message for that status.
1000
+ */
1001
+ reason?(): string | undefined;
993
1002
  }
994
1003
  interface TraceEvent {
995
1004
  stage: string;
@@ -1686,10 +1695,46 @@ interface ToolSelectorOptions {
1686
1695
  maxTools?: number;
1687
1696
  /** Tokenizer-aware scorer for tie-breaking (optional). */
1688
1697
  scoreIntentMatch?: (text: string, tool: ToolDescriptor) => number;
1698
+ /**
1699
+ * How many requests are selected by relevance before the set is FROZEN for a
1700
+ * given key. Below this count the selector learns; past it, it always emits
1701
+ * the same set and `setIsStable` is true again.
1702
+ *
1703
+ * The default of 16 is measured, not round: across 214 sessions of real
1704
+ * traffic, a set frozen from the first 16 invoked tools came up short in
1705
+ * 0.0% of sessions (8 fell short in 2.3%, 12 in 0.5%).
1706
+ */
1707
+ learningTurns?: number;
1708
+ /** Where frozen sets are remembered. Defaults to an in-process map. */
1709
+ freezer?: ToolSetFreezer;
1710
+ }
1711
+ /**
1712
+ * Memory for the frozen tool set. The caller owns the key: the pipeline passes
1713
+ * the caller's toolset hash, which in practice is one key per session.
1714
+ */
1715
+ interface ToolSetFreezer {
1716
+ read(key: string): {
1717
+ ids: string[];
1718
+ turns: number;
1719
+ } | undefined;
1720
+ record(key: string, ids: string[], turns: number): void;
1721
+ }
1722
+ /** In-process freezer with a key cap (clears wholesale when full). */
1723
+ declare class InMemoryToolSetFreezer implements ToolSetFreezer {
1724
+ private readonly cap;
1725
+ private map;
1726
+ constructor(cap?: number);
1727
+ read(key: string): {
1728
+ ids: string[];
1729
+ turns: number;
1730
+ } | undefined;
1731
+ record(key: string, ids: string[], turns: number): void;
1689
1732
  }
1690
1733
  declare class ToolSelector {
1691
1734
  private readonly maxTools;
1692
1735
  private readonly scoreIntentMatch;
1736
+ private readonly learningTurns;
1737
+ private readonly freezer;
1693
1738
  constructor(opts?: ToolSelectorOptions);
1694
1739
  /**
1695
1740
  * Todas las herramientas que pasó el llamante, acotadas SOLO por el
@@ -1704,7 +1749,13 @@ declare class ToolSelector {
1704
1749
  * pasaría a fallar en duro.
1705
1750
  */
1706
1751
  passthrough(tools: ToolDescriptor[] | undefined): ToolSelection;
1707
- select(text: string, tools: ToolDescriptor[] | undefined, execution: ExecutionClass): ToolSelection;
1752
+ select(text: string, tools: ToolDescriptor[] | undefined, execution: ExecutionClass,
1753
+ /**
1754
+ * Freeze key. The pipeline passes the caller's toolset hash, which in
1755
+ * practice is one key per session. Without a key nothing is frozen and the
1756
+ * selector behaves exactly as before.
1757
+ */
1758
+ freezeKey?: string): ToolSelection;
1708
1759
  }
1709
1760
 
1710
1761
  /**
@@ -2533,8 +2584,14 @@ declare class LicenseError extends Error {
2533
2584
  *
2534
2585
  * El comportamiento no cambia —sigue siendo fail-closed, `granted()` no lo
2535
2586
  * concede— pero ahora el mensaje puede decir la verdad.
2587
+ *
2588
+ * `none` means "there is no key": what is left after `normalizeLicenseKey` is
2589
+ * EMPTY. It is the same value `LicenseError.status` already carries when nothing
2590
+ * was configured, because it is the same problem with the same fix: a
2591
+ * `licenseKey: " "` (an empty environment variable, a blank secret file) used
2592
+ * to tell the customer their key "does not verify" — there was no key.
2536
2593
  */
2537
- type LicenseStatus = "active" | "grace" | "expired" | "invalid" | "revoked" | "stale";
2594
+ type LicenseStatus = "active" | "grace" | "expired" | "invalid" | "revoked" | "stale" | "none";
2538
2595
  /** Decoded license token body (mirror of `@bivelio/protocol`'s LicensePayload,
2539
2596
  * declared locally so the SDK stays free of a control-plane dependency). */
2540
2597
  interface LicenseTokenPayload {
@@ -2579,6 +2636,48 @@ interface LicenseKeyset {
2579
2636
  }
2580
2637
  /** Narrow a verification key to a {@link LicenseKeyset} (vs a bare SPKI PEM string). */
2581
2638
  declare function isKeyset(k: string | LicenseKeyset): k is LicenseKeyset;
2639
+ /**
2640
+ * Normalize a license key the way it arrives from the real world — ONCE, at the
2641
+ * entry point, never inside the verifier.
2642
+ *
2643
+ * The README says `export BIVELIO_LICENSE_KEY=…` and dotenv trims, so that path
2644
+ * never showed it. But anyone reading the key from a file (`readFileSync`), from
2645
+ * a Docker/Kubernetes secret (mounted as a file, trailing newline included) or
2646
+ * copying it out of an `Authorization` header handed over a CORRECT key with a
2647
+ * `\n` after it, a space before it, quotes around it or a `Bearer ` in front —
2648
+ * and the SDK answered "does not verify, check you pasted the WHOLE key". A
2649
+ * wrong diagnosis on a good key is the worst possible hint: it sends the
2650
+ * customer to look where the problem is not.
2651
+ *
2652
+ * Worse, it was INCONSISTENT: `tok + "\r\n"` passed and `tok + "\n"` did not,
2653
+ * because `atob` ignores ASCII whitespace while the padding was computed from a
2654
+ * length that included it, and two extra characters give the same remainder
2655
+ * modulo 4 as zero. Behavior that depends on which editor saved the file is not
2656
+ * behavior.
2657
+ *
2658
+ * Only OUTER wrapping is removed — whitespace, one pair of single or double
2659
+ * quotes, a `Bearer` prefix in any capitalization — in that order, in one pass.
2660
+ * Anything INSIDE the key is left alone: an interior space is not wrapping, it
2661
+ * is a broken key, and {@link licenseKeyShape} reports it as `malformed`.
2662
+ */
2663
+ declare function normalizeLicenseKey(raw: string): string;
2664
+ /**
2665
+ * The SHAPE of an already-normalized key, so the error message can tell apart
2666
+ * three problems that used to get the same text:
2667
+ *
2668
+ * · `empty` — there is no key (only whitespace, or nothing at all).
2669
+ * · `malformed` — not even the shape of a token: not three dot-separated
2670
+ * base64url segments. This is where the `bvk_` service key
2671
+ * pasted where the license goes lands, as does arbitrary text
2672
+ * or a token with a line break in the middle.
2673
+ * · `token` — has the shape; whether it verifies is up to the signature.
2674
+ *
2675
+ * It is the SAME shape rule {@link verifyCompactSignature} enforces, so that "has
2676
+ * the shape" and "verification was attempted" mean the same thing.
2677
+ */
2678
+ type LicenseKeyShape = "empty" | "malformed" | "token";
2679
+ /** Classify a normalized key. See {@link LicenseKeyShape}. */
2680
+ declare function licenseKeyShape(normalizedKey: string): LicenseKeyShape;
2582
2681
  /**
2583
2682
  * Verify the Ed25519 signature of any BV-SALA compact token
2584
2683
  * (`base64url(header).base64url(body).base64url(sig)`, sig over `${head}.${body}`)
@@ -2587,6 +2686,13 @@ declare function isKeyset(k: string | LicenseKeyset): k is LicenseKeyset;
2587
2686
  * so the browser (`@noble/curves`) and node (`node:crypto`) sides agree byte-for-byte
2588
2687
  * and there is no bespoke per-message verification path to keep in sync. It does NOT
2589
2688
  * interpret the payload or the header `typ` — callers do that.
2689
+ *
2690
+ * Shape is checked SEGMENT by segment, not just by counting dots. Three parts
2691
+ * used to be enough and the rest was left to `atob`, which ignores whitespace:
2692
+ * that is how a token with `\r\n` after it verified while the same token with
2693
+ * `\n` did not (see {@link normalizeLicenseKey}). The shared primitive must give
2694
+ * the same answer to the same input, always; a caller that wants to tolerate
2695
+ * wrapping normalizes BEFORE calling.
2590
2696
  */
2591
2697
  declare function verifyCompactSignature(token: string, publicKey: string | LicenseKeyset): CompactVerification;
2592
2698
  /** Verify a token's signature and decode its payload. Does not check expiry. */
@@ -2614,6 +2720,9 @@ declare function deriveStatus(payload: {
2614
2720
  * Verify + apply the grace policy → a resolved status. Mirrors what the issuing
2615
2721
  * control plane resolves for the same token. Bad signature / malformed
2616
2722
  * → invalid; otherwise the status comes from {@link deriveStatus}.
2723
+ *
2724
+ * A public entry point, so it normalizes like the others (gate and refresher):
2725
+ * the same key must resolve the same whichever way it comes in.
2617
2726
  */
2618
2727
  declare function resolveLicense(token: string, publicKey: string | LicenseKeyset, opts?: {
2619
2728
  nowSeconds?: number;
@@ -2690,6 +2799,15 @@ declare class RevocationSnapshot {
2690
2799
  interface ResolvedGate extends LicenseGate {
2691
2800
  isActive(): boolean;
2692
2801
  status(): LicenseStatus;
2802
+ /**
2803
+ * Why the key does not open, when it does not; `undefined` when it verified.
2804
+ * Under `status() === "invalid"` it tells a key that does NOT EVEN HAVE THE
2805
+ * SHAPE of a token (`"malformed"`: the `bvk_` pasted where the license goes,
2806
+ * arbitrary text) from a well-formed one whose signature fails (the verifier's
2807
+ * reason, e.g. `"bad signature"`). Both are `invalid` — the stable handle for
2808
+ * branching does not change — but the advice given to the customer differs.
2809
+ */
2810
+ reason(): string | undefined;
2693
2811
  /** Unix seconds after which the online status should be re-checked (poller anchor). */
2694
2812
  refreshAfter?(): number | undefined;
2695
2813
  }
@@ -3021,6 +3139,11 @@ interface SavingsLayerConfig {
3021
3139
  * against the embedded public key and builds a {@link licenseGate} from it.
3022
3140
  * Without a valid, active license (and without {@link devUnlicensed}), the
3023
3141
  * pipeline refuses to run — get a key at https://savings.bivelio.com.
3142
+ *
3143
+ * Surrounding whitespace (the trailing newline of a file or mounted secret),
3144
+ * wrapping quotes and a `Bearer ` prefix are stripped before verification, so
3145
+ * a key read with `readFileSync` works as-is. Anything INSIDE the key is not
3146
+ * touched: a key with a space in the middle is a broken key.
3024
3147
  */
3025
3148
  licenseKey?: string;
3026
3149
  /**
@@ -3324,4 +3447,4 @@ interface SavingsLayer {
3324
3447
  */
3325
3448
  declare function createSavingsLayer(config: Omit<SavingsLayerConfig, "tokenizer"> & Partial<Pick<SavingsLayerConfig, "tokenizer">>): SavingsLayer;
3326
3449
 
3327
- export { type AnthropicOptions, AnthropicProvider, BV_SALA_VERSION, type BivelioHandoff, CATALOGO, COMPACT_ROWS_INSTRUCTION, type CacheOutcome, type CacheTier, type CachedAnswer, type CanonicalRequest, type ChatMessage, type CompactRowsDecodeOptions, type CompactRowsOptions, type CompactVerification, type CompressionEconomics, type CompressionPolicyInput, type CompressionRequest, type CompressionResult, type Compressor, type ConstraintBackend, type ConstraintBackendId, type ConstraintPlan, type ConversationState, type CostBasis, type CostModel, DEFAULT_COST_MODEL, DEFAULT_MAX_STALENESS_SECONDS, type DeferredExecutionReport, type DeterministicResolver, EMBEDDED_PUBLIC_KEY, type EmbeddingFn, type EvidenceDoc, type ExactCache, ExactTokenizer, type ExecutionClass, ExecutionRouter, type FormatChoice, FormatSelector, type FormatSelectorOptions, type HandoffFact, HeuristicEstimator, InMemoryExactCache, InMemorySemanticCache, type JsonSchema, LicenseError, type LicenseGate, type LicenseKeyset, type LicenseStatus, type LicenseTokenPayload, type LocalClassifier, MODELOS_POR_CODIFICACION, MODELS_BY_ENCODING, type MemoryDelta, type MessageRole, MockProvider, type MockProviderOptions, type ModelCascadeMode, type ModelCascadeReport, type ModelCascadeSettings, type OpenAICompatibleOptions, OpenAICompatibleProvider, type OutputContract, PRECIOS_AL_DIA, type Policy, type ProtectedSpan, type Provider, type ProviderRequest, type ProviderResponse, type ProviderUsage, QUALITY_STATUSES, type QualityStatus, type RequestContext, type Reranker, type ResolvedDeps, type ResolvedGate, type ResolvedLicense, ResolverRegistry, type ResolverResult, type RetrievalOptions, type RetrievalOutcome, type RetrievalPlan, type Retriever, type RevocationListBody, type RevocationListVerification, type RevocationPollEvent, RevocationPoller, type RevocationPollerOptions, RevocationSnapshot, type RouteDecision, type SavingsLayer, type SavingsLayerConfig, SavingsLedger, type SavingsReport, type SavingsRequest, type SavingsResult, type SavingsToggles, SchemaRegistry, type SemanticCacheOptions, type SemanticHit, type SemanticRecord, type SemanticVerifier, type SerializationFormat, type Serializer, Singleflight, type TokenEstimator, TokenizerRegistry, type ToolCallRequest, type ToolDescriptor, type ToolRisk, type ToolSelection, ToolSelector, type ToolSelectorOptions, TraceCollector, type TraceEvent, UMBRAL_LOCAL, UsageEmitter, type UsageEmitterOptions, type ValidationError, type ValidationResult, adaptiveK, arithmeticResolver, buildExactKey, buildInflightKey, canonicalize, compactRowsSerializer, compresionFiel, computeNextDelay, cosine, createCompactRowsSerializer, createDefaultResolvers, createHandoff, createMockProvider, createSavingsLayer, csvSerializer, decodeCompactRows, dedupe, defaultTokenizerRegistry, deriveSeatId, deriveStatus, detectCi, embebedorLocal, emptyState, encodeCompactRows, evalArithmetic, evaluatePolicy, executeRetrievalPlan, gateFromKey, heuristicEstimator, isKeyset, isStrictCompatible, jsonCompactSerializer, keyTokensMatch, liveGateFromKey, mergeDelta, modelFamilyOf, multisetEqual, negationCount, noopCompressor, normalizeText, numericTokens, precioDeLista, projectContext, providerNativeBackend, randomId, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, sha256, shouldCompress, stableStringify, uniformColumns, validateAgainstSchema, vectorLocal, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans, words };
3450
+ export { type AnthropicOptions, AnthropicProvider, BV_SALA_VERSION, type BivelioHandoff, CATALOGO, COMPACT_ROWS_INSTRUCTION, type CacheOutcome, type CacheTier, type CachedAnswer, type CanonicalRequest, type ChatMessage, type CompactRowsDecodeOptions, type CompactRowsOptions, type CompactVerification, type CompressionEconomics, type CompressionPolicyInput, type CompressionRequest, type CompressionResult, type Compressor, type ConstraintBackend, type ConstraintBackendId, type ConstraintPlan, type ConversationState, type CostBasis, type CostModel, DEFAULT_COST_MODEL, DEFAULT_MAX_STALENESS_SECONDS, type DeferredExecutionReport, type DeterministicResolver, EMBEDDED_PUBLIC_KEY, type EmbeddingFn, type EvidenceDoc, type ExactCache, ExactTokenizer, type ExecutionClass, ExecutionRouter, type FormatChoice, FormatSelector, type FormatSelectorOptions, type HandoffFact, HeuristicEstimator, InMemoryExactCache, InMemorySemanticCache, InMemoryToolSetFreezer, type JsonSchema, LicenseError, type LicenseGate, type LicenseKeyShape, type LicenseKeyset, type LicenseStatus, type LicenseTokenPayload, type LocalClassifier, MODELOS_POR_CODIFICACION, MODELS_BY_ENCODING, type MemoryDelta, type MessageRole, MockProvider, type MockProviderOptions, type ModelCascadeMode, type ModelCascadeReport, type ModelCascadeSettings, type OpenAICompatibleOptions, OpenAICompatibleProvider, type OutputContract, PRECIOS_AL_DIA, type Policy, type ProtectedSpan, type Provider, type ProviderRequest, type ProviderResponse, type ProviderUsage, QUALITY_STATUSES, type QualityStatus, type RequestContext, type Reranker, type ResolvedDeps, type ResolvedGate, type ResolvedLicense, ResolverRegistry, type ResolverResult, type RetrievalOptions, type RetrievalOutcome, type RetrievalPlan, type Retriever, type RevocationListBody, type RevocationListVerification, type RevocationPollEvent, RevocationPoller, type RevocationPollerOptions, RevocationSnapshot, type RouteDecision, type SavingsLayer, type SavingsLayerConfig, SavingsLedger, type SavingsReport, type SavingsRequest, type SavingsResult, type SavingsToggles, SchemaRegistry, type SemanticCacheOptions, type SemanticHit, type SemanticRecord, type SemanticVerifier, type SerializationFormat, type Serializer, Singleflight, type TokenEstimator, TokenizerRegistry, type ToolCallRequest, type ToolDescriptor, type ToolRisk, type ToolSelection, ToolSelector, type ToolSelectorOptions, type ToolSetFreezer, TraceCollector, type TraceEvent, UMBRAL_LOCAL, UsageEmitter, type UsageEmitterOptions, type ValidationError, type ValidationResult, adaptiveK, arithmeticResolver, buildExactKey, buildInflightKey, canonicalize, compactRowsSerializer, compresionFiel, computeNextDelay, cosine, createCompactRowsSerializer, createDefaultResolvers, createHandoff, createMockProvider, createSavingsLayer, csvSerializer, decodeCompactRows, dedupe, defaultTokenizerRegistry, deriveSeatId, deriveStatus, detectCi, embebedorLocal, emptyState, encodeCompactRows, evalArithmetic, evaluatePolicy, executeRetrievalPlan, gateFromKey, heuristicEstimator, isKeyset, isStrictCompatible, jsonCompactSerializer, keyTokensMatch, licenseKeyShape, liveGateFromKey, mergeDelta, modelFamilyOf, multisetEqual, negationCount, noopCompressor, normalizeLicenseKey, normalizeText, numericTokens, precioDeLista, projectContext, providerNativeBackend, randomId, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, sha256, shouldCompress, stableStringify, uniformColumns, validateAgainstSchema, vectorLocal, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans, words };