@bryti/agent 0.0.1 → 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.
- package/Dockerfile +27 -0
- package/README.md +77 -50
- package/config.example.yml +265 -0
- package/dist/active-hours.d.ts +23 -0
- package/dist/active-hours.d.ts.map +1 -0
- package/dist/active-hours.js +68 -0
- package/dist/active-hours.js.map +1 -0
- package/dist/agent.d.ts +84 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +383 -0
- package/dist/agent.js.map +1 -0
- package/dist/channels/markdown/ir.d.ts +79 -0
- package/dist/channels/markdown/ir.d.ts.map +1 -0
- package/dist/channels/markdown/ir.js +824 -0
- package/dist/channels/markdown/ir.js.map +1 -0
- package/dist/channels/markdown/render.d.ts +35 -0
- package/dist/channels/markdown/render.d.ts.map +1 -0
- package/dist/channels/markdown/render.js +178 -0
- package/dist/channels/markdown/render.js.map +1 -0
- package/dist/channels/telegram-network-errors.d.ts +27 -0
- package/dist/channels/telegram-network-errors.d.ts.map +1 -0
- package/dist/channels/telegram-network-errors.js +156 -0
- package/dist/channels/telegram-network-errors.js.map +1 -0
- package/dist/channels/telegram.d.ts +76 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +814 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/types.d.ts +59 -0
- package/dist/channels/types.d.ts.map +1 -0
- package/dist/channels/types.js +9 -0
- package/dist/channels/types.js.map +1 -0
- package/dist/channels/whatsapp.d.ts +45 -0
- package/dist/channels/whatsapp.d.ts.map +1 -0
- package/dist/channels/whatsapp.js +310 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +635 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +35 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +113 -0
- package/dist/commands.js.map +1 -0
- package/dist/compaction/history.d.ts +17 -0
- package/dist/compaction/history.d.ts.map +1 -0
- package/dist/compaction/history.js +35 -0
- package/dist/compaction/history.js.map +1 -0
- package/dist/compaction/index.d.ts +3 -0
- package/dist/compaction/index.d.ts.map +1 -0
- package/dist/compaction/index.js +3 -0
- package/dist/compaction/index.js.map +1 -0
- package/dist/compaction/proactive.d.ts +25 -0
- package/dist/compaction/proactive.d.ts.map +1 -0
- package/dist/compaction/proactive.js +87 -0
- package/dist/compaction/proactive.js.map +1 -0
- package/dist/compaction/transcript-repair.d.ts +55 -0
- package/dist/compaction/transcript-repair.d.ts.map +1 -0
- package/dist/compaction/transcript-repair.js +215 -0
- package/dist/compaction/transcript-repair.js.map +1 -0
- package/dist/config.d.ts +128 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +317 -0
- package/dist/config.js.map +1 -0
- package/dist/crash-recovery.d.ts +23 -0
- package/dist/crash-recovery.d.ts.map +1 -0
- package/dist/crash-recovery.js +96 -0
- package/dist/crash-recovery.js.map +1 -0
- package/dist/defaults/extensions/EXTENSIONS.md +158 -0
- package/dist/defaults/extensions/documents-hedgedoc.ts +153 -0
- package/dist/history.d.ts +31 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +49 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +673 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +39 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +143 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory/conversation-search.d.ts +15 -0
- package/dist/memory/conversation-search.d.ts.map +1 -0
- package/dist/memory/conversation-search.js +60 -0
- package/dist/memory/conversation-search.js.map +1 -0
- package/dist/memory/core-memory.d.ts +28 -0
- package/dist/memory/core-memory.d.ts.map +1 -0
- package/dist/memory/core-memory.js +102 -0
- package/dist/memory/core-memory.js.map +1 -0
- package/dist/memory/embeddings.d.ts +44 -0
- package/dist/memory/embeddings.d.ts.map +1 -0
- package/dist/memory/embeddings.js +139 -0
- package/dist/memory/embeddings.js.map +1 -0
- package/dist/memory/search.d.ts +49 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +97 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/store.d.ts +32 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +205 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/message-queue.d.ts +73 -0
- package/dist/message-queue.d.ts.map +1 -0
- package/dist/message-queue.js +188 -0
- package/dist/message-queue.js.map +1 -0
- package/dist/model-infra.d.ts +64 -0
- package/dist/model-infra.d.ts.map +1 -0
- package/dist/model-infra.js +202 -0
- package/dist/model-infra.js.map +1 -0
- package/dist/projection/format.d.ts +10 -0
- package/dist/projection/format.d.ts.map +1 -0
- package/dist/projection/format.js +30 -0
- package/dist/projection/format.js.map +1 -0
- package/dist/projection/index.d.ts +11 -0
- package/dist/projection/index.d.ts.map +1 -0
- package/dist/projection/index.js +9 -0
- package/dist/projection/index.js.map +1 -0
- package/dist/projection/reflection.d.ts +94 -0
- package/dist/projection/reflection.d.ts.map +1 -0
- package/dist/projection/reflection.js +334 -0
- package/dist/projection/reflection.js.map +1 -0
- package/dist/projection/store.d.ts +144 -0
- package/dist/projection/store.d.ts.map +1 -0
- package/dist/projection/store.js +519 -0
- package/dist/projection/store.js.map +1 -0
- package/dist/projection/tools.d.ts +11 -0
- package/dist/projection/tools.d.ts.map +1 -0
- package/dist/projection/tools.js +237 -0
- package/dist/projection/tools.js.map +1 -0
- package/dist/scheduler.d.ts +36 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +286 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/system-prompt.d.ts +41 -0
- package/dist/system-prompt.d.ts.map +1 -0
- package/dist/system-prompt.js +162 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/time.d.ts +52 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +138 -0
- package/dist/time.js.map +1 -0
- package/dist/tools/archival-memory-tool.d.ts +8 -0
- package/dist/tools/archival-memory-tool.d.ts.map +1 -0
- package/dist/tools/archival-memory-tool.js +68 -0
- package/dist/tools/archival-memory-tool.js.map +1 -0
- package/dist/tools/conversation-search-tool.d.ts +6 -0
- package/dist/tools/conversation-search-tool.d.ts.map +1 -0
- package/dist/tools/conversation-search-tool.js +28 -0
- package/dist/tools/conversation-search-tool.js.map +1 -0
- package/dist/tools/core-memory-tool.d.ts +7 -0
- package/dist/tools/core-memory-tool.d.ts.map +1 -0
- package/dist/tools/core-memory-tool.js +59 -0
- package/dist/tools/core-memory-tool.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +15 -0
- package/dist/tools/fetch-url.d.ts.map +1 -0
- package/dist/tools/fetch-url.js +76 -0
- package/dist/tools/fetch-url.js.map +1 -0
- package/dist/tools/files.d.ts +10 -0
- package/dist/tools/files.d.ts.map +1 -0
- package/dist/tools/files.js +127 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/index.d.ts +17 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +118 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/result.d.ts +21 -0
- package/dist/tools/result.d.ts.map +1 -0
- package/dist/tools/result.js +36 -0
- package/dist/tools/result.js.map +1 -0
- package/dist/tools/skill-install.d.ts +17 -0
- package/dist/tools/skill-install.d.ts.map +1 -0
- package/dist/tools/skill-install.js +148 -0
- package/dist/tools/skill-install.js.map +1 -0
- package/dist/tools/web-search.d.ts +42 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +237 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/trust/guardrail.d.ts +60 -0
- package/dist/trust/guardrail.d.ts.map +1 -0
- package/dist/trust/guardrail.js +171 -0
- package/dist/trust/guardrail.js.map +1 -0
- package/dist/trust/index.d.ts +12 -0
- package/dist/trust/index.d.ts.map +1 -0
- package/dist/trust/index.js +12 -0
- package/dist/trust/index.js.map +1 -0
- package/dist/trust/store.d.ts +118 -0
- package/dist/trust/store.d.ts.map +1 -0
- package/dist/trust/store.js +209 -0
- package/dist/trust/store.js.map +1 -0
- package/dist/trust/wrapper.d.ts +36 -0
- package/dist/trust/wrapper.d.ts.map +1 -0
- package/dist/trust/wrapper.js +142 -0
- package/dist/trust/wrapper.js.map +1 -0
- package/dist/usage.d.ts +53 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +124 -0
- package/dist/usage.js.map +1 -0
- package/dist/util/math.d.ts +9 -0
- package/dist/util/math.d.ts.map +1 -0
- package/dist/util/math.js +22 -0
- package/dist/util/math.js.map +1 -0
- package/dist/util/ssrf.d.ts +21 -0
- package/dist/util/ssrf.d.ts.map +1 -0
- package/dist/util/ssrf.js +77 -0
- package/dist/util/ssrf.js.map +1 -0
- package/dist/workers/index.d.ts +8 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +7 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/registry.d.ts +53 -0
- package/dist/workers/registry.d.ts.map +1 -0
- package/dist/workers/registry.js +38 -0
- package/dist/workers/registry.js.map +1 -0
- package/dist/workers/scoped-tools.d.ts +21 -0
- package/dist/workers/scoped-tools.d.ts.map +1 -0
- package/dist/workers/scoped-tools.js +111 -0
- package/dist/workers/scoped-tools.js.map +1 -0
- package/dist/workers/spawn.d.ts +62 -0
- package/dist/workers/spawn.d.ts.map +1 -0
- package/dist/workers/spawn.js +314 -0
- package/dist/workers/spawn.js.map +1 -0
- package/dist/workers/tools.d.ts +26 -0
- package/dist/workers/tools.d.ts.map +1 -0
- package/dist/workers/tools.js +380 -0
- package/dist/workers/tools.js.map +1 -0
- package/docker-compose.yml +72 -0
- package/package.json +16 -1
- package/run.sh +27 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust levels and runtime permissions.
|
|
3
|
+
*
|
|
4
|
+
* Three capability levels: Safe (local data the agent owns), Guarded
|
|
5
|
+
* (external content processed through worker isolation), and Elevated
|
|
6
|
+
* (direct external access: network, shell, unreviewed extensions).
|
|
7
|
+
*
|
|
8
|
+
* Elevated tools require explicit user approval on first use. Approvals
|
|
9
|
+
* are persisted to disk so they survive restarts.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Capability levels, ordered from least to most privileged.
|
|
13
|
+
*
|
|
14
|
+
* - `safe`: local data the agent owns outright (core memory, SQLite, session
|
|
15
|
+
* files). No user approval needed.
|
|
16
|
+
* - `guarded`: external content that arrives through the worker isolation
|
|
17
|
+
* boundary. The worker runs in a separate session with scoped tools, so
|
|
18
|
+
* untrusted content never reaches the main agent directly. No explicit
|
|
19
|
+
* approval needed, but the boundary itself is the protection.
|
|
20
|
+
* - `elevated`: direct external access without isolation — network requests,
|
|
21
|
+
* shell execution, unreviewed extension code. Requires explicit user approval
|
|
22
|
+
* before the tool may run.
|
|
23
|
+
*/
|
|
24
|
+
export type CapabilityLevel = "safe" | "guarded" | "elevated";
|
|
25
|
+
/**
|
|
26
|
+
* Specific capabilities a tool may require. Used in permission prompts
|
|
27
|
+
* to tell the user *what* the tool wants access to.
|
|
28
|
+
*/
|
|
29
|
+
export type Capability = "network" | "filesystem" | "shell";
|
|
30
|
+
/**
|
|
31
|
+
* Tool capability declaration. Tools that need elevated access declare
|
|
32
|
+
* their capabilities here.
|
|
33
|
+
*/
|
|
34
|
+
export interface ToolCapabilities {
|
|
35
|
+
/** Overall trust level required */
|
|
36
|
+
level: CapabilityLevel;
|
|
37
|
+
/** Specific capabilities needed (for elevated tools) */
|
|
38
|
+
capabilities?: Capability[];
|
|
39
|
+
/** Human-readable reason shown in the permission prompt */
|
|
40
|
+
reason?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface TrustStore {
|
|
43
|
+
/** Check if a tool is approved for elevated access. */
|
|
44
|
+
isApproved(toolName: string): boolean;
|
|
45
|
+
/** Grant approval for a tool. "always" persists to disk; "once" is session-only. */
|
|
46
|
+
approve(toolName: string, duration: "always" | "once"): void;
|
|
47
|
+
/** Revoke approval for a tool. */
|
|
48
|
+
revoke(toolName: string): void;
|
|
49
|
+
/** List all approved tools. */
|
|
50
|
+
listApproved(): Array<{
|
|
51
|
+
tool: string;
|
|
52
|
+
duration: "always" | "once";
|
|
53
|
+
}>;
|
|
54
|
+
/** Consume a one-time approval (returns true if it existed). */
|
|
55
|
+
consumeOnce(toolName: string): boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create a trust store backed by a JSON file. Pre-approved tools from config
|
|
59
|
+
* are always allowed; runtime approvals are stored in trust-approvals.json.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createTrustStore(dataDir: string, preApproved?: string[]): TrustStore;
|
|
62
|
+
/**
|
|
63
|
+
* Register a tool's capability requirements.
|
|
64
|
+
*/
|
|
65
|
+
export declare function registerToolCapabilities(toolName: string, capabilities: ToolCapabilities): void;
|
|
66
|
+
/**
|
|
67
|
+
* Get a tool's declared capabilities. Returns Safe if not registered.
|
|
68
|
+
*/
|
|
69
|
+
export declare function getToolCapabilities(toolName: string): ToolCapabilities;
|
|
70
|
+
export interface PermissionCheckResult {
|
|
71
|
+
allowed: boolean;
|
|
72
|
+
/** If not allowed, a message to show the user via the agent. */
|
|
73
|
+
blockReason?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check whether a tool call should be allowed. Safe and guarded tools always
|
|
77
|
+
* pass; elevated tools require explicit user approval.
|
|
78
|
+
*/
|
|
79
|
+
export declare function checkPermission(toolName: string, trustStore: TrustStore): PermissionCheckResult;
|
|
80
|
+
/**
|
|
81
|
+
* Mark a tool as pending approval for `userId`.
|
|
82
|
+
*
|
|
83
|
+
* This is a text-based approval handshake for channels that don't support
|
|
84
|
+
* inline buttons: the agent tells the user what it wants to do, sets a
|
|
85
|
+
* pending entry here, and the next message from that user is tested against
|
|
86
|
+
* an affirmative word list by checkPendingApproval().
|
|
87
|
+
*
|
|
88
|
+
* The key lives only in the in-process `pendingApprovals` Map. It is
|
|
89
|
+
* intentionally not persisted to disk: if the process restarts between the
|
|
90
|
+
* agent's request and the user's reply, the pending state is lost and the
|
|
91
|
+
* user simply needs to ask again. This avoids stale approval prompts
|
|
92
|
+
* surviving across restarts.
|
|
93
|
+
*/
|
|
94
|
+
export declare function setPendingApproval(userId: string, toolName: string): void;
|
|
95
|
+
/**
|
|
96
|
+
* Check whether the user's message completes a pending approval handshake.
|
|
97
|
+
*
|
|
98
|
+
* Returns the tool name that was approved if `userMessage` is an affirmative
|
|
99
|
+
* word and there is a pending entry for `userId`; returns null if the message
|
|
100
|
+
* is negative (clears the pending state), or if it is neither (leaves the
|
|
101
|
+
* pending state intact so the conversation can continue).
|
|
102
|
+
*
|
|
103
|
+
* The affirmative and negative word lists are intentionally short to avoid
|
|
104
|
+
* false positives: only unambiguous single-word replies are treated as
|
|
105
|
+
* approval signals.
|
|
106
|
+
*/
|
|
107
|
+
export declare function checkPendingApproval(userId: string, userMessage: string): string | null;
|
|
108
|
+
/**
|
|
109
|
+
* Check whether the user's message is an "always approve" grant rather than
|
|
110
|
+
* a one-time "yes".
|
|
111
|
+
*
|
|
112
|
+
* Only exact matches of "always" or "always allow" qualify. Phrases like
|
|
113
|
+
* "yes always" or "sure always" do NOT match here; they fall through to the
|
|
114
|
+
* normal affirmative check in checkPendingApproval() and result in a one-time
|
|
115
|
+
* approval. This keeps the always-grant intentional and explicit.
|
|
116
|
+
*/
|
|
117
|
+
export declare function isAlwaysApproval(userMessage: string): boolean;
|
|
118
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/trust/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;AAE9D;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC;AAE5D;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mCAAmC;IACnC,KAAK,EAAE,eAAe,CAAC;IACvB,wDAAwD;IACxD,YAAY,CAAC,EAAE,UAAU,EAAE,CAAC;IAC5B,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAkBD,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAEtC,oFAAoF;IACpF,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAC;IAE7D,kCAAkC;IAClC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B,+BAA+B;IAC/B,YAAY,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAA;KAAE,CAAC,CAAC;IAErE,gEAAgE;IAChE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;CACxC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,GAAE,MAAM,EAAO,GAAG,UAAU,CA6ExF;AASD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAE/F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAEtE;AAMD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,UAAU,GACrB,qBAAqB,CAwBvB;AAgBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAEzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwBvF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAG7D"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust levels and runtime permissions.
|
|
3
|
+
*
|
|
4
|
+
* Three capability levels: Safe (local data the agent owns), Guarded
|
|
5
|
+
* (external content processed through worker isolation), and Elevated
|
|
6
|
+
* (direct external access: network, shell, unreviewed extensions).
|
|
7
|
+
*
|
|
8
|
+
* Elevated tools require explicit user approval on first use. Approvals
|
|
9
|
+
* are persisted to disk so they survive restarts.
|
|
10
|
+
*/
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
/**
|
|
14
|
+
* Create a trust store backed by a JSON file. Pre-approved tools from config
|
|
15
|
+
* are always allowed; runtime approvals are stored in trust-approvals.json.
|
|
16
|
+
*/
|
|
17
|
+
export function createTrustStore(dataDir, preApproved = []) {
|
|
18
|
+
const filePath = path.join(dataDir, "trust-approvals.json");
|
|
19
|
+
const preApprovedSet = new Set(preApproved);
|
|
20
|
+
const onceApprovals = new Set();
|
|
21
|
+
function loadPersistedApprovals() {
|
|
22
|
+
if (!fs.existsSync(filePath))
|
|
23
|
+
return new Map();
|
|
24
|
+
try {
|
|
25
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
26
|
+
return new Map(data.map((r) => [r.tool, r]));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return new Map();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function savePersistedApprovals(approvals) {
|
|
33
|
+
const data = [...approvals.values()];
|
|
34
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
isApproved(toolName) {
|
|
38
|
+
if (preApprovedSet.has(toolName))
|
|
39
|
+
return true;
|
|
40
|
+
if (onceApprovals.has(toolName))
|
|
41
|
+
return true;
|
|
42
|
+
const persisted = loadPersistedApprovals();
|
|
43
|
+
return persisted.has(toolName);
|
|
44
|
+
},
|
|
45
|
+
approve(toolName, duration) {
|
|
46
|
+
if (duration === "once") {
|
|
47
|
+
onceApprovals.add(toolName);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const persisted = loadPersistedApprovals();
|
|
51
|
+
persisted.set(toolName, {
|
|
52
|
+
tool: toolName,
|
|
53
|
+
grantedAt: new Date().toISOString(),
|
|
54
|
+
duration: "always",
|
|
55
|
+
});
|
|
56
|
+
savePersistedApprovals(persisted);
|
|
57
|
+
},
|
|
58
|
+
revoke(toolName) {
|
|
59
|
+
onceApprovals.delete(toolName);
|
|
60
|
+
const persisted = loadPersistedApprovals();
|
|
61
|
+
if (persisted.delete(toolName)) {
|
|
62
|
+
savePersistedApprovals(persisted);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
listApproved() {
|
|
66
|
+
const result = [];
|
|
67
|
+
for (const tool of preApprovedSet) {
|
|
68
|
+
result.push({ tool, duration: "always" });
|
|
69
|
+
}
|
|
70
|
+
for (const tool of onceApprovals) {
|
|
71
|
+
if (!preApprovedSet.has(tool)) {
|
|
72
|
+
result.push({ tool, duration: "once" });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const persisted = loadPersistedApprovals();
|
|
76
|
+
for (const [tool, record] of persisted) {
|
|
77
|
+
if (!preApprovedSet.has(tool) && !onceApprovals.has(tool)) {
|
|
78
|
+
result.push({ tool, duration: record.duration });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
},
|
|
83
|
+
consumeOnce(toolName) {
|
|
84
|
+
if (onceApprovals.has(toolName)) {
|
|
85
|
+
onceApprovals.delete(toolName);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Capability registry
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
/** Map of tool name -> capabilities. Tools not in the registry are Safe. */
|
|
96
|
+
const toolCapabilityRegistry = new Map();
|
|
97
|
+
/**
|
|
98
|
+
* Register a tool's capability requirements.
|
|
99
|
+
*/
|
|
100
|
+
export function registerToolCapabilities(toolName, capabilities) {
|
|
101
|
+
toolCapabilityRegistry.set(toolName, capabilities);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get a tool's declared capabilities. Returns Safe if not registered.
|
|
105
|
+
*/
|
|
106
|
+
export function getToolCapabilities(toolName) {
|
|
107
|
+
return toolCapabilityRegistry.get(toolName) ?? { level: "safe" };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Check whether a tool call should be allowed. Safe and guarded tools always
|
|
111
|
+
* pass; elevated tools require explicit user approval.
|
|
112
|
+
*/
|
|
113
|
+
export function checkPermission(toolName, trustStore) {
|
|
114
|
+
const caps = getToolCapabilities(toolName);
|
|
115
|
+
if (caps.level === "safe" || caps.level === "guarded") {
|
|
116
|
+
return { allowed: true };
|
|
117
|
+
}
|
|
118
|
+
// Elevated: check approval
|
|
119
|
+
if (trustStore.isApproved(toolName)) {
|
|
120
|
+
// Consume one-time approvals
|
|
121
|
+
trustStore.consumeOnce(toolName);
|
|
122
|
+
return { allowed: true };
|
|
123
|
+
}
|
|
124
|
+
const capList = caps.capabilities?.join(", ") ?? "elevated access";
|
|
125
|
+
const reason = caps.reason ?? `This tool requires ${capList}.`;
|
|
126
|
+
return {
|
|
127
|
+
allowed: false,
|
|
128
|
+
blockReason: `Permission required: "${toolName}" needs ${capList}. ${reason} ` +
|
|
129
|
+
`Ask the user: "Can I use ${toolName}? It needs ${capList}." ` +
|
|
130
|
+
`If they approve, I'll remember the permission.`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Pending approval tracking
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// File-backed approvals (trust-approvals.json) persist across restarts because
|
|
137
|
+
// they represent deliberate, considered user decisions: "always allow this tool".
|
|
138
|
+
// Pending approvals, by contrast, are transient per-session state: the agent
|
|
139
|
+
// asked for permission in this conversation, the user hasn't replied yet. There
|
|
140
|
+
// is no value in carrying that half-finished handshake across a restart, and
|
|
141
|
+
// doing so could surface stale permission prompts after an unrelated restart.
|
|
142
|
+
// The in-process Map is therefore intentional and correct for pending state.
|
|
143
|
+
/** Tools waiting for user approval. Set by the agent when a tool is blocked. */
|
|
144
|
+
const pendingApprovals = new Map();
|
|
145
|
+
/**
|
|
146
|
+
* Mark a tool as pending approval for `userId`.
|
|
147
|
+
*
|
|
148
|
+
* This is a text-based approval handshake for channels that don't support
|
|
149
|
+
* inline buttons: the agent tells the user what it wants to do, sets a
|
|
150
|
+
* pending entry here, and the next message from that user is tested against
|
|
151
|
+
* an affirmative word list by checkPendingApproval().
|
|
152
|
+
*
|
|
153
|
+
* The key lives only in the in-process `pendingApprovals` Map. It is
|
|
154
|
+
* intentionally not persisted to disk: if the process restarts between the
|
|
155
|
+
* agent's request and the user's reply, the pending state is lost and the
|
|
156
|
+
* user simply needs to ask again. This avoids stale approval prompts
|
|
157
|
+
* surviving across restarts.
|
|
158
|
+
*/
|
|
159
|
+
export function setPendingApproval(userId, toolName) {
|
|
160
|
+
pendingApprovals.set(userId, toolName);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Check whether the user's message completes a pending approval handshake.
|
|
164
|
+
*
|
|
165
|
+
* Returns the tool name that was approved if `userMessage` is an affirmative
|
|
166
|
+
* word and there is a pending entry for `userId`; returns null if the message
|
|
167
|
+
* is negative (clears the pending state), or if it is neither (leaves the
|
|
168
|
+
* pending state intact so the conversation can continue).
|
|
169
|
+
*
|
|
170
|
+
* The affirmative and negative word lists are intentionally short to avoid
|
|
171
|
+
* false positives: only unambiguous single-word replies are treated as
|
|
172
|
+
* approval signals.
|
|
173
|
+
*/
|
|
174
|
+
export function checkPendingApproval(userId, userMessage) {
|
|
175
|
+
const toolName = pendingApprovals.get(userId);
|
|
176
|
+
if (!toolName)
|
|
177
|
+
return null;
|
|
178
|
+
const lower = userMessage.toLowerCase().trim();
|
|
179
|
+
const affirmative = [
|
|
180
|
+
"yes", "y", "yep", "yeah", "sure", "ok", "okay", "allow",
|
|
181
|
+
"allow it", "go ahead", "do it", "approved", "ja", "oke",
|
|
182
|
+
"always", "always allow",
|
|
183
|
+
];
|
|
184
|
+
if (affirmative.includes(lower)) {
|
|
185
|
+
pendingApprovals.delete(userId);
|
|
186
|
+
return toolName;
|
|
187
|
+
}
|
|
188
|
+
const negative = ["no", "n", "nope", "deny", "cancel", "nee"];
|
|
189
|
+
if (negative.includes(lower)) {
|
|
190
|
+
pendingApprovals.delete(userId);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
// Not a clear yes/no; leave the pending state but don't block conversation
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check whether the user's message is an "always approve" grant rather than
|
|
198
|
+
* a one-time "yes".
|
|
199
|
+
*
|
|
200
|
+
* Only exact matches of "always" or "always allow" qualify. Phrases like
|
|
201
|
+
* "yes always" or "sure always" do NOT match here; they fall through to the
|
|
202
|
+
* normal affirmative check in checkPendingApproval() and result in a one-time
|
|
203
|
+
* approval. This keeps the always-grant intentional and explicit.
|
|
204
|
+
*/
|
|
205
|
+
export function isAlwaysApproval(userMessage) {
|
|
206
|
+
const lower = userMessage.toLowerCase().trim();
|
|
207
|
+
return lower === "always" || lower === "always allow";
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/trust/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust-aware tool wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Wraps tool execute() with permission checks and the LLM guardrail.
|
|
5
|
+
* Elevated tools first need tool-level approval, then each invocation
|
|
6
|
+
* goes through the guardrail (ALLOW / ASK / BLOCK). Safe and guarded
|
|
7
|
+
* tools pass through without checks.
|
|
8
|
+
*/
|
|
9
|
+
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
10
|
+
import { type TrustStore } from "./store.js";
|
|
11
|
+
import type { Config } from "../config.js";
|
|
12
|
+
import type { ApprovalResult } from "../channels/types.js";
|
|
13
|
+
/**
|
|
14
|
+
* Callback for interactive approval requests. Sends a prompt to the user
|
|
15
|
+
* (inline buttons or text) and resolves with their decision.
|
|
16
|
+
*/
|
|
17
|
+
export type ApprovalCallback = (prompt: string, approvalKey: string) => Promise<ApprovalResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Context needed for guardrail evaluation.
|
|
20
|
+
*/
|
|
21
|
+
export interface TrustWrapperContext {
|
|
22
|
+
config: Config;
|
|
23
|
+
/** The last user message (for guardrail context). */
|
|
24
|
+
getLastUserMessage: () => string | undefined;
|
|
25
|
+
/** If provided, approval requests use this instead of text-based blocking. */
|
|
26
|
+
onApprovalNeeded?: ApprovalCallback;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Wrap a tool's execute function with trust check + LLM guardrail.
|
|
30
|
+
*/
|
|
31
|
+
export declare function wrapToolWithTrustCheck<T extends AgentTool<any>>(tool: T, trustStore: TrustStore, userId: string, context?: TrustWrapperContext): T;
|
|
32
|
+
/**
|
|
33
|
+
* Wrap all tools in an array with trust checks.
|
|
34
|
+
*/
|
|
35
|
+
export declare function wrapToolsWithTrustChecks(tools: AgentTool<any>[], trustStore: TrustStore, userId: string, context?: TrustWrapperContext): AgentTool<any>[];
|
|
36
|
+
//# sourceMappingURL=wrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../src/trust/wrapper.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,6BAA6B,CAAC;AAC9E,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAEhG;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC7C,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAoCD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,SAAS,CAAC,GAAG,CAAC,EAC7D,IAAI,EAAE,CAAC,EACP,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,mBAAmB,GAC5B,CAAC,CAwGH;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,EACvB,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,mBAAmB,GAC5B,SAAS,CAAC,GAAG,CAAC,EAAE,CAElB"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust-aware tool wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Wraps tool execute() with permission checks and the LLM guardrail.
|
|
5
|
+
* Elevated tools first need tool-level approval, then each invocation
|
|
6
|
+
* goes through the guardrail (ALLOW / ASK / BLOCK). Safe and guarded
|
|
7
|
+
* tools pass through without checks.
|
|
8
|
+
*/
|
|
9
|
+
import { checkPermission, setPendingApproval, getToolCapabilities, } from "./store.js";
|
|
10
|
+
import { evaluateToolCall } from "./guardrail.js";
|
|
11
|
+
function escapeHtml(text) {
|
|
12
|
+
return text
|
|
13
|
+
.replace(/&/g, "&")
|
|
14
|
+
.replace(/</g, "<")
|
|
15
|
+
.replace(/>/g, ">");
|
|
16
|
+
}
|
|
17
|
+
// TOOL_DESCRIPTIONS is shown to the user inside approval prompts so they see
|
|
18
|
+
// plain English rather than internal tool names. It is NOT passed to the LLM
|
|
19
|
+
// guardrail; the guardrail receives the tool's own `.description` property
|
|
20
|
+
// from the tool definition instead.
|
|
21
|
+
/**
|
|
22
|
+
* Human-readable labels for elevated tools, shown instead of raw tool names
|
|
23
|
+
* in user-facing approval prompts.
|
|
24
|
+
*/
|
|
25
|
+
const TOOL_DESCRIPTIONS = {
|
|
26
|
+
system_restart: "Restart to pick up changes",
|
|
27
|
+
shell_exec: "Run a shell command",
|
|
28
|
+
http_request: "Make a web request",
|
|
29
|
+
};
|
|
30
|
+
function humanToolDescription(toolName, reason) {
|
|
31
|
+
return TOOL_DESCRIPTIONS[toolName] ?? reason ?? "Perform an action that needs your permission";
|
|
32
|
+
}
|
|
33
|
+
function denied(toolName) {
|
|
34
|
+
return {
|
|
35
|
+
content: [{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: `User denied permission for ${toolName}. Tell the user the action was not taken.`,
|
|
38
|
+
}],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Wrap a tool's execute function with trust check + LLM guardrail.
|
|
43
|
+
*/
|
|
44
|
+
export function wrapToolWithTrustCheck(tool, trustStore, userId, context) {
|
|
45
|
+
const originalExecute = tool.execute;
|
|
46
|
+
const wrappedExecute = async (toolCallId, params, signal, onUpdate) => {
|
|
47
|
+
const caps = getToolCapabilities(tool.name);
|
|
48
|
+
// Safe and guarded tools: always execute
|
|
49
|
+
if (caps.level === "safe" || caps.level === "guarded") {
|
|
50
|
+
return originalExecute.call(tool, toolCallId, params, signal, onUpdate);
|
|
51
|
+
}
|
|
52
|
+
// Elevated: first check if the tool itself is approved
|
|
53
|
+
const permResult = checkPermission(tool.name, trustStore);
|
|
54
|
+
if (!permResult.allowed) {
|
|
55
|
+
const description = humanToolDescription(tool.name, caps.reason);
|
|
56
|
+
const prompt = `⚡ <b>Permission request</b>\n\n` +
|
|
57
|
+
`${escapeHtml(description)}`;
|
|
58
|
+
if (context?.onApprovalNeeded) {
|
|
59
|
+
const approvalKey = `tool:${userId}:${tool.name}`;
|
|
60
|
+
const result = await context.onApprovalNeeded(prompt, approvalKey);
|
|
61
|
+
if (result === "deny") {
|
|
62
|
+
return denied(tool.name);
|
|
63
|
+
}
|
|
64
|
+
const duration = result === "allow_always" ? "always" : "once";
|
|
65
|
+
trustStore.approve(tool.name, duration);
|
|
66
|
+
// Fall through to guardrail with the now-approved tool
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// No inline approval available — fall back to text-based flow
|
|
70
|
+
setPendingApproval(userId, tool.name);
|
|
71
|
+
return {
|
|
72
|
+
content: [{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: permResult.blockReason ?? `Permission denied for ${tool.name}.`,
|
|
75
|
+
}],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Tool is approved. Run the LLM guardrail on the specific arguments.
|
|
80
|
+
if (context?.config) {
|
|
81
|
+
const argsStr = typeof params === "string" ? params : JSON.stringify(params);
|
|
82
|
+
let guardrailResult;
|
|
83
|
+
try {
|
|
84
|
+
guardrailResult = await evaluateToolCall(context.config, {
|
|
85
|
+
toolName: tool.name,
|
|
86
|
+
args: argsStr,
|
|
87
|
+
userMessage: context.getLastUserMessage?.(),
|
|
88
|
+
toolDescription: tool.description,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Guardrail failure: fail safe to ASK
|
|
93
|
+
guardrailResult = { verdict: "ASK", reason: "Guardrail unavailable." };
|
|
94
|
+
}
|
|
95
|
+
if (guardrailResult.verdict === "BLOCK") {
|
|
96
|
+
return {
|
|
97
|
+
content: [{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: `Blocked: ${guardrailResult.reason}. ` +
|
|
100
|
+
`Tell the user this action was blocked for safety and explain why.`,
|
|
101
|
+
}],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (guardrailResult.verdict === "ASK") {
|
|
105
|
+
const description = humanToolDescription(tool.name, caps.reason);
|
|
106
|
+
const prompt = `⚠️ <b>Confirmation needed</b>\n\n` +
|
|
107
|
+
`${escapeHtml(description)}\n` +
|
|
108
|
+
`${escapeHtml(guardrailResult.reason)}`;
|
|
109
|
+
if (context?.onApprovalNeeded) {
|
|
110
|
+
const approvalKey = `guardrail:${userId}:${toolCallId}`;
|
|
111
|
+
const result = await context.onApprovalNeeded(prompt, approvalKey);
|
|
112
|
+
if (result === "deny") {
|
|
113
|
+
return denied(tool.name);
|
|
114
|
+
}
|
|
115
|
+
// allow or allow_always: fall through to execution
|
|
116
|
+
// (guardrail "always allow" for a specific invocation isn't meaningful, treat same as once)
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
setPendingApproval(userId, `${tool.name}:${toolCallId}`);
|
|
120
|
+
return {
|
|
121
|
+
content: [{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: `Guardrail flagged this action: ${guardrailResult.reason}. ` +
|
|
124
|
+
`Ask the user to confirm: describe what you're about to do and why, ` +
|
|
125
|
+
`then ask "Should I go ahead?"`,
|
|
126
|
+
}],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ALLOW: fall through to execution
|
|
131
|
+
}
|
|
132
|
+
return originalExecute.call(tool, toolCallId, params, signal, onUpdate);
|
|
133
|
+
};
|
|
134
|
+
return { ...tool, execute: wrappedExecute };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Wrap all tools in an array with trust checks.
|
|
138
|
+
*/
|
|
139
|
+
export function wrapToolsWithTrustChecks(tools, trustStore, userId, context) {
|
|
140
|
+
return tools.map((tool) => wrapToolWithTrustCheck(tool, trustStore, userId, context));
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=wrapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrapper.js","sourceRoot":"","sources":["../../src/trust/wrapper.ts"],"names":[],"mappings":"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"}
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-message token usage tracking.
|
|
3
|
+
*
|
|
4
|
+
* Records input tokens, output tokens, model, cost in USD, and latency for
|
|
5
|
+
* every agent response. Entries are appended to daily JSONL files under
|
|
6
|
+
* data/usage/. Used for cost monitoring and operator visibility into model
|
|
7
|
+
* spending over time.
|
|
8
|
+
*/
|
|
9
|
+
import type { Config, ModelEntry } from "./config.js";
|
|
10
|
+
export interface UsageRecord {
|
|
11
|
+
timestamp: string;
|
|
12
|
+
user_id: string;
|
|
13
|
+
model: string;
|
|
14
|
+
input_tokens: number;
|
|
15
|
+
output_tokens: number;
|
|
16
|
+
cost_usd: number;
|
|
17
|
+
latency_ms: number;
|
|
18
|
+
}
|
|
19
|
+
export interface UsageSummary {
|
|
20
|
+
date: string;
|
|
21
|
+
total_messages: number;
|
|
22
|
+
total_input_tokens: number;
|
|
23
|
+
total_output_tokens: number;
|
|
24
|
+
total_cost_usd: number;
|
|
25
|
+
by_user: Record<string, {
|
|
26
|
+
messages: number;
|
|
27
|
+
input_tokens: number;
|
|
28
|
+
output_tokens: number;
|
|
29
|
+
cost_usd: number;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export interface UsageTracker {
|
|
33
|
+
append(entry: Omit<UsageRecord, "timestamp">): Promise<void>;
|
|
34
|
+
summarize(date?: string): Promise<UsageSummary>;
|
|
35
|
+
}
|
|
36
|
+
export declare function calculateCostUsd(inputTokens: number, outputTokens: number, cost?: ModelEntry["cost"]): number;
|
|
37
|
+
/**
|
|
38
|
+
* Look up a model's cost config using a three-step cascade.
|
|
39
|
+
*
|
|
40
|
+
* The model string returned by the SDK may differ from the config format in
|
|
41
|
+
* two ways: (1) it may carry an embedded "provider/model" prefix, or (2) the
|
|
42
|
+
* provider context may only be known separately. The cascade handles both:
|
|
43
|
+
*
|
|
44
|
+
* Step 1 — explicit provider + bare model id: use the provider hint (if any)
|
|
45
|
+
* and strip any embedded prefix from the model string.
|
|
46
|
+
* Step 2 — embedded provider prefix: parse "provider/model" from the model
|
|
47
|
+
* string itself and look up that pair.
|
|
48
|
+
* Step 3 — bare id across all providers: fall back to searching every
|
|
49
|
+
* configured provider for a model whose id matches the full model string.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveModelCost(config: Config, provider: string | undefined, model: string | undefined): ModelEntry["cost"] | undefined;
|
|
52
|
+
export declare function createUsageTracker(dataDir: string): UsageTracker;
|
|
53
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CACb,MAAM,EACN;QACE,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;KAClB,CACF,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CACjD;AAYD,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,GACxB,MAAM,CAOR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,UAAU,CAAC,MAAM,CAAC,GAAG,SAAS,CA0BhC;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CA6DhE"}
|