usertrust 0.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.
Files changed (149) hide show
  1. package/dist/audit/canonical.d.ts +7 -0
  2. package/dist/audit/canonical.d.ts.map +1 -0
  3. package/dist/audit/canonical.js +24 -0
  4. package/dist/audit/canonical.js.map +1 -0
  5. package/dist/audit/chain.d.ts +33 -0
  6. package/dist/audit/chain.d.ts.map +1 -0
  7. package/dist/audit/chain.js +285 -0
  8. package/dist/audit/chain.js.map +1 -0
  9. package/dist/audit/entropy.d.ts +95 -0
  10. package/dist/audit/entropy.d.ts.map +1 -0
  11. package/dist/audit/entropy.js +229 -0
  12. package/dist/audit/entropy.js.map +1 -0
  13. package/dist/audit/merkle.d.ts +87 -0
  14. package/dist/audit/merkle.d.ts.map +1 -0
  15. package/dist/audit/merkle.js +315 -0
  16. package/dist/audit/merkle.js.map +1 -0
  17. package/dist/audit/rotation.d.ts +61 -0
  18. package/dist/audit/rotation.d.ts.map +1 -0
  19. package/dist/audit/rotation.js +160 -0
  20. package/dist/audit/rotation.js.map +1 -0
  21. package/dist/audit/verify.d.ts +20 -0
  22. package/dist/audit/verify.d.ts.map +1 -0
  23. package/dist/audit/verify.js +73 -0
  24. package/dist/audit/verify.js.map +1 -0
  25. package/dist/board/board.d.ts +67 -0
  26. package/dist/board/board.d.ts.map +1 -0
  27. package/dist/board/board.js +191 -0
  28. package/dist/board/board.js.map +1 -0
  29. package/dist/board/concerns.d.ts +59 -0
  30. package/dist/board/concerns.d.ts.map +1 -0
  31. package/dist/board/concerns.js +149 -0
  32. package/dist/board/concerns.js.map +1 -0
  33. package/dist/board/director.d.ts +49 -0
  34. package/dist/board/director.d.ts.map +1 -0
  35. package/dist/board/director.js +127 -0
  36. package/dist/board/director.js.map +1 -0
  37. package/dist/cli/health.d.ts +8 -0
  38. package/dist/cli/health.d.ts.map +1 -0
  39. package/dist/cli/health.js +119 -0
  40. package/dist/cli/health.js.map +1 -0
  41. package/dist/cli/init.d.ts +8 -0
  42. package/dist/cli/init.d.ts.map +1 -0
  43. package/dist/cli/init.js +67 -0
  44. package/dist/cli/init.js.map +1 -0
  45. package/dist/cli/inspect.d.ts +8 -0
  46. package/dist/cli/inspect.d.ts.map +1 -0
  47. package/dist/cli/inspect.js +114 -0
  48. package/dist/cli/inspect.js.map +1 -0
  49. package/dist/cli/main.d.ts +3 -0
  50. package/dist/cli/main.d.ts.map +1 -0
  51. package/dist/cli/main.js +35 -0
  52. package/dist/cli/main.js.map +1 -0
  53. package/dist/cli/snapshot.d.ts +10 -0
  54. package/dist/cli/snapshot.d.ts.map +1 -0
  55. package/dist/cli/snapshot.js +61 -0
  56. package/dist/cli/snapshot.js.map +1 -0
  57. package/dist/cli/tb.d.ts +8 -0
  58. package/dist/cli/tb.d.ts.map +1 -0
  59. package/dist/cli/tb.js +43 -0
  60. package/dist/cli/tb.js.map +1 -0
  61. package/dist/cli/verify.d.ts +7 -0
  62. package/dist/cli/verify.d.ts.map +1 -0
  63. package/dist/cli/verify.js +32 -0
  64. package/dist/cli/verify.js.map +1 -0
  65. package/dist/config.d.ts +12 -0
  66. package/dist/config.d.ts.map +1 -0
  67. package/dist/config.js +34 -0
  68. package/dist/config.js.map +1 -0
  69. package/dist/detect.d.ts +18 -0
  70. package/dist/detect.d.ts.map +1 -0
  71. package/dist/detect.js +49 -0
  72. package/dist/detect.js.map +1 -0
  73. package/dist/govern.d.ts +75 -0
  74. package/dist/govern.d.ts.map +1 -0
  75. package/dist/govern.js +581 -0
  76. package/dist/govern.js.map +1 -0
  77. package/dist/index.d.ts +6 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +8 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/ledger/client.d.ts +89 -0
  82. package/dist/ledger/client.d.ts.map +1 -0
  83. package/dist/ledger/client.js +417 -0
  84. package/dist/ledger/client.js.map +1 -0
  85. package/dist/ledger/engine.d.ts +68 -0
  86. package/dist/ledger/engine.d.ts.map +1 -0
  87. package/dist/ledger/engine.js +142 -0
  88. package/dist/ledger/engine.js.map +1 -0
  89. package/dist/ledger/pricing.d.ts +35 -0
  90. package/dist/ledger/pricing.d.ts.map +1 -0
  91. package/dist/ledger/pricing.js +142 -0
  92. package/dist/ledger/pricing.js.map +1 -0
  93. package/dist/memory/patterns.d.ts +35 -0
  94. package/dist/memory/patterns.d.ts.map +1 -0
  95. package/dist/memory/patterns.js +152 -0
  96. package/dist/memory/patterns.js.map +1 -0
  97. package/dist/policy/decay.d.ts +95 -0
  98. package/dist/policy/decay.d.ts.map +1 -0
  99. package/dist/policy/decay.js +133 -0
  100. package/dist/policy/decay.js.map +1 -0
  101. package/dist/policy/default-rules.d.ts +21 -0
  102. package/dist/policy/default-rules.d.ts.map +1 -0
  103. package/dist/policy/default-rules.js +60 -0
  104. package/dist/policy/default-rules.js.map +1 -0
  105. package/dist/policy/gate.d.ts +116 -0
  106. package/dist/policy/gate.d.ts.map +1 -0
  107. package/dist/policy/gate.js +227 -0
  108. package/dist/policy/gate.js.map +1 -0
  109. package/dist/policy/pii.d.ts +28 -0
  110. package/dist/policy/pii.d.ts.map +1 -0
  111. package/dist/policy/pii.js +124 -0
  112. package/dist/policy/pii.js.map +1 -0
  113. package/dist/proxy.d.ts +33 -0
  114. package/dist/proxy.d.ts.map +1 -0
  115. package/dist/proxy.js +36 -0
  116. package/dist/proxy.js.map +1 -0
  117. package/dist/resilience/circuit.d.ts +87 -0
  118. package/dist/resilience/circuit.d.ts.map +1 -0
  119. package/dist/resilience/circuit.js +167 -0
  120. package/dist/resilience/circuit.js.map +1 -0
  121. package/dist/resilience/scope.d.ts +97 -0
  122. package/dist/resilience/scope.d.ts.map +1 -0
  123. package/dist/resilience/scope.js +244 -0
  124. package/dist/resilience/scope.js.map +1 -0
  125. package/dist/shared/constants.d.ts +7 -0
  126. package/dist/shared/constants.d.ts.map +1 -0
  127. package/dist/shared/constants.js +7 -0
  128. package/dist/shared/constants.js.map +1 -0
  129. package/dist/shared/errors.d.ts +31 -0
  130. package/dist/shared/errors.d.ts.map +1 -0
  131. package/dist/shared/errors.js +61 -0
  132. package/dist/shared/errors.js.map +1 -0
  133. package/dist/shared/ids.d.ts +7 -0
  134. package/dist/shared/ids.d.ts.map +1 -0
  135. package/dist/shared/ids.js +31 -0
  136. package/dist/shared/ids.js.map +1 -0
  137. package/dist/shared/types.d.ts +162 -0
  138. package/dist/shared/types.d.ts.map +1 -0
  139. package/dist/shared/types.js +41 -0
  140. package/dist/shared/types.js.map +1 -0
  141. package/dist/snapshot/checkpoint.d.ts +22 -0
  142. package/dist/snapshot/checkpoint.d.ts.map +1 -0
  143. package/dist/snapshot/checkpoint.js +172 -0
  144. package/dist/snapshot/checkpoint.js.map +1 -0
  145. package/dist/streaming.d.ts +44 -0
  146. package/dist/streaming.d.ts.map +1 -0
  147. package/dist/streaming.js +123 -0
  148. package/dist/streaming.js.map +1 -0
  149. package/package.json +54 -0
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Two-phase spend engine for the usertrust SDK.
3
+ * Implements PENDING -> POST/VOID lifecycle for all governed operations.
4
+ *
5
+ * Extracted from usertools platform TokenEngineImpl.
6
+ */
7
+ import { createHash } from "node:crypto";
8
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { CreateTransferError } from "tigerbeetle-node";
11
+ import { DEFAULT_HOLD_TTL_MS } from "../shared/constants.js";
12
+ import { InsufficientBalanceError } from "../shared/errors.js";
13
+ import { fnv1a32 } from "../shared/ids.js";
14
+ import { XFER_SPEND } from "./client.js";
15
+ import { estimateCost } from "./pricing.js";
16
+ function writeDeadLetter(entry, dlqPath) {
17
+ try {
18
+ if (!existsSync(dlqPath)) {
19
+ mkdirSync(dlqPath, { recursive: true });
20
+ }
21
+ const line = `${JSON.stringify(entry)}\n`;
22
+ const filePath = join(dlqPath, "dead-letter.jsonl");
23
+ writeFileSync(filePath, line, { flag: "a" });
24
+ }
25
+ catch (err) {
26
+ console.error("[usertrust] Failed to write dead letter:", err);
27
+ }
28
+ }
29
+ // ── Helpers ──
30
+ /** Derive a 64-bit user fingerprint for user_data_64 on transfers. */
31
+ function deriveUserId64(userId) {
32
+ const hash = createHash("sha256").update(userId).digest("hex");
33
+ return BigInt(`0x${hash.slice(0, 16)}`);
34
+ }
35
+ /** Check whether a caught error is a TigerBeetle insufficient-balance rejection. */
36
+ function isInsufficientBalanceError(err) {
37
+ if (err == null || typeof err !== "object")
38
+ return false;
39
+ if (!("code" in err) || !("name" in err))
40
+ return false;
41
+ const e = err;
42
+ return (e.name === "TBTransferError" &&
43
+ (e.code === CreateTransferError.exceeds_credits ||
44
+ e.code === CreateTransferError.overflows_debits ||
45
+ e.code === CreateTransferError.overflows_debits_pending));
46
+ }
47
+ // ── Engine ──
48
+ export class TrustEngine {
49
+ tb;
50
+ holdTtlMs;
51
+ dlqPath;
52
+ constructor(opts) {
53
+ this.tb = opts.tbClient;
54
+ this.holdTtlMs = opts.holdTtlMs ?? DEFAULT_HOLD_TTL_MS;
55
+ this.dlqPath = opts.dlqPath ?? join(".usertrust", "dlq");
56
+ }
57
+ /**
58
+ * Create a PENDING hold for an AI compute action.
59
+ * The hold reserves tokens until settled (POST) or released (VOID).
60
+ */
61
+ async spendPending(p) {
62
+ const cost = estimateCost(p.action.model, p.action.inputTokens, p.action.outputTokens);
63
+ const userAccount = this.tb.getAccountId(p.userId);
64
+ const treasury = this.tb.getTreasuryId();
65
+ const bal = await this.tb.lookupBalance(userAccount);
66
+ if (bal.available < cost) {
67
+ throw new InsufficientBalanceError(p.userId, cost, bal.available);
68
+ }
69
+ const holdTimeoutSeconds = Math.ceil(this.holdTtlMs / 1000);
70
+ let pendingId;
71
+ try {
72
+ pendingId = await this.tb.createPendingTransfer({
73
+ debitAccountId: userAccount,
74
+ creditAccountId: treasury,
75
+ amount: cost,
76
+ code: XFER_SPEND,
77
+ timeoutSeconds: holdTimeoutSeconds,
78
+ userData64: deriveUserId64(p.userId),
79
+ ...(p.metadata?.agentRef ? { userData32: fnv1a32(p.metadata.agentRef) } : {}),
80
+ });
81
+ }
82
+ catch (err) {
83
+ if (isInsufficientBalanceError(err)) {
84
+ throw new InsufficientBalanceError(p.userId, cost, 0);
85
+ }
86
+ throw err;
87
+ }
88
+ return {
89
+ transferId: pendingId.toString(),
90
+ debitAccountId: userAccount.toString(),
91
+ creditAccountId: treasury.toString(),
92
+ amount: cost,
93
+ timestamp: new Date().toISOString(),
94
+ pending: true,
95
+ };
96
+ }
97
+ /**
98
+ * Settle a PENDING hold — debit is finalized.
99
+ * Optionally pass actual amount if less than the hold.
100
+ */
101
+ async postPendingSpend(transferId, actualAmount) {
102
+ try {
103
+ await this.tb.postTransfer(BigInt(transferId), actualAmount);
104
+ }
105
+ catch (err) {
106
+ // Post may have succeeded on TB but timed out — try to void
107
+ let voidSucceeded = false;
108
+ try {
109
+ await this.tb.voidTransfer(BigInt(transferId));
110
+ voidSucceeded = true;
111
+ }
112
+ catch {
113
+ // Both post and void failed — transfer state is ambiguous
114
+ writeDeadLetter({
115
+ timestamp: new Date().toISOString(),
116
+ source: "engine.postPendingSpend.ambiguous",
117
+ transferId,
118
+ payload: { actualAmount },
119
+ error: "Both post and void failed — transfer state ambiguous",
120
+ }, this.dlqPath);
121
+ throw new Error(`Spend settlement ambiguous for transfer ${transferId}`);
122
+ }
123
+ if (voidSucceeded) {
124
+ throw new Error("Spend failed: pending transfer voided after post failure");
125
+ }
126
+ }
127
+ }
128
+ /**
129
+ * Release a PENDING hold — tokens returned to the user's available balance.
130
+ */
131
+ async voidPendingSpend(transferId) {
132
+ await this.tb.voidTransfer(BigInt(transferId));
133
+ }
134
+ /**
135
+ * Query balance for a user.
136
+ */
137
+ async balance(userId) {
138
+ const accountId = this.tb.getAccountId(userId);
139
+ return await this.tb.lookupBalance(accountId);
140
+ }
141
+ }
142
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/ledger/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Pricing table for the top 20 LLM models.
3
+ * All rates are in usertokens per 1,000 LLM tokens.
4
+ * 1 usertoken = $0.0001 (one basis point of a cent).
5
+ *
6
+ * Extracted from usertools platform — canonical pricing source.
7
+ */
8
+ export interface ModelRates {
9
+ inputPer1k: number;
10
+ outputPer1k: number;
11
+ }
12
+ /**
13
+ * Pricing table for the top 20 models supported by the SDK.
14
+ * Key: model identifier as sent by the client.
15
+ */
16
+ export declare const PRICING_TABLE: Record<string, ModelRates>;
17
+ /** Fallback rate for unknown models (sonnet-class pricing). */
18
+ export declare const FALLBACK_RATE: ModelRates;
19
+ /**
20
+ * Look up rates by model string. Falls back to prefix matching,
21
+ * then FALLBACK_RATE for unknown models.
22
+ */
23
+ export declare function getModelRates(model: string): ModelRates;
24
+ /**
25
+ * Estimate cost in usertokens for a model call.
26
+ * Returns at least 1 (floor to prevent zero-amount transfers).
27
+ */
28
+ export declare function estimateCost(model: string, inputTokens: number, outputTokens: number): number;
29
+ /**
30
+ * Estimate input token count from a messages array.
31
+ * Heuristic: ~4 chars/token with a 1.5x safety margin so the PENDING
32
+ * hold exceeds actual cost in the vast majority of cases.
33
+ */
34
+ export declare function estimateInputTokens(messages: unknown[]): number;
35
+ //# sourceMappingURL=pricing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/ledger/pricing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,UAAU;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CA0CpD,CAAC;AAKF,+DAA+D;AAC/D,eAAO,MAAM,aAAa,EAAE,UAAiD,CAAC;AAE9E;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAUvD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAK7F;AA6BD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAqC/D"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Pricing table for the top 20 LLM models.
3
+ * All rates are in usertokens per 1,000 LLM tokens.
4
+ * 1 usertoken = $0.0001 (one basis point of a cent).
5
+ *
6
+ * Extracted from usertools platform — canonical pricing source.
7
+ */
8
+ /**
9
+ * Pricing table for the top 20 models supported by the SDK.
10
+ * Key: model identifier as sent by the client.
11
+ */
12
+ export const PRICING_TABLE = {
13
+ // ── Anthropic ──
14
+ "claude-sonnet-4-6": { inputPer1k: 30, outputPer1k: 150 },
15
+ "claude-haiku-4-5": { inputPer1k: 10, outputPer1k: 50 },
16
+ "claude-opus-4-6": { inputPer1k: 50, outputPer1k: 250 },
17
+ // ── OpenAI ──
18
+ "gpt-4o": { inputPer1k: 25, outputPer1k: 100 },
19
+ "gpt-4o-mini": { inputPer1k: 1.5, outputPer1k: 6 },
20
+ "gpt-5.4": { inputPer1k: 25, outputPer1k: 150 },
21
+ o3: { inputPer1k: 20, outputPer1k: 80 },
22
+ "o4-mini": { inputPer1k: 5.5, outputPer1k: 22 },
23
+ // ── Google Gemini ──
24
+ "gemini-2.5-flash": { inputPer1k: 3, outputPer1k: 25 },
25
+ "gemini-2.5-pro": { inputPer1k: 12.5, outputPer1k: 100 },
26
+ "gemini-3.1-pro": { inputPer1k: 20, outputPer1k: 120 },
27
+ // ── Mistral ──
28
+ "mistral-large": { inputPer1k: 5, outputPer1k: 15 },
29
+ // ── DeepSeek ──
30
+ "deepseek-chat": { inputPer1k: 2.8, outputPer1k: 4.2 },
31
+ "deepseek-reasoner": { inputPer1k: 2.8, outputPer1k: 4.2 },
32
+ // ── xAI ──
33
+ "grok-3": { inputPer1k: 30, outputPer1k: 150 },
34
+ // ── Meta (via Bedrock) ──
35
+ "llama-4-maverick": { inputPer1k: 2.4, outputPer1k: 9.7 },
36
+ // ── Cohere ──
37
+ "command-a": { inputPer1k: 25, outputPer1k: 100 },
38
+ // ── Perplexity ──
39
+ "sonar-pro": { inputPer1k: 30, outputPer1k: 150 },
40
+ // ── Alibaba ──
41
+ "qwen-72b": { inputPer1k: 2.9, outputPer1k: 3.9 },
42
+ // ── Amazon ──
43
+ "nova-pro": { inputPer1k: 8, outputPer1k: 32 },
44
+ };
45
+ /** Pre-sorted entries for prefix matching (longest key first). */
46
+ const SORTED_TABLE = Object.entries(PRICING_TABLE).sort((a, b) => b[0].length - a[0].length);
47
+ /** Fallback rate for unknown models (sonnet-class pricing). */
48
+ export const FALLBACK_RATE = { inputPer1k: 30, outputPer1k: 150 };
49
+ /**
50
+ * Look up rates by model string. Falls back to prefix matching,
51
+ * then FALLBACK_RATE for unknown models.
52
+ */
53
+ export function getModelRates(model) {
54
+ const exact = PRICING_TABLE[model];
55
+ if (exact)
56
+ return exact;
57
+ // Prefix match — longest key first prevents partial matches
58
+ for (const [key, rates] of SORTED_TABLE) {
59
+ if (model.startsWith(key))
60
+ return rates;
61
+ }
62
+ return FALLBACK_RATE;
63
+ }
64
+ /**
65
+ * Estimate cost in usertokens for a model call.
66
+ * Returns at least 1 (floor to prevent zero-amount transfers).
67
+ */
68
+ export function estimateCost(model, inputTokens, outputTokens) {
69
+ const rates = getModelRates(model);
70
+ const inputCost = (inputTokens / 1000) * rates.inputPer1k;
71
+ const outputCost = (outputTokens / 1000) * rates.outputPer1k;
72
+ return Math.max(1, Math.ceil(inputCost + outputCost));
73
+ }
74
+ /**
75
+ * Estimate the block-level token count for a non-text content block.
76
+ * Extracts textual payload where possible, falls back to serialised length.
77
+ */
78
+ function estimateBlockTokens(block) {
79
+ let chars = 0;
80
+ if (typeof block.text === "string")
81
+ chars += block.text.length;
82
+ if (typeof block.content === "string")
83
+ chars += block.content.length;
84
+ // Handle nested arrays (tool_result payloads)
85
+ if (Array.isArray(block.content)) {
86
+ for (const item of block.content) {
87
+ if (typeof item === "string") {
88
+ chars += item.length;
89
+ }
90
+ else if (item != null && typeof item === "object") {
91
+ chars += JSON.stringify(item).length;
92
+ }
93
+ }
94
+ }
95
+ // Conservative fallback for unknown/binary shapes
96
+ if (chars === 0)
97
+ chars = JSON.stringify(block).length;
98
+ return Math.ceil(chars / 4);
99
+ }
100
+ /**
101
+ * Estimate input token count from a messages array.
102
+ * Heuristic: ~4 chars/token with a 1.5x safety margin so the PENDING
103
+ * hold exceeds actual cost in the vast majority of cases.
104
+ */
105
+ export function estimateInputTokens(messages) {
106
+ if (!Array.isArray(messages))
107
+ return 1;
108
+ let textChars = 0;
109
+ let blockTokens = 0;
110
+ for (const msg of messages) {
111
+ if (msg == null || typeof msg !== "object")
112
+ continue;
113
+ const m = msg;
114
+ // ~4 tokens per-message overhead (role, structure)
115
+ textChars += 16;
116
+ const content = m.content;
117
+ if (typeof content === "string") {
118
+ textChars += content.length;
119
+ }
120
+ else if (Array.isArray(content)) {
121
+ for (const block of content) {
122
+ if (block == null || typeof block !== "object")
123
+ continue;
124
+ const b = block;
125
+ if (b.type === "text" && typeof b.text === "string") {
126
+ textChars += b.text.length;
127
+ }
128
+ else {
129
+ blockTokens += estimateBlockTokens(b);
130
+ }
131
+ }
132
+ }
133
+ // Tool-call overhead
134
+ if (typeof m.tool_call_id === "string")
135
+ blockTokens += 10;
136
+ }
137
+ const textTokens = Math.ceil(textChars / 4);
138
+ const raw = textTokens + blockTokens;
139
+ // 1.5x safety margin
140
+ return Math.max(1, Math.ceil(raw * 1.5));
141
+ }
142
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../src/ledger/pricing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ export interface PatternEntry {
2
+ promptHash: string;
3
+ model: string;
4
+ cost: number;
5
+ success: boolean;
6
+ timestamp: string;
7
+ }
8
+ export interface PatternStats {
9
+ totalEntries: number;
10
+ uniqueModels: number;
11
+ hitCount: Map<string, number>;
12
+ }
13
+ /** Reset internal state — for testing only. */
14
+ export declare function _resetPatternCache(): void;
15
+ /**
16
+ * Hash prompt text with SHA-256. Never store raw prompts.
17
+ */
18
+ export declare function hashPrompt(text: string): string;
19
+ /**
20
+ * Record a pattern entry from a completed LLM call.
21
+ * Appends to `.usertools/patterns/memory.json`.
22
+ * Evicts oldest entries when exceeding 10,000.
23
+ */
24
+ export declare function recordPattern(entry: Omit<PatternEntry, "timestamp">, vaultPath?: string): Promise<void>;
25
+ /**
26
+ * Suggest the best model for a given prompt hash based on past patterns.
27
+ * Returns the model with the best cost-adjusted success ratio, or null if
28
+ * no patterns exist for this prompt hash.
29
+ */
30
+ export declare function suggestModel(promptHash: string, vaultPath?: string): string | null;
31
+ /**
32
+ * Return summary statistics for all stored patterns.
33
+ */
34
+ export declare function getPatternStats(vaultPath?: string): Promise<PatternStats>;
35
+ //# sourceMappingURL=patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/memory/patterns.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,YAAY;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAoCD,+CAA+C;AAC/C,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAuCD;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAClC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,EACtC,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA6ClF;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAgB/E"}
@@ -0,0 +1,152 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { VAULT_DIR } from "../shared/constants.js";
5
+ // ── Constants ──
6
+ const PATTERNS_DIR = "patterns";
7
+ const MEMORY_FILE = "memory.json";
8
+ const MAX_ENTRIES = 10_000;
9
+ const cacheByVault = new Map();
10
+ function resolveVaultKey(vaultPath) {
11
+ return vaultPath ?? VAULT_DIR;
12
+ }
13
+ function getCache(vaultPath) {
14
+ const key = resolveVaultKey(vaultPath);
15
+ let entry = cacheByVault.get(key);
16
+ if (entry === undefined) {
17
+ entry = { entries: [], initialized: false };
18
+ cacheByVault.set(key, entry);
19
+ }
20
+ return entry;
21
+ }
22
+ function memoryFilePath(vaultPath) {
23
+ const base = vaultPath ?? VAULT_DIR;
24
+ return join(base, PATTERNS_DIR, MEMORY_FILE);
25
+ }
26
+ /** Reset internal state — for testing only. */
27
+ export function _resetPatternCache() {
28
+ cacheByVault.clear();
29
+ }
30
+ // ── Internals ──
31
+ async function ensureLoaded(vaultPath) {
32
+ const cache = getCache(vaultPath);
33
+ if (cache.initialized) {
34
+ return cache.entries;
35
+ }
36
+ const filePath = memoryFilePath(vaultPath);
37
+ try {
38
+ const raw = await readFile(filePath, "utf-8");
39
+ const parsed = JSON.parse(raw);
40
+ if (!Array.isArray(parsed)) {
41
+ cache.entries = [];
42
+ }
43
+ else {
44
+ cache.entries = parsed;
45
+ }
46
+ }
47
+ catch {
48
+ // File doesn't exist or is corrupt — start fresh
49
+ cache.entries = [];
50
+ }
51
+ cache.initialized = true;
52
+ return cache.entries;
53
+ }
54
+ async function persist(entries, vaultPath) {
55
+ const filePath = memoryFilePath(vaultPath);
56
+ const dir = dirname(filePath);
57
+ await mkdir(dir, { recursive: true });
58
+ // Atomic write via rename
59
+ const tmpPath = `${filePath}.tmp.${Date.now()}`;
60
+ await writeFile(tmpPath, JSON.stringify(entries, null, "\t"), "utf-8");
61
+ await rename(tmpPath, filePath);
62
+ }
63
+ // ── Public API ──
64
+ /**
65
+ * Hash prompt text with SHA-256. Never store raw prompts.
66
+ */
67
+ export function hashPrompt(text) {
68
+ return createHash("sha256").update(text).digest("hex");
69
+ }
70
+ /**
71
+ * Record a pattern entry from a completed LLM call.
72
+ * Appends to `.usertools/patterns/memory.json`.
73
+ * Evicts oldest entries when exceeding 10,000.
74
+ */
75
+ export async function recordPattern(entry, vaultPath) {
76
+ const entries = await ensureLoaded(vaultPath);
77
+ const full = {
78
+ ...entry,
79
+ timestamp: new Date().toISOString(),
80
+ };
81
+ entries.push(full);
82
+ // Evict oldest if over capacity
83
+ if (entries.length > MAX_ENTRIES) {
84
+ const excess = entries.length - MAX_ENTRIES;
85
+ entries.splice(0, excess);
86
+ }
87
+ const cache = getCache(vaultPath);
88
+ cache.entries = entries;
89
+ await persist(entries, vaultPath);
90
+ }
91
+ /**
92
+ * Suggest the best model for a given prompt hash based on past patterns.
93
+ * Returns the model with the best cost-adjusted success ratio, or null if
94
+ * no patterns exist for this prompt hash.
95
+ */
96
+ export function suggestModel(promptHash, vaultPath) {
97
+ const cache = getCache(vaultPath);
98
+ if (!cache.initialized || cache.entries.length === 0) {
99
+ return null;
100
+ }
101
+ // Filter entries matching this prompt hash
102
+ const matching = cache.entries.filter((e) => e.promptHash === promptHash);
103
+ if (matching.length === 0) {
104
+ return null;
105
+ }
106
+ // Group by model, compute success rate / average cost
107
+ const modelStats = new Map();
108
+ for (const entry of matching) {
109
+ let stats = modelStats.get(entry.model);
110
+ if (stats === undefined) {
111
+ stats = { successes: 0, total: 0, totalCost: 0 };
112
+ modelStats.set(entry.model, stats);
113
+ }
114
+ stats.total += 1;
115
+ stats.totalCost += entry.cost;
116
+ if (entry.success) {
117
+ stats.successes += 1;
118
+ }
119
+ }
120
+ // Score: success_rate / avg_cost (higher is better)
121
+ // If avg_cost is 0, treat as very efficient (use large score)
122
+ let bestModel = null;
123
+ let bestScore = -1;
124
+ for (const [model, stats] of modelStats) {
125
+ const successRate = stats.successes / stats.total;
126
+ const avgCost = stats.totalCost / stats.total;
127
+ const score = avgCost > 0 ? successRate / avgCost : successRate * 1_000_000;
128
+ if (score > bestScore) {
129
+ bestScore = score;
130
+ bestModel = model;
131
+ }
132
+ }
133
+ return bestModel;
134
+ }
135
+ /**
136
+ * Return summary statistics for all stored patterns.
137
+ */
138
+ export async function getPatternStats(vaultPath) {
139
+ const entries = await ensureLoaded(vaultPath);
140
+ const models = new Set();
141
+ const hitCount = new Map();
142
+ for (const entry of entries) {
143
+ models.add(entry.model);
144
+ hitCount.set(entry.promptHash, (hitCount.get(entry.promptHash) ?? 0) + 1);
145
+ }
146
+ return {
147
+ totalEntries: entries.length,
148
+ uniqueModels: models.size,
149
+ hitCount,
150
+ };
151
+ }
152
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/memory/patterns.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Decay Rate Calculator
3
+ *
4
+ * Calculates exponential decay rates for quota/budget consumption.
5
+ * Enables time-weighted rate limiting that rewards idle periods.
6
+ *
7
+ * The decay model uses: value(t) = initial * e^(-lambda * t)
8
+ * where lambda is the decay constant and t is time elapsed.
9
+ *
10
+ * Adapted from Field Project fermion/src/higgs/decay-rate.ts.
11
+ */
12
+ /** Configuration for decay rate calculation. */
13
+ export interface DecayConfig {
14
+ /** Half-life in milliseconds — time for value to decay to 50%. */
15
+ halfLifeMs: number;
16
+ /** Minimum threshold below which value is considered zero. */
17
+ minThreshold?: number;
18
+ }
19
+ /** A timestamped entry for weighted sum calculation. */
20
+ export interface TimestampedEntry {
21
+ /** Timestamp in milliseconds (e.g. Date.now()) */
22
+ ts: number;
23
+ /** The value at that timestamp */
24
+ value: number;
25
+ }
26
+ /** Result of a decay rate calculation. */
27
+ export interface DecayResult {
28
+ /** The decayed value. */
29
+ decayedValue: number;
30
+ /** The decay factor applied (0-1). */
31
+ decayFactor: number;
32
+ /** Time elapsed since reference point (ms). */
33
+ elapsedMs: number;
34
+ /** Whether the value has decayed below threshold. */
35
+ fullyDecayed: boolean;
36
+ }
37
+ /**
38
+ * Decay rate calculator using exponential decay model.
39
+ *
40
+ * Used for time-weighted rate limiting. Older consumption contributes less
41
+ * to current budget usage, rewarding tenants who space out their requests.
42
+ */
43
+ export declare class DecayRateCalculator {
44
+ /** Decay constant (lambda = ln(2) / halfLife) */
45
+ private readonly lambda;
46
+ /** Minimum threshold for considering a value "fully decayed" */
47
+ private readonly minThreshold;
48
+ constructor(config: DecayConfig);
49
+ /**
50
+ * Calculate the decayed value after a given time elapsed.
51
+ *
52
+ * @param initialValue - The original value before decay
53
+ * @param elapsedMs - Time elapsed in milliseconds
54
+ * @returns Decay result with decayed value and metadata
55
+ */
56
+ calculate(initialValue: number, elapsedMs: number): DecayResult;
57
+ /**
58
+ * Calculate the weighted sum of multiple timestamped values.
59
+ * Each value is decayed based on its age relative to the reference time.
60
+ *
61
+ * @param entries - Array of timestamped entries
62
+ * @param referenceTime - The reference time (usually Date.now())
63
+ * @returns Total decayed value
64
+ */
65
+ calculateWeightedSum(entries: TimestampedEntry[], referenceTime?: number): number;
66
+ /**
67
+ * Calculate time required for a value to decay to a target.
68
+ *
69
+ * @param currentValue - Current value
70
+ * @param targetValue - Target value to decay to
71
+ * @returns Time in milliseconds, or Infinity if target >= current
72
+ */
73
+ timeToDecay(currentValue: number, targetValue: number): number;
74
+ /**
75
+ * Get the half-life of this calculator.
76
+ */
77
+ getHalfLifeMs(): number;
78
+ }
79
+ /**
80
+ * Calculate the decay rate for a set of timestamped entries.
81
+ *
82
+ * This is the primary entry point — a pure function that creates a calculator,
83
+ * computes the weighted sum, and returns the total decayed value.
84
+ *
85
+ * @param entries - Array of timestamped entries
86
+ * @param halfLife - Half-life in milliseconds (default: 1 hour)
87
+ * @returns Total decayed value (weighted sum)
88
+ */
89
+ export declare function calculateDecayRate(entries: TimestampedEntry[], halfLife?: number): number;
90
+ /**
91
+ * Create a decay calculator with a half-life matching a cost window.
92
+ * The half-life is set to 1/4 of the window so most decay occurs within it.
93
+ */
94
+ export declare function createCostDecayCalculator(windowMs: number): DecayRateCalculator;
95
+ //# sourceMappingURL=decay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decay.d.ts","sourceRoot":"","sources":["../../src/policy/decay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC3B,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAChC,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;CACd;AAED,0CAA0C;AAC1C,MAAM,WAAW,WAAW;IAC3B,yBAAyB;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,YAAY,EAAE,OAAO,CAAC;CACtB;AAMD;;;;;GAKG;AACH,qBAAa,mBAAmB;IAC/B,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,gEAAgE;IAChE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,MAAM,EAAE,WAAW;IAQ/B;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW;IA0B/D;;;;;;;OAOG;IACH,oBAAoB,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,aAAa,GAAE,MAAmB,GAAG,MAAM;IAiB7F;;;;;;OAMG;IACH,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IAS9D;;OAEG;IACH,aAAa,IAAI,MAAM;CAGvB;AAMD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,QAAQ,SAAY,GAAG,MAAM,CAG5F;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CAK/E"}